From 810748d747194b50fde9c80d2145846e6352f9f3 Mon Sep 17 00:00:00 2001 From: Eric Hulburd Date: Mon, 15 Feb 2016 18:51:55 -0600 Subject: [PATCH] add composite line charts --- client/d3/bar/composite.js | 135 +++++++++++++++++++++++++++++++++++++ client/d3/base.js | 59 +++++++++++++++- client/d3/line/line.js | 34 +++++----- 3 files changed, 210 insertions(+), 18 deletions(-) create mode 100644 client/d3/bar/composite.js diff --git a/client/d3/bar/composite.js b/client/d3/bar/composite.js new file mode 100644 index 0000000..3b41cb9 --- /dev/null +++ b/client/d3/bar/composite.js @@ -0,0 +1,135 @@ +import Chart from './../base'; + +class BarLineChart extends Chart { + + get chart_options(){ + return Object.assign(Chart.DEFAULTS, { + }); + } + + afterAxes(){ + var chart = this; + line_chart.fnLine = d3.svg.line() + .interpolate(line_chart.chart_options.interpolation) + .x(function(d){ return line_chart.x_scale(d[line_chart.domain_attr]); }) + .y(function(d){ return line_chart.y_scale_right(d[line_chart.line_attr]); }); + } + + defineAxes(){ + var chart = this; + + // Axes Left + chart.y_scale_left = d3.scale.linear() + .range([chart.height, 0]); + chart.y_axis_left = d3.svg.axis() + .scale(chart.y_scale_left) + .orient("left") + .outerTickSize(0); + + // Axes Right + chart.y_scale_right = d3.scale.linear() + .range([chart.height, 0]); + chart.y_axis_left = d3.svg.axis() + .scale(chart.y_scale_right) + .orient("right") + .outerTickSize(0); + + chart.x_scale = d3.scale.ordinal() + .rangeRoundBands([chart.height, 0], 0.1); + + chart.x_axis = d3.svg.axis() + .scale(chart.x_scale) + .orient("bottom") + .outerTickSize(0) + //chart.x_axis.tickFormat(d3.time.format('%b %d at %H')) + //chart.x_axis.ticks(d3.time.hour, 12); + + // append axis groups. + chart.svg.append("g") + .attr("class", "d3-chart-range d3-chart-range-left d3-chart-axis"); + chart.svg.append("g") + .attr("class", "d3-chart-range d3-chart-range-right d3-chart-axis"); + chart.svg.append("g") + .attr("class", "d3-chart-domain d3-chart-axis") + .attr("transform", "translate(0, " + (chart.height) + ")"); + } + + defineDomain(domain){ + var chart = this; + + chart.domain = domain; + chart.x_scale.domain(domain); + chart.svg.select(".d3-chart-domain") + .call(chart.x_axis); + .selectAll("text") + .attr("transform", function(){ + var elem = this, + bbox = elem.getBBox(), + middleX = bbox.x + (bbox.width / 2), + middleY = bbox.y + (bbox.height / 2); + return "rotate(-30," + middleX + "," + middleY + ")"; + }); + } + + drawBarData(data){ + var chart = this; + data = chart.serializeBarData(data); + + chart.y_scale_left.domain(data.range_extent); + chart.svg.select(".d3-chart-range").call(chart.y_axis_left); + + data.series.forEach(function(series){ + var filtered_values = series.values.filter((value){ return chart.domain.indexOf(value[chart.domain_attr]) < 0; }) + bars = chart.svg.selectAll(".d3-chart-bar") + .data(series.values); + chart.applyData(series, bars.enter().append("rect")); + chart.applyData(series, bars.transition()); + bars.exit().remove(); + }); + } + + // helper method for drawData + applyBarData(series, elements){ + var chart = this, + series_class = "d3-chart-bar " + series.css_class; + elements + .attr("class", function(d){ return series_class + " " + d.css_class; }) + .attr("title", function(d){ return d.title; }) + .attr("width", chart.x_scale.rangeBand()) + .attr("x", chart.x_scale(series.title)) + .attr("height", return chart.y_scale(d[chart.bar_attr])) + .attr("y", function(d) { return chart.y_scale(d.cummulative); }) + .attr('fill', function(d){ return chart.fnColor(d.title); }); + } + + drawLineData(data){ + var chart = this, + nested_extent = chart.nestedExtent(data.series, 'values', chart.domain_attr, chart.line_attr); + + // calibrate axes + bar_chart.y_scale_right.domain([Math.min(0, nested_extent.range_min), nested_extent.range_max]); + bar_chart.svg.select(".d3-chart-range-right") + .call(bar_chart.y_axis_right); + + // draw lines + var line = g.selectAll(".d3-chart-line") + .data(data.series); + + [line.enter().append('g'), line.transition()].forEach((groups)=>{ + line_chart.applyLineData(groups, data.series); + }); + line.exit().remove(); + } + + applyLineData(groups){ + var chart = this; + groups + .attr('class', function(series){ return "d3-chart-line " + chart.cssClass(series); }) + .attr("title", function(series){ return series.title; }) + .append("path") + .attr("d", function(series){ return chart.fnLine(series.values.filter((value)=>{ return chart.domain.indexOf(value[chart.domain_attr]) < 0; })); }) + .style("stroke", function(series){ return line_chart.fnColor(series.title); }); + + } + +} diff --git a/client/d3/base.js b/client/d3/base.js index 2da3768..8730d89 100644 --- a/client/d3/base.js +++ b/client/d3/base.js @@ -21,7 +21,7 @@ const DEFAULTS = { } return array.join(' '); }, - toClass: function(series){ + toCssClass: function(series){ return series ? series.title.toLowerCase().replace(/\s+/g, '-') : ""; } }; @@ -45,6 +45,63 @@ class Chart { if (chart.afterAxes) chart.afterAxes(); } + defineAxes(){ + var chart = this; + + chart.y_scale = d3.scale.linear() + .range([chart.height, 0]); + chart.y_axis = d3.svg.axis() + .scale(chart.y_scale) + .orient("left") + .outerTickSize(1); + + if (chart.time_series){ + chart.x_scale = d3.time.scale() + .range([0, chart.width]); + } else { + chart.x_scale = d3.scale.linear() + .range([0, chart.width]); + } + + chart.x_axis = d3.svg.axis() + .scale(chart.x_scale) + .orient("bottom") + .outerTickSize(0) + //chart.x_axis.tickFormat(d3.time.format('%b %d at %H')) + //chart.x_axis.ticks(d3.time.hour, 12); + + // append axes + chart.svg.append("g") + .attr("class", "d3-chart-range d3-chart-axis"); + chart.svg.append("g") + .attr("class", "d3-chart-domain d3-chart-axis") + .attr("transform", "translate(0, " + (chart.height) + ")"); + } + + cssClass(series){ + var chart = this; + if (!chart.toCssClass) return ''; + return chart.toCssClass(series); + } + + nestedExtent(a, series_values, domain_attr, range_attr){ + var extent = { + min_domain: Infinity, + max_domain: -Infinity, + min_range: Infinity, + max_range: -Infinity + }; + a.forEach((series)=>{ + series[series_values].forEach((value)=>{ + extent.min_domain = Math.min(min_domain, value[domain_attr]); + extent.max_domain = Math.max(max_domain, value[domain_attr]); + extent.min_range = Math.min(min_range, value[range_attr]); + extent.max_range = Math.max(max_range, value[range_attr]); + }); + }); + returnextent + } + } Chart.DEFAULTS = DEFAULTS; diff --git a/client/d3/line/line.js b/client/d3/line/line.js index 20db7b8..146241b 100644 --- a/client/d3/line/line.js +++ b/client/d3/line/line.js @@ -13,36 +13,36 @@ class LineChart extends Chart { } defineAxes(){ - var line_chart = this; + var chart = this; - line_chart.y_scale = d3.scale.linear() - .range([line_chart.height, 0]); - line_chart.y_axis = d3.svg.axis() - .scale(line_chart.y_scale) + chart.y_scale = d3.scale.linear() + .range([chart.height, 0]); + chart.y_axis = d3.svg.axis() + .scale(chart.y_scale) .orient("left") .outerTickSize(1); - if (line_chart.time_series){ - line_chart.x_scale = d3.time.scale() - .range([0, line_chart.width]); + if (chart.time_series){ + chart.x_scale = d3.time.scale() + .range([0, chart.width]); } else { - line_chart.x_scale = d3.scale.linear() - .range([0, line_chart.width]); + chart.x_scale = d3.scale.linear() + .range([0, chart.width]); } - line_chart.x_axis = d3.svg.axis() - .scale(line_chart.x_scale) + chart.x_axis = d3.svg.axis() + .scale(chart.x_scale) .orient("bottom") .outerTickSize(0) - //line_chart.x_axis.tickFormat(d3.time.format('%b %d at %H')) - //line_chart.x_axis.ticks(d3.time.hour, 12); + //chart.x_axis.tickFormat(d3.time.format('%b %d at %H')) + //chart.x_axis.ticks(d3.time.hour, 12); // append axes - line_chart.svg.append("g") + chart.svg.append("g") .attr("class", "d3-chart-range d3-chart-axis"); - line_chart.svg.append("g") + chart.svg.append("g") .attr("class", "d3-chart-domain d3-chart-axis") - .attr("transform", "translate(0, " + (line_chart.height) + ")"); + .attr("transform", "translate(0, " + (chart.height) + ")"); } afterAxes(){