Built with blockbuilder.org
forked from cdagli's block: fresh block
forked from cdagli's block: Scroll Bar Chart (Using Zoom)
forked from cdagli's block: Scroll Bar Chart (24.12.2017)
| license: mit |
Built with blockbuilder.org
forked from cdagli's block: fresh block
forked from cdagli's block: Scroll Bar Chart (Using Zoom)
forked from cdagli's block: Scroll Bar Chart (24.12.2017)
| <!DOCTYPE html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <script src="http://d3js.org/d3.v3.min.js"></script> | |
| <style> | |
| .bar { | |
| fill: steelblue; | |
| } | |
| .subBar{ | |
| fill: steelblue; | |
| } | |
| .axis text { | |
| font: 10px sans-serif; | |
| user-select: none; | |
| } | |
| .axis path, | |
| .axis line { | |
| fill: none; | |
| stroke: #000; | |
| shape-rendering: crispEdges; | |
| } | |
| .x.axis path { | |
| display: none; | |
| } | |
| .brush .extent { | |
| stroke: #fff; | |
| fill-opacity: .125; | |
| shape-rendering: crispEdges; | |
| } | |
| .tooltip { | |
| position: absolute; | |
| pointer-events: none; | |
| padding: 12px; | |
| background: white; | |
| border: 1px solid gray; | |
| border-radius: 4px; | |
| } | |
| .tooltip-value | |
| { | |
| text-align: left; | |
| margin-top: 5px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!DOCTYPE html> | |
| <input name="updateButton" | |
| type="button" | |
| value="Random Data" | |
| onclick="changeData()" /> | |
| <input name="updateButton" | |
| type="button" | |
| value="Random Height" | |
| onclick="changeHeight()" /> | |
| <div class='chart1'></div> | |
| <div style="height: 50px; width: 100%; background: gray; color: white; padding: 50px">Some random div</div> | |
| <div class='chart2'></div> | |
| <div style="height: 50px; width: 100%; background: gray; color: white; padding: 50px">More random div</div> | |
| <script> | |
| 'use strict'; | |
| var DATA_COUNT = 30; | |
| var MAX_LABEL_LENGTH = 5; | |
| var MIN_LABEL_LENGTH = 50; | |
| var MAX_LABEL_LENGTH_ALLOWED = 27; | |
| var MARGIN_MODIFIER_CONSTANT = 4; | |
| var MIN_BAR_WIDTH = 20; | |
| var MIN_BAR_PADDING = 5; | |
| var data = null; | |
| var chart1Options = { | |
| color: 'orange', | |
| height: 550, | |
| width: 650, | |
| xAxisLabel: 'Name', | |
| yAxisLabel: 'Value' | |
| }; | |
| var chart1 = ChartFactory().createNew('.chart1', 1, chart1Options, data); | |
| changeData(); | |
| var chart2Options = { | |
| xAxisLabel: 'Name', | |
| yAxisLabel: 'Value' | |
| } | |
| var chart2 = ChartFactory().createNew('.chart2', 2, chart2Options); | |
| data = generateData(MAX_LABEL_LENGTH, MIN_LABEL_LENGTH); | |
| chart2.data(data).height(500).width(600); | |
| function changeHeight() { | |
| chart1.height(Math.random() * 400 + 250); | |
| } | |
| function changeData() { | |
| data = generateData(MAX_LABEL_LENGTH, MIN_LABEL_LENGTH); | |
| chart1.data(data); | |
| } | |
| setInterval(function () { | |
| data = generateData(MAX_LABEL_LENGTH, MIN_LABEL_LENGTH); | |
| chart2.data(data).height(Math.random() * 400 + 250).width(Math.random() * 400 + 250); | |
| }, 1000); | |
| function generateData(maxLabelLength, minLabelLength) { | |
| var data = []; | |
| var dataCount = Math.floor(Math.random() * DATA_COUNT + 5); | |
| for (var i = 0; i < dataCount; i++) { | |
| var datum = {}; | |
| var plusOrMinus = Math.random() < 0.5 ? -1 : 1; | |
| datum.name = stringGen(minLabelLength, maxLabelLength); | |
| datum.value = Math.floor(Math.random() * 6000 * plusOrMinus); | |
| data.push(datum); | |
| } | |
| function stringGen(minLength, maxLength) { | |
| var text = ''; | |
| var charset = 'abcdefghijklmnopqrstuvwxyz0123456789'; | |
| for (var i = 0; i < getRandomArbitrary(minLength, maxLength); i++) { | |
| text += charset.charAt(Math.floor(Math.random() * charset.length)); | |
| } | |
| return text; | |
| } | |
| function getRandomArbitrary(min, max) { | |
| return Math.round(Math.random() * (max - min) + min); | |
| } | |
| return data; | |
| } | |
| function ChartFactory() { | |
| function Chart(element, chartId, options, data) { | |
| if (!options) { | |
| options = { | |
| color: 'steelblue' | |
| }; | |
| } | |
| if (!data) { | |
| data = []; | |
| } | |
| var DEFAULT_HEIGHT_IN_PIXELS = 300; | |
| var DEFAULT_WIDTH_IN_PIXELS = 300; | |
| var DEFAULT_HEIGHT_OVERVIEW_IN_PIXELS = 75; | |
| var DEFAULT_MARGIN = { | |
| bottom: 80, | |
| left: 80, | |
| right: 30, | |
| top: 20 | |
| }; | |
| var DEFAULT_PADDING = { | |
| bottom: 0, | |
| left: 0, | |
| right: 0, | |
| top: 0 | |
| }; | |
| var bars = null; | |
| var barColor = options.color; | |
| var barsGroup = null; | |
| var barPadding = null; | |
| var barWidth = null; | |
| var chart = null; | |
| var chartClipPath = null; | |
| var defs = null; | |
| var height = null; | |
| var heightOverview = null; | |
| var horizontalGridLinesGroup = null; | |
| var horizontalGridLines = null; | |
| var innerHeight = null; | |
| var innerWidth = null; | |
| var margin = null; | |
| var marginModifier = null; | |
| var maxLabelLength = null; | |
| var outerHeight = null; | |
| var outerWidth = null; | |
| var overviewGroup = null; | |
| var overviewBarsGroup = null; | |
| var overviewVisible = null; | |
| var overviewRect = null; | |
| var dragHandle = null; | |
| var dragHandleWidth = null; | |
| var padding = null; | |
| var svg = null; | |
| var tooltipDiv = null; | |
| var tooltipGuideline = null; | |
| var width = null; | |
| var x = null; | |
| var xAxis = null; | |
| var xAxisClipPath = null; | |
| var xAxisGroup = null; | |
| var xAxisLabel = null; | |
| var xOverview = null; | |
| var y = null; | |
| var yAxis = null; | |
| var yAxisLabel = null; | |
| var xAxisTickLabels = null; | |
| var yAxisGroup = null; | |
| var yOverview = null; | |
| var yTickValues = null; | |
| var zoom = null; | |
| margin = Object.assign({}, DEFAULT_MARGIN); | |
| padding = Object.assign({}, DEFAULT_PADDING); | |
| width = null; | |
| height = null; | |
| heightOverview = DEFAULT_HEIGHT_OVERVIEW_IN_PIXELS; | |
| outerWidth = options.width || DEFAULT_WIDTH_IN_PIXELS; | |
| outerHeight = options.height || DEFAULT_HEIGHT_IN_PIXELS; | |
| innerHeight = null; | |
| innerWidth = null; | |
| render(element); | |
| if (data.length) { | |
| redraw(data); | |
| } | |
| function render(element) { | |
| innerWidth = outerWidth - margin.left - margin.right; | |
| innerHeight = outerHeight - margin.top - margin.bottom; | |
| width = innerWidth - padding.left - padding.right; | |
| height = innerHeight - padding.top - padding.bottom; | |
| svg = d3.select(element) | |
| .append('svg'); | |
| chart = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
| defs = chart.append('defs'); | |
| chartClipPath = defs.append('clipPath').attr('id', 'chart-' + chartId + '-clip-path').append('rect'); | |
| xAxisClipPath = defs.append('clipPath').attr('id', 'x-axis' + chartId + '-clip-path').append('rect'); | |
| tooltipDiv = d3.select('body') | |
| .append('div') | |
| .attr('class', 'tooltip') | |
| .style('opacity', 0); | |
| tooltipGuideline = chart.append('line'); | |
| xAxisGroup = chart.append('g').attr('class', 'x-axis').attr('clip-path', 'url(#x-axis' + chartId + '-clip-path)'); | |
| xAxisLabel = chart.append('text') | |
| .attr('text-anchor', 'middle'); | |
| yAxisLabel = chart.append('text') | |
| .attr('text-anchor', 'middle'); | |
| horizontalGridLinesGroup = chart.append('g'); | |
| barsGroup = chart.append('g'); | |
| barsGroup.attr('clip-path', 'url(#chart-' + chartId + '-clip-path)'); | |
| xAxisTickLabels = xAxisGroup.append('g') | |
| .attr('class', 'x axis') | |
| .style('text-anchor', 'end') | |
| yAxisGroup = chart.append('g').attr('class', 'y axis'); | |
| overviewGroup = chart.append('g').append('g'); | |
| overviewBarsGroup = overviewGroup.append('g'); | |
| overviewRect = overviewGroup.append('rect'); | |
| dragHandle = overviewGroup.append('rect') | |
| .attr('class', 'drag-handle') | |
| .style('fill', barColor) | |
| .style('opacity', .3); | |
| zoom = d3.behavior.zoom().scaleExtent([1, 1]); | |
| } | |
| Chart.prototype.height = function (value) { | |
| if (!arguments.length) { | |
| return outerHeight; | |
| } | |
| outerHeight = value; | |
| innerHeight = outerHeight - margin.top - margin.bottom; | |
| height = innerHeight - padding.top - padding.bottom; | |
| if (typeof redraw === 'function') { | |
| redraw(); | |
| } | |
| return this; | |
| }; | |
| Chart.prototype.width = function (value) { | |
| if (!arguments.length) { | |
| return outerWidth; | |
| } | |
| outerWidth = value; | |
| innerWidth = outerWidth - margin.left - margin.right; | |
| width = innerWidth - padding.left - padding.right; | |
| if (typeof redraw === 'function') { | |
| redraw(); | |
| } | |
| return this; | |
| }; | |
| Chart.prototype.data = function (value) { | |
| if (!arguments.length) { | |
| return data; | |
| } | |
| data = value; | |
| if (typeof redraw === 'function') { | |
| redraw(); | |
| } | |
| return this; | |
| }; | |
| function setXScale() { | |
| x = d3.scale.ordinal() | |
| .domain(data.map(function (d) { | |
| return d.name; | |
| })) | |
| .range(data.map(function (d, i) { | |
| return i * (barWidth + barPadding); | |
| })); | |
| xAxis = d3.svg.axis() | |
| .scale(x) | |
| .orient('bottom') | |
| xOverview = d3.scale.ordinal() | |
| .domain(data.map(function (d) { | |
| return d.name; | |
| })) | |
| .rangeBands([0, width], barPadding / barWidth, 0); | |
| } | |
| function setYScale() { | |
| var yMinValue = d3.min(data, function (d) { | |
| return d.value; | |
| }); | |
| var yMaxValue = d3.max(data, function (d) { | |
| return d.value; | |
| }); | |
| y = d3.scale.linear() | |
| .domain([yMinValue > 0 ? 0 : yMinValue, yMaxValue < 0 ? 0 : yMaxValue]) | |
| .range([height, 0]).nice(); | |
| yTickValues = generateYAxisTicksProportionalToHeight(data, height, y.ticks()); | |
| yAxis = d3.svg.axis() | |
| .scale(y) | |
| .tickValues(yTickValues) | |
| .orient('left'); | |
| yOverview = d3.scale.linear().range([heightOverview, 0]); | |
| yOverview.domain(y.domain()); | |
| } | |
| function adjustSVGHeight() { | |
| maxLabelLength = d3.max(data.map(function (d) { return d.name.length; })); | |
| marginModifier = maxLabelLength < MAX_LABEL_LENGTH_ALLOWED ? maxLabelLength : MAX_LABEL_LENGTH_ALLOWED; | |
| margin.bottom = DEFAULT_MARGIN.bottom + marginModifier * MARGIN_MODIFIER_CONSTANT, | |
| innerWidth = outerWidth - margin.left - margin.right; | |
| innerHeight = outerHeight - margin.top - margin.bottom; | |
| width = innerWidth - padding.left - padding.right; | |
| height = innerHeight - padding.top - padding.bottom; | |
| var totalHeight = outerHeight + (overviewVisible ? heightOverview : 0); | |
| svg.attr('width', outerWidth) | |
| .attr('height', totalHeight); | |
| } | |
| function showTooltip(data) { | |
| tooltipDiv.style('opacity', .9); | |
| tooltipDiv.html('<div class="tooltip-name" style="color:' + options.color + '">' + data.name + '</div><div class="tooltip-value">Value:' + data.value + '</div>') | |
| .style('left', (d3.event.pageX + 25) + 'px') | |
| .style('top', (d3.event.pageY - 25) + 'px'); | |
| tooltipGuideline.attr({ | |
| 'stroke': 'gray', | |
| 'stroke-dasharray': '3,3', | |
| 'stroke-width': '1px', | |
| 'x1': 0, | |
| 'x2': width, | |
| 'y1': y(data.value), | |
| 'y2': y(data.value), | |
| }) | |
| .style('display', 'block'); | |
| } | |
| function hideTooltip() { | |
| tooltipDiv.style('opacity', 0); | |
| tooltipGuideline.style('display', 'none'); | |
| } | |
| function calculateBarWidth() { | |
| var calculatedBarWidth = width / data.length; | |
| if (calculatedBarWidth > MIN_BAR_WIDTH) { | |
| barWidth = calculatedBarWidth * 90 / 100; | |
| barPadding = calculatedBarWidth * 10 / 100; | |
| overviewVisible = false; | |
| } else { | |
| barWidth = MIN_BAR_WIDTH; | |
| barPadding = MIN_BAR_PADDING; | |
| overviewVisible = true; | |
| } | |
| } | |
| function redraw() { | |
| resetDrag(); | |
| calculateBarWidth(); | |
| adjustSVGHeight(); | |
| setXScale(); | |
| setYScale(); | |
| var labelRotationValues = calculateRotationDegree(barWidth, data); | |
| xAxisClipPath.attr('width', width).attr('height', height + margin.bottom); | |
| chartClipPath.attr('width', width).attr('height', height); | |
| xAxisTickLabels.transition() | |
| .attr('transform', 'translate(' + (barWidth + barPadding) / 2 + ',' + height + ')') | |
| .call(xAxis) | |
| .selectAll('text') | |
| .attr('y', labelRotationValues.labelYDistance) | |
| .attr('x', labelRotationValues.labelXDistance) | |
| .attr('dy', 0) | |
| .attr('transform', 'rotate(' + labelRotationValues.rotationDegree + ')') | |
| .style('text-anchor', 'end') | |
| .text(function (d) { | |
| if (d.length > MAX_LABEL_LENGTH_ALLOWED) { | |
| return d.substring(0, MAX_LABEL_LENGTH_ALLOWED) + '...'; | |
| } | |
| else { | |
| return d; | |
| } | |
| }); | |
| yAxisGroup.transition().call(yAxis); | |
| horizontalGridLines = horizontalGridLinesGroup.selectAll('line.horizontalGrid').data(yTickValues); | |
| horizontalGridLines.exit().remove(); | |
| horizontalGridLines.enter() | |
| .append('line') | |
| .attr( | |
| { | |
| 'class': 'horizontalGrid', | |
| 'fill': 'none', | |
| 'opacity': function (d) { return d === 0 ? 1 : .1; }, | |
| 'shape-rendering': 'crispEdges', | |
| 'stroke': 'black', | |
| 'stroke-width': '1px', | |
| 'x1': 0, | |
| 'x2': width, | |
| 'y1': function (d) { return y(d); }, | |
| 'y2': function (d) { return y(d); } | |
| }); | |
| horizontalGridLines.data(yTickValues) | |
| .transition() | |
| .attr( | |
| { | |
| 'class': 'horizontalGrid', | |
| 'fill': 'none', | |
| 'opacity': function (d) { return d === 0 ? 1 : .1; }, | |
| 'shape-rendering': 'crispEdges', | |
| 'stroke': 'black', | |
| 'stroke-width': '1px', | |
| 'x1': 0, | |
| 'x2': width, | |
| 'y1': function (d) { return y(d); }, | |
| 'y2': function (d) { return y(d); } | |
| }); | |
| xAxisLabel | |
| .transition() | |
| .attr('transform', 'translate(' + (width / 2) + ',' + (outerHeight - margin.bottom + labelRotationValues.oppositeLength) + ')') | |
| .text(options.xAxisLabel); | |
| yAxisLabel | |
| .transition() | |
| .attr('transform', 'translate(' + -margin.left / 2 + ',' + (height / 2) + ')rotate(-90)') | |
| .text(options.yAxisLabel); | |
| bars = barsGroup.selectAll('.bar') | |
| .data(data); | |
| bars.exit().remove(); | |
| bars.enter().append('rect') | |
| .attr('class', 'bar') | |
| .attr('x', function (d) { | |
| return x(d.name); | |
| }) | |
| .attr('y', function (d) { | |
| return d.value > 0 ? y(d.value) : y(0); | |
| }) | |
| .attr('height', function (d) { | |
| return Math.abs(y(d.value) - y(0)); | |
| }) | |
| .attr('width', barWidth) | |
| .style('fill', barColor) | |
| .on('mouseenter', showTooltip.bind(this)) | |
| .on('touchstart', showTooltip.bind(this)) | |
| .on('mouseleave', hideTooltip.bind(this)) | |
| .on('touchend', hideTooltip.bind(this)); | |
| bars.data(data) | |
| .transition() | |
| .attr('x', function (d) { | |
| return x(d.name); | |
| }) | |
| .attr('y', function (d) { | |
| return d.value > 0 ? y(d.value) : y(0); | |
| }) | |
| .attr('height', function (d) { | |
| return Math.abs(y(d.value) - y(0)); | |
| }) | |
| .attr('width', barWidth); | |
| if (overviewVisible) { | |
| overviewGroup | |
| .attr('width', width) | |
| .attr('height', heightOverview) | |
| .attr('visibility', 'visible'); | |
| var subBarYValue = function (datum) { | |
| return height + labelRotationValues.oppositeLength + heightOverview / 2 + (datum.value > 0 ? yOverview(datum.value) : yOverview(0)); | |
| }; | |
| var overviewBars = overviewBarsGroup.selectAll('.subBar') | |
| .data(data); | |
| overviewBars.exit().remove(); | |
| overviewBars.enter().append('rect') | |
| .classed('subBar', true) | |
| .attr({ | |
| height: function (d) { | |
| return Math.abs(yOverview(d.value) - yOverview(0)); | |
| }, | |
| width: function (d) { | |
| return xOverview.rangeBand() | |
| }, | |
| x: function (d) { | |
| return xOverview(d.name); | |
| }, | |
| y: subBarYValue | |
| }) | |
| .style('fill', barColor); | |
| overviewBars | |
| .transition() | |
| .attr({ | |
| height: function (d) { | |
| return Math.abs(yOverview(d.value) - yOverview(0)); | |
| }, | |
| width: function (d) { | |
| return xOverview.rangeBand() | |
| }, | |
| x: function (d) { | |
| return xOverview(d.name); | |
| }, | |
| y: subBarYValue | |
| }); | |
| overviewRect.attr('y', height + heightOverview / 2 + labelRotationValues.oppositeLength) | |
| .attr('width', width) | |
| .attr('height', heightOverview) | |
| .style('opacity', '0') | |
| .style('cursor', 'pointer').on('click', click); | |
| dragHandleWidth = (width / (barWidth) * (xOverview.rangeBand())); | |
| dragHandle | |
| .transition() | |
| .attr('x', 0) | |
| .attr('y', height + heightOverview / 2 + labelRotationValues.oppositeLength) | |
| .attr('height', heightOverview) | |
| .attr('width', dragHandleWidth) | |
| .attr('pointer-events', 'all') | |
| .attr('cursor', 'ew-resize') | |
| dragHandle.call(d3.behavior.drag().on('drag', drag)); | |
| } | |
| else { | |
| overviewGroup.attr('visibility', 'hidden'); | |
| } | |
| } | |
| function drag() { | |
| var nx = d3.event.dx; | |
| var dragHandleX = parseFloat(dragHandle.attr('x')) + nx; | |
| var customScale = d3.scale.linear().domain([0, width]).range([0, ((barWidth + barPadding) * data.length)]); | |
| var transformX = customScale(dragHandleX); | |
| var xEndValue = customScale(xOverview(data[data.length - 1].name)) - customScale(dragHandleWidth) + barWidth; | |
| if (transformX < xEndValue && transformX >= 0) { | |
| dragHandle.attr('x', dragHandleX); | |
| bars.attr('transform', 'translate(' + -transformX + ',0)'); | |
| chart.select('.x.axis').attr('transform', 'translate(' + (-transformX + (barWidth + barPadding) / 2) + ',' + (height) + ')'); | |
| chart.select('.y.axis').call(yAxis); | |
| zoom.translate([transformX, 0]); | |
| } | |
| } | |
| function resetDrag() { | |
| var customScale = d3.scale.linear().domain([0, width]).range([0, ((barWidth + barPadding) * data.length)]) | |
| var transformX = customScale(0); | |
| if (bars) { | |
| bars.attr('transform', 'translate(' + transformX + ',0)'); | |
| chart.select('.x.axis').attr('transform', 'translate(' + (transformX) + ',' + (height) + ')'); | |
| chart.select('.y.axis').call(yAxis); | |
| zoom.translate([0, 0]); | |
| } | |
| } | |
| function click() { | |
| var newX = null; | |
| var dragHandleX = null; | |
| var customScale = d3.scale.linear().domain([0, width]).range([0, ((barWidth + barPadding) * data.length)]) | |
| dragHandleX = (d3.event.x - margin.left) - dragHandleWidth / 2; | |
| newX = customScale(dragHandleX); | |
| if (dragHandleX > width - dragHandleWidth) { | |
| newX = customScale(width - dragHandleWidth); | |
| dragHandleX = width - dragHandleWidth; | |
| } else if (dragHandleX - (dragHandleWidth / 2) < 0) { | |
| newX = 0; | |
| dragHandleX = 0; | |
| } | |
| dragHandle.transition().attr('x', dragHandleX); | |
| bars.transition().duration(300).attr('transform', 'translate(' + (-newX) + ',0)'); | |
| chart.transition().duration(300).select('.x.axis').attr('transform', 'translate(' + -(newX - (barWidth + barPadding) / 2) + ',' + (height) + ')'); | |
| chart.select('.y.axis').call(yAxis); | |
| zoom.translate([-newX, 0]); | |
| } | |
| } | |
| function createNew(element, chartId, options, data) { | |
| return new Chart(element, chartId, options, data); | |
| } | |
| return { | |
| createNew: createNew | |
| }; | |
| } | |
| function calculateRotationDegree(barWidth, data) { | |
| var CHARACTER_LENGTH_IN_PIXELS = 5; | |
| var MIN_OPPOSITE_LENGTH_IN_PIXELS = 20; | |
| var xTickLengths = data.map(function (d) { return d.name.length; }); | |
| var maxLabelLength = d3.max(xTickLengths); | |
| var hypotenuseLength = maxLabelLength * CHARACTER_LENGTH_IN_PIXELS; | |
| var adjacentLength = barWidth / 2; | |
| var rotationDegree = null; | |
| if (adjacentLength > hypotenuseLength) { | |
| rotationDegree = 0; | |
| } else { | |
| rotationDegree = -1 * Math.acos(adjacentLength / hypotenuseLength) * (180 / Math.PI); | |
| } | |
| var oppositeLength = Math.sqrt(Math.pow(hypotenuseLength, 2) - Math.pow(adjacentLength, 2)); | |
| var yDistanceScale = d3.scale.pow().exponent(1.9).range([20, 0]).domain([0, -90]); | |
| var xDistanceScale = d3.scale.pow().exponent(0.1).range([25, -10]).domain([0, -90]); | |
| return { | |
| labelXDistance: xDistanceScale(rotationDegree), | |
| labelYDistance: yDistanceScale(rotationDegree), | |
| oppositeLength: isNaN(oppositeLength) ? MIN_OPPOSITE_LENGTH_IN_PIXELS : (oppositeLength + MIN_OPPOSITE_LENGTH_IN_PIXELS), | |
| rotationDegree: rotationDegree | |
| }; | |
| } | |
| function generateYAxisTicksProportionalToHeight(data, height, yTicks) { | |
| var MINIMUM_TICK_HEIGHT_IN_PIXELS = 27; | |
| var stepSize = Math.abs(yTicks[1] - yTicks[0]); | |
| var lastTick = yTicks[yTicks.length - 1]; | |
| var maxMultiplier = lastTick === 0 ? 1 : lastTick / stepSize; | |
| var yTickSpaceInPixels = height / yTicks.length; | |
| var allowedMultipliers = calculateFactors(maxMultiplier); | |
| var yTickScale = d3.scale.quantize().range(allowedMultipliers.reverse()).domain([0, MINIMUM_TICK_HEIGHT_IN_PIXELS]); | |
| var calculatedMultiplier = yTickScale(yTickSpaceInPixels); | |
| var newStepSize = calculatedMultiplier * stepSize; | |
| var tickMultiplier = Math.floor(yTicks[0] / newStepSize); | |
| var tickStartValue = tickMultiplier * newStepSize; | |
| var newTicks = []; | |
| yTicks.forEach(function (tick) { | |
| var tickValueNewIndex = Math.floor((tick / newStepSize)) - tickMultiplier; | |
| var tickValue = tickStartValue + ((tickValueNewIndex) * newStepSize); | |
| newTicks[tickValueNewIndex] = tickValue; | |
| }); | |
| newTicks[0] = yTicks[0] | |
| newTicks[newTicks.length - 1] = yTicks[yTicks.length - 1] | |
| return newTicks; | |
| function calculateFactors(num) { | |
| var arr = []; | |
| for (var i = 1; i <= num; i++) { | |
| if (num % i == 0) { | |
| arr.push(i); | |
| } | |
| } | |
| return arr; | |
| } | |
| } | |
| </script> | |
| </body> |