Created
February 11, 2014 16:34
-
-
Save lwhorton/8938460 to your computer and use it in GitHub Desktop.
Basic flag-configurable line chart.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // These are the most basic properties required by a line chart. | |
| function Chart() {} | |
| Chart.prototype.data = null; | |
| Chart.prototype.svgWidth = 600; | |
| Chart.prototype.svgHeight = 500; | |
| Chart.prototype.margin = {top: 20.0, right: 20.0, bottom: 20.0, left: 20.0}; | |
| Chart.prototype.width = 500; | |
| Chart.prototype.height = 400; | |
| Chart.prototype.xValue = function (d) { return d[0]; }; | |
| Chart.prototype.yValue = function (d) { return d[1]; }; | |
| Chart.prototype.xScale = d3.time.scale(); | |
| Chart.prototype.yScale = d3.scale.linear(); | |
| Chart.prototype.line = { | |
| render: function (lineSelection, lineFn) { | |
| // The 'this' context is the chart object instance. | |
| lineSelection | |
| .transition() | |
| .duration(this.transitionDuration) | |
| .attr("d", lineFn) | |
| } | |
| }; | |
| // Add-ons: functionality beyond this point is extra fluff not required to render a line chart. | |
| // Animation | |
| Chart.prototype.transitionDuration = 500; | |
| // Axes with default render methods. | |
| Chart.prototype.xAxis = { | |
| flag: false, | |
| render: function (axisSelection, axisFn) { | |
| axisSelection | |
| .transition() | |
| .duration(this.transitionDuration) | |
| .attr("transform", "translate(0," + this.yScale.range()[0] + ")") | |
| .call(axisFn); | |
| } | |
| }; | |
| Chart.prototype.yAxis = { | |
| flag: false, | |
| render: function (axisSelection, axisFn) { | |
| axisSelection | |
| .transition() | |
| .duration(this.transitionDuration) | |
| .call(axisFn); | |
| } | |
| }; | |
| return { | |
| lineChart: function (config) { | |
| // Instantiate a new chart and override its default prototype settings with properties | |
| // set on the chart that are provided by the given config. | |
| var c = new Chart(); | |
| Object.keys(config).forEach(function(key) { | |
| if (config[key] === true) { | |
| // If the config is a truthy boolean, enable the feature and use | |
| // the default render method provided by the prototype. | |
| c[key].flag = true; | |
| } else if (config[key] === false) { | |
| // If the config is a falsy boolean, disable the render method provided | |
| // by the prototype, but allow function continuity without actually rendering. | |
| c[key].flag = false; | |
| c[key].render = function () { return false; }; | |
| } else if (Object.prototype.toString.call(config[key]) === '[object Function]') { | |
| // Use inflection on the default prototype to determine if the provided | |
| // function is an accessor that simply needs overriding, or a render function | |
| // that also requires setting a flag on the property object. | |
| if (Object.prototype.toString.call(Chart.prototype[key]) === '[object Object]') { | |
| c[key].flag = true; | |
| c[key].render = config[key]; | |
| } else { | |
| c[key] = config[key]; | |
| } | |
| } else { | |
| // If the config is anything else (number, string, etc.), just override the prototype default. | |
| c[key] = config[key]; | |
| } | |
| }); | |
| // Rendering or re-rendering the chart follows the same procedure - given a d3 selection, | |
| // define that selection's dimensions, scales, axes, or other components, and either | |
| // (add to) or (update existing) elements on the DOM. | |
| c.render = function (selection) { | |
| // Note: 'this' refers to the HTML element, as per d3js API documentation on each(). | |
| selection.each(function (chartData) { | |
| // If we have no data to render, return. | |
| if (!chartData) return c; | |
| // Convert chartData to a copy of standard representations | |
| // (greedily, which is needed for nondeterministic accessors). | |
| // This allows us to reference all future data | |
| c.data = chartData.map(function(d, i) { | |
| return [c.xValue.call(null, d, i), c.yValue.call(null, d, i)]; | |
| }); | |
| // Update our chart's width / height. | |
| c.width = c.svgWidth - c.margin.left - c.margin.right; | |
| c.height = c.svgHeight - c.margin.top - c.margin.bottom; | |
| // Update the x-scale. | |
| c.xScale | |
| .domain(d3.extent(c.data, Chart.prototype.xValue)) | |
| .range([0, c.width]); | |
| // Update the y-scale. | |
| c.yScale | |
| .domain([0, d3.max(c.data, Chart.prototype.yValue)]) | |
| .range([c.height, 0]); | |
| // Select the svg element, if it exists. Because we have been | |
| // handed a d3 selection, "this" provides a "selectAll". Because | |
| // we attached data to the chart with a .datum() previously, | |
| // we have access to a .enter() method within this selection. | |
| var svg = d3.select(this).selectAll("svg").data([c.data]); | |
| // Create the chart's components. | |
| var gEnter = svg.enter().append("svg").append("g"); | |
| gEnter.append("path") | |
| .datum(c.data) | |
| .attr("class", "line line-color-1") | |
| .attr('stroke', '#569bbe') | |
| .attr('stroke-width', 1) | |
| .attr('fill', 'none'); | |
| gEnter.append("g").attr("class", "x-axis"); | |
| gEnter.append("g").attr("class", "y-axis"); | |
| // Update the svg dimensions. | |
| svg.attr("width", c.svgWidth) | |
| .attr("height", c.svgHeight); | |
| // Update the chart dimensions (centered inside the svg). | |
| var g = svg.select("g") | |
| .attr("transform", "translate(" + c.margin.left + "," + c.margin.top + ")"); | |
| // Render the line by calling the line render function, which can be the default | |
| // or the user-provided function. In the instance of a user's render function, we | |
| // pass them handles to the lineSelection, as well as the function describing the line. | |
| c.line.render.call(c, | |
| g.select(".line"), | |
| d3.svg.line() | |
| .x(function (d, i) { return c.xScale(Chart.prototype.xValue.call(null, d, i)); }) | |
| .y(function (d, i) { return c.yScale(Chart.prototype.yValue.call(null, d, i)); })); | |
| // ------------------------------------------------------------------------------------- | |
| // Add-ons: functionality beyond this point is optional. In most instances, we are | |
| // providing user's custom functions a handle to the selection, and the 'this' context | |
| // of the chart instance (see line-render notes above). | |
| // ------------------------------------------------------------------------------------- | |
| // Update the x-axis. | |
| if (c.xAxis.flag) | |
| c.xAxis.render.call(c, | |
| g.select(".x-axis"), | |
| d3.svg.axis().scale(c.xScale).orient('top')); | |
| // Update the y-axis. | |
| if (c.yAxis.flag) | |
| c.yAxis.render.call(c, | |
| g.select(".y-axis"), | |
| d3.svg.axis().scale(c.yScale).orient('left')); | |
| }); | |
| } | |
| return c; | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment