dont know what happened
This commit is contained in:
48
client/d3/bar/base.js
vendored
Normal file
48
client/d3/bar/base.js
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import extend from 'extend';
|
||||
|
||||
// This class is inspired by // http://bl.ocks.org/mbostock/3885304.
|
||||
class BarChart {
|
||||
|
||||
get chart_options(){
|
||||
return {
|
||||
series_opacity_gradient: true,
|
||||
margin: {top: 0, left: 70, bottom: 50, right: 20}
|
||||
};
|
||||
}
|
||||
|
||||
serializeData(data){
|
||||
var bar_chart = this,
|
||||
serialized_data = {
|
||||
max: undefined,
|
||||
series: []
|
||||
};
|
||||
|
||||
data.forEach(function(data_set){
|
||||
var series = extend({
|
||||
css_class: bar_chart.toClass ? bar_chart.toClass(data_set) : "",
|
||||
title: bar_chart.titleize ? bar_chart.titleize(data_set) : ""
|
||||
}, data_set);
|
||||
series.total = 0;
|
||||
series.values = [];
|
||||
data_set.values.forEach(function(datum, j){
|
||||
var series_datum = extend({
|
||||
name: datum.name,
|
||||
value: datum.value,
|
||||
cummulative: series.total,
|
||||
css_class: bar_chart.toClass ? bar_chart.toClass(data_set, datum) : "",
|
||||
title: bar_chart.titleize ? bar_chart.titleize(data_set, datum) : "",
|
||||
opacity: 1.0 - 0.5 * (j / data_set.values.length)
|
||||
}, datum);
|
||||
series_datum.series = series;
|
||||
series.total += datum.value;
|
||||
series.values.push(series_datum);
|
||||
});
|
||||
serialized_data.series.push(series);
|
||||
serialized_data.max = serialized_data.max === undefined ?
|
||||
series.total :
|
||||
Math.max(serialized_data.max, Math.abs(series.total));
|
||||
});
|
||||
return serialized_data;
|
||||
};
|
||||
|
||||
}
|
||||
135
client/d3/bar/composite.js
vendored
Normal file
135
client/d3/bar/composite.js
vendored
Normal file
@@ -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); });
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
76
client/d3/bar/horizontal.js
vendored
Normal file
76
client/d3/bar/horizontal.js
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
import BarChart from './bar.base';
|
||||
|
||||
class HorizontalBarChart extends BarChart {
|
||||
|
||||
defineAxes(){
|
||||
var bar_chart = this;
|
||||
bar_chart.y_scale = d3.scale.ordinal()
|
||||
.rangeRoundBands([bar_chart.height, 0], 0.1);
|
||||
|
||||
bar_chart.y_axis = d3.svg.axis()
|
||||
.scale(bar_chart.y_scale)
|
||||
.orient("left");
|
||||
|
||||
bar_chart.x_scale = d3.scale.linear()
|
||||
.range([0, bar_chart.width]);
|
||||
|
||||
bar_chart.x_axis = d3.svg.axis()
|
||||
.scale(bar_chart.x_scale)
|
||||
.orient("bottom")
|
||||
.ticks(bar_chart.range_ticks)
|
||||
.outerTickSize(0);
|
||||
|
||||
// append axes
|
||||
bar_chart.svg.append("g")
|
||||
.attr("class", "d3-chart-domain d3-chart-axis");
|
||||
bar_chart.svg.append("g")
|
||||
.attr("class", "d3-chart-range d3-chart-axis")
|
||||
.attr("transform", "translate(0, " + (bar_chart.height - bar_chart.margin.top) + ")");
|
||||
}
|
||||
|
||||
drawData(data){
|
||||
var bar_chart = this;
|
||||
data = bar_chart.serializeData(data);
|
||||
|
||||
// calibrate axes
|
||||
bar_chart.y_scale.domain(data.series.reverse().map(function(d) { return d.name; }));
|
||||
bar_chart.svg.select(".d3-chart-domain.d3-chart-axis")
|
||||
.call(bar_chart.y_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+")";
|
||||
});
|
||||
|
||||
bar_chart.x_scale.domain([0, data.max]);
|
||||
bar_chart.svg.select(".d3-chart-range.d3-chart-axis").call(bar_chart.x_axis);
|
||||
|
||||
data.series.forEach(function(series){
|
||||
var bars = bar_chart.svg.selectAll("d3-chart-rect.d3-chart-bar." + series.css_class)
|
||||
.data(series.values);
|
||||
bar_chart.applyData(series, bars.enter().append("rect"));
|
||||
bar_chart.applyData(series, bars.transition());
|
||||
bars.exit().remove();
|
||||
});
|
||||
}
|
||||
|
||||
// helper method for drawData.
|
||||
applyData(series, elements){
|
||||
var bar_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("y", function(d) { return bar_chart.y_scale(series.name); })
|
||||
.attr("height", bar_chart.y_scale.rangeBand())
|
||||
.attr("x", function(d) { return bar_chart.x_scale(d.cummulative); })
|
||||
.attr("width", function(d) { return bar_chart.x_scale(d.value); })
|
||||
.attr("opacity", function(d) { return d.opacity; });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default HorizontalBarChart;
|
||||
76
client/d3/bar/vertical.js
vendored
Normal file
76
client/d3/bar/vertical.js
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
import BarChart from './bar.base';
|
||||
|
||||
class VerticalBarChart extends BarChart {
|
||||
|
||||
defineAxes(){
|
||||
var bar_chart = this;
|
||||
bar_chart.y_scale = d3.scale.ordinal()
|
||||
.rangeRoundBands([bar_chart.height, 0], 0.1);
|
||||
|
||||
bar_chart.y_axis = d3.svg.axis()
|
||||
.scale(bar_chart.y_scale)
|
||||
.ticks(bar_chart.range_ticks)
|
||||
.orient("left")
|
||||
.outerTickSize(0);
|
||||
|
||||
bar_chart.x_scale = d3.scale.linear()
|
||||
.range([0, bar_chart.width]);
|
||||
|
||||
bar_chart.x_axis = d3.svg.axis()
|
||||
.scale(bar_chart.x_scale)
|
||||
.orient("bottom");
|
||||
|
||||
// append axes
|
||||
bar_chart.svg.append("g")
|
||||
.attr("class", "d3-chart-range d3-chart-axis");
|
||||
bar_chart.svg.append("g")
|
||||
.attr("class", "d3-chart-domain d3-chart-axis")
|
||||
.attr("transform", "translate(0, " + (bar_chart.height - bar_chart.margin.top) + ")");
|
||||
}
|
||||
|
||||
drawData(data){
|
||||
var bar_chart = this;
|
||||
data = bar_chart.serializeData(data);
|
||||
|
||||
// calibrate axes
|
||||
bar_chart.x_scale.domain(data.series.reverse().map((d)=>{ return d.name; }));
|
||||
bar_chart.svg.select(".d3-chart-domain.d3-chart-axis")
|
||||
.call(bar_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+")";
|
||||
});
|
||||
|
||||
bar_chart.y_scale.domain([data.min, data.max]);
|
||||
bar_chart.svg.select(".d3-chart-range.d3-chart-axis").call(bar_chart.y_axis);
|
||||
|
||||
data.series.forEach(function(series){
|
||||
var bars = bar_chart.svg.selectAll(".d3-chart-rect.d3-chart-bar." + series.css_class)
|
||||
.data(series.values);
|
||||
bar_chart.applyData(series, bars.enter().append("rect"));
|
||||
bar_chart.applyData(series, bars.transition());
|
||||
bars.exit().remove();
|
||||
});
|
||||
}
|
||||
|
||||
// helper method for drawData
|
||||
applyData(series, elements){
|
||||
var bar_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", function(d) { return bar_chart.x_scale.rangeBand(); })
|
||||
.attr("x", function(d) { return bar_chart.x_scale(series.name); })
|
||||
.attr("height", return bar_chart.y_scale(d.value))
|
||||
.attr("y", function(d) { return bar_chart.y_scale(d.cummulative); })
|
||||
.attr("opacity", function(d) { return d.opacity; });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default VerticalBarChart;
|
||||
75
client/d3/base.js
vendored
Normal file
75
client/d3/base.js
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
import extend from 'extend';
|
||||
|
||||
const DEFAULTS = {
|
||||
outer_width: 500,
|
||||
outer_height: 300,
|
||||
margin: {top: 30, left: 70, bottom: 50, right: 20},
|
||||
domain_ticks: 10,
|
||||
range_ticks: 8,
|
||||
container: "container",
|
||||
time_series: false,
|
||||
range_label: undefined,
|
||||
domain_attr: undefined,
|
||||
range_attr: undefined,
|
||||
toCssClass: function(series){
|
||||
return series ? series.title.toLowerCase().replace(/\s+/g, '-') : "";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Chart {
|
||||
|
||||
constructor(options){
|
||||
var chart = this;
|
||||
chart = extend(chart, chart.chart_options, options);
|
||||
|
||||
chart.height = chart.outer_height - chart.margin.top - chart.margin.bottom;
|
||||
chart.width = chart.outer_width - chart.margin.left - chart.margin.right;
|
||||
|
||||
chart.svg = d3.select(chart.container).append("svg")
|
||||
.attr("width", chart.outer_width)
|
||||
.attr("height", chart.outer_height)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + chart.margin.left + "," + chart.margin.top + ")");
|
||||
chart.defineAxes();
|
||||
if (chart.afterAxes) chart.afterAxes();
|
||||
}
|
||||
|
||||
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]);
|
||||
});
|
||||
});
|
||||
return extent;
|
||||
}
|
||||
|
||||
titleize(s){
|
||||
var words = s.split(' '),
|
||||
array = [];
|
||||
for (var i=0; i<words.length; ++i) {
|
||||
array.push(words[i].charAt(0).toUpperCase() + words[i].toLowerCase().slice(1));
|
||||
}
|
||||
return array.join(' ');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Chart.DEFAULTS = DEFAULTS;
|
||||
|
||||
export default Chart;
|
||||
5
client/d3/chart.scss
Normal file
5
client/d3/chart.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
.d3-chart-axis path {
|
||||
stroke-width: 2;
|
||||
fill: none;
|
||||
stroke:#000000;
|
||||
}
|
||||
153
client/d3/grid/calendar_grid.js
vendored
Normal file
153
client/d3/grid/calendar_grid.js
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
import Chart from './../base';
|
||||
import extend from 'extend';
|
||||
|
||||
// inspired by https://gist.github.com/mbostock/4b66c0d9be9a0d56484e
|
||||
class CalendarGridChart extends Chart{
|
||||
|
||||
get chart_options(){
|
||||
var chart = this;
|
||||
return extend(Object.assign({}, Chart.DEFAULTS), {
|
||||
margin: {top: 30, left: 150, bottom: 0, right: 0},
|
||||
grid_padding: 0.05,
|
||||
parse_date_format: '%Y-%m-%d',
|
||||
display_date_format: '%B %Y',
|
||||
date_attr: 'date',
|
||||
range_attr: undefined,
|
||||
min_range_zero: false,
|
||||
color: '#FFF',
|
||||
extent: []
|
||||
})
|
||||
}
|
||||
|
||||
defineAxes(){
|
||||
var grid_chart = this;
|
||||
|
||||
// y scale is dependent on number of months.
|
||||
grid_chart.y_axis = d3.svg.axis().orient("left").outerTickSize(0);
|
||||
grid_chart.y_scale = d3.scale.ordinal()
|
||||
grid_chart.svg.append("g")
|
||||
.attr("class", "d3-chart-range d3-chart-axis");
|
||||
|
||||
grid_chart.x_scale = d3.scale.ordinal()
|
||||
.domain(d3.range(31).map((n)=>{ return n+1; }))
|
||||
.rangeRoundBands([0, grid_chart.width], grid_chart.grid_padding, 0);
|
||||
|
||||
grid_chart.x_axis = d3.svg.axis()
|
||||
.scale(grid_chart.x_scale)
|
||||
.orient("top")
|
||||
.outerTickSize(0);
|
||||
|
||||
// append x axis
|
||||
grid_chart.svg.append("g").attr("class", "d3-chart-domain d3-chart-axis");
|
||||
}
|
||||
|
||||
afterAxes(){
|
||||
var grid_chart = this;
|
||||
grid_chart.grid_unit_size = grid_chart.width / 31 - grid_chart.grid_padding * grid_chart.width / 30;
|
||||
|
||||
if (grid_chart.display_date_format) grid_chart.displayDate = d3.time.format(grid_chart.display_date_format);
|
||||
|
||||
if (!grid_chart.toDate && grid_chart.parse_date_format){
|
||||
grid_chart.parseDate = d3.time.format(grid_chart.parse_date_format);
|
||||
grid_chart.toDate = (datum)=>{
|
||||
grid_chart.parseDate(datum[grid_chart.date_attr]);
|
||||
}
|
||||
} else if (!grid_chart.toDate){
|
||||
grid_chart.toDate = (datum)=>{ return datum[grid_chart.date_attr] };
|
||||
}
|
||||
|
||||
|
||||
grid_chart.monthFormat = d3.time.format('%B %Y');
|
||||
grid_chart.toMonthString = (datum)=>{
|
||||
return grid_chart.monthFormat(grid_chart.toDate(datum));
|
||||
}
|
||||
}
|
||||
|
||||
serializeData(data){
|
||||
var grid_chart = this;
|
||||
data.css_class = data.css_class || grid_chart.toClass ? grid_chart.toClass(data) : "";
|
||||
|
||||
grid_chart.rangeValue = grid_chart.range_attr ? function(d){ return d[grid_chart.range_attr]; } : grid_chart.rangeValue;
|
||||
|
||||
data.months = [];
|
||||
if (data.min_range !== undefined && data.max_range !== undefined){
|
||||
data.range = {min: data.min_range, max: data.max_range};
|
||||
data.values.forEach((value)=>{
|
||||
var date = grid_chart.toDate(value),
|
||||
date_s = grid_chart.monthFormat(date);
|
||||
if (data.months.indexOf(date_s) < 0) data.months.push(date_s);
|
||||
});
|
||||
} else {
|
||||
var min_range = Infinity,
|
||||
max_range = -Infinity;
|
||||
data.values.forEach((value)=>{
|
||||
var date = grid_chart.toDate(value),
|
||||
date_s = grid_chart.monthFormat(date),
|
||||
range_value =grid_chart.rangeValue(value);
|
||||
min_range = Math.min(min_range, range_value);
|
||||
max_range = Math.max(max_range, range_value);
|
||||
if (data.months.indexOf(date_s) < 0) data.months.push(date_s);
|
||||
});
|
||||
if (grid_chart.min_range_zero) min_range = Math.min(min_range, 0);
|
||||
data.range = { min: min_range, max: max_range };
|
||||
}
|
||||
data.range.diff = data.range.max - data.range.min;
|
||||
|
||||
data.months = data.months.sort((date_s1, date_s2)=>{
|
||||
var date1 = grid_chart.monthFormat.parse(date_s1),
|
||||
date2 = grid_chart.monthFormat.parse(date_s2);
|
||||
return date1.getTime() - date2.getTime();
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
drawData(data){
|
||||
var grid_chart = this;
|
||||
grid_chart.i = grid_chart.i || 1;
|
||||
data = grid_chart.serializeData(data);
|
||||
|
||||
// calibrate axes
|
||||
var y_axis_height = grid_chart.grid_unit_size * (1 + grid_chart.grid_padding) * data.months.length;
|
||||
grid_chart.y_scale.rangeRoundBands([0, y_axis_height], grid_chart.grid_padding, 0);
|
||||
grid_chart.y_scale.domain(data.months);
|
||||
grid_chart.y_axis.scale(grid_chart.y_scale);
|
||||
|
||||
grid_chart.svg.select(".d3-chart-range")
|
||||
.call(grid_chart.y_axis);
|
||||
|
||||
grid_chart.svg.select(".d3-chart-domain").call(grid_chart.x_axis);
|
||||
|
||||
var grid_units = grid_chart.svg.selectAll(".d3-chart-grid-unit")
|
||||
.data(data.values);
|
||||
grid_units.exit().remove();
|
||||
grid_chart.applyData(data, grid_units.enter().append("rect"));
|
||||
grid_chart.applyData(data, grid_units);
|
||||
}
|
||||
|
||||
// helper method for drawData.
|
||||
applyData(data, elements){
|
||||
var grid_chart = this,
|
||||
series_class = "d3-chart-grid-unit " + data.css_class;
|
||||
elements
|
||||
.attr("class", series_class)
|
||||
.attr("y", function(d) {
|
||||
var bottom = grid_chart.y_scale(grid_chart.toMonthString(d)),
|
||||
middle = grid_chart.y_scale.rangeBand() / 2 - grid_chart.grid_unit_size / 2;
|
||||
return bottom + middle;
|
||||
})
|
||||
.attr("height", grid_chart.grid_unit_size)
|
||||
.attr("x", function(d) {
|
||||
return grid_chart.x_scale(grid_chart.toDate(d).getDate());
|
||||
})
|
||||
.attr("width", function(d) { return grid_chart.grid_unit_size; })
|
||||
.attr('fill', grid_chart.color)
|
||||
.attr("opacity", function(d) { return grid_chart.calculateOpacity(75, data.range); });
|
||||
}
|
||||
|
||||
calculateOpacity(value, range){
|
||||
return Math.max(0, Math.min(1, 1 - (range.max - (value - range.min)) / range.diff));
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export default CalendarGridChart;
|
||||
122
client/d3/line/line.js
vendored
Normal file
122
client/d3/line/line.js
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
import extend from 'extend';
|
||||
|
||||
import Chart from './../base';
|
||||
|
||||
|
||||
// inspired by https://gist.github.com/mbostock/4b66c0d9be9a0d56484e
|
||||
class LineChart extends Chart {
|
||||
|
||||
get chart_options(){
|
||||
return {
|
||||
interpolation: 'basis'
|
||||
};
|
||||
}
|
||||
|
||||
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) + ")");
|
||||
}
|
||||
|
||||
afterAxes(){
|
||||
var line_chart = this;
|
||||
// function that draws the lines.
|
||||
line_chart.line = 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(d[line_chart.range_attr]); });
|
||||
|
||||
// function that returns unique color based on series_title.
|
||||
line_chart.color = d3.scale.category20();
|
||||
}
|
||||
|
||||
serializeData(data){
|
||||
var line_chart = this,
|
||||
serialized_data = {
|
||||
series: [],
|
||||
range_min: Infinity,
|
||||
range_max: -Infinity,
|
||||
domain_min: Infinity,
|
||||
domain_max: -Infinity,
|
||||
};
|
||||
|
||||
data.forEach(function(data_set){
|
||||
var series = extend({
|
||||
css_class: line_chart.toClass ? line_chart.toClass(data_set) : "",
|
||||
title: line_chart.titleize ? line_chart.titleize(data_set) : "",
|
||||
color: ''
|
||||
}, data_set);
|
||||
|
||||
series.values.forEach((value)=>{
|
||||
series_data.range_min = Math.min(series_data.range_min, value[line_chart.range_attr]);
|
||||
series_data.range_max = Math.max(series_data.range_max, value[line_chart.range_attr]);
|
||||
series_data.domain_min = Math.min(series_data.domain_min, value[line_chart.domain_attr]);
|
||||
series_data.domain_max = Math.max(series_data.domain_max, value[line_chart.domain_attr]);
|
||||
});
|
||||
serialized_data.series.push(series);
|
||||
});
|
||||
return serialized_data;
|
||||
};
|
||||
|
||||
drawData(data){
|
||||
var line_chart = this;
|
||||
data = line_chart.serialize_data;
|
||||
|
||||
// calibrate axes
|
||||
bar_chart.y_scale.domain([Math.min(0, data.range_min), data.range_max]);
|
||||
bar_chart.svg.select(".d3-chart-range.d3-chart-axis")
|
||||
.call(bar_chart.y_axis);
|
||||
|
||||
bar_chart.x_scale.domain([data.domain_max, Math.min(data.domain_min)]);
|
||||
bar_chart.svg.select(".d3-chart-domain.d3-chart-axis").call(bar_chart.x_axis);
|
||||
|
||||
// draw lines
|
||||
var line = g.selectAll(".d3-chart-series")
|
||||
.data(data.series);
|
||||
|
||||
[line.enter().append('g'), line.transition()].forEach((groups)=>{
|
||||
line_chart.applyData(groups);
|
||||
});
|
||||
line.exit().remove();
|
||||
}
|
||||
|
||||
applyData(groups){
|
||||
var line_chart = this;
|
||||
groups
|
||||
.attr('class', function(series){ return "d3-chart-line " + series.css_class; })
|
||||
.attr("title", function(series){ return series.title; })
|
||||
.append("path")
|
||||
.attr("d", function(series){ return line_chart.line(series.values); })
|
||||
.style("stroke", function(series){ return line_chart.color(series.title); });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default LineChart;
|
||||
13
client/d3/line/spline.js
vendored
Normal file
13
client/d3/line/spline.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import LineChart from './line';
|
||||
|
||||
const INTEPOLATION = 'cardinal';
|
||||
|
||||
class SplineChart extends LineChart {
|
||||
|
||||
get chart_options(){
|
||||
return {
|
||||
interpolation: INTEPOLATION
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
108
client/d3/line/spline_stack.js
vendored
Normal file
108
client/d3/line/spline_stack.js
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
import LineChart from './line';
|
||||
|
||||
const INTERPOLATION = 'cardinal';
|
||||
|
||||
// inspired by https://bl.ocks.org/mbostock/3885211
|
||||
class SplineStackChart extends LineChart {
|
||||
|
||||
get chart_options(){
|
||||
return Object.assign(Object.assign({}, LineChart.DEFAULTS), {
|
||||
interpolation: INTERPOLATION
|
||||
});
|
||||
}
|
||||
|
||||
afterAxes(){
|
||||
var spline_stack = this;
|
||||
spline_stack.fnArea = d3.svg.area()
|
||||
.x(function(d, i) { return spline_stack.x_scale(d.x); })
|
||||
.y0(function(d) { return spline_stack.y_scale(d.y0); })
|
||||
.y1(function(d) { return spline_stack.y_scale(d.y0 + d.y); })
|
||||
.interpolate(spline_stack.interpolation);
|
||||
|
||||
spline_stack.fnStack = d3.layout.stack()
|
||||
.values(function(d) { return d.values; });
|
||||
|
||||
// function that returns unique color based on series_title.
|
||||
spline_stack.fnColor = d3.scale.category20();
|
||||
}
|
||||
|
||||
serializeData(data){
|
||||
var spline_stack = this,
|
||||
serialized_data = {
|
||||
series: [] };
|
||||
data.series.forEach(function(series, i){
|
||||
series.css_class = series.css_class || spline_stack.toClass ? spline_stack.toClass(series) : "series-" + i;
|
||||
if (spline_stack.domain_attr !== 'x' && spline_stack.range_attr !== 'y'){
|
||||
series.values = series.values.map((value)=>{
|
||||
return {x: value[spline_stack.domain_attr], y: value[spline_stack.range_attr], series: series};
|
||||
});
|
||||
}
|
||||
serialized_data.series.push(series);
|
||||
});
|
||||
|
||||
serialized_data.series = spline_stack.fnStack(serialized_data.series);
|
||||
// assume all series have same domain, use first series to establish extent.
|
||||
serialized_data.domain_extent = d3.extent(serialized_data.series[0].values.map((value)=>{ return value.x; }));
|
||||
// final series will have the highest y values.
|
||||
serialized_data.range_max = d3.max(serialized_data.series[serialized_data.series.length - 1].values.map((value)=>{ return value.y0 + value.y; }))
|
||||
|
||||
return serialized_data;
|
||||
};
|
||||
|
||||
drawData(data){
|
||||
var spline_stack = this;
|
||||
data = spline_stack.serializeData(data);
|
||||
|
||||
// calibrate axes.
|
||||
spline_stack.y_scale.domain([0, data.range_max]);
|
||||
spline_stack.svg.select(".d3-chart-range.d3-chart-axis")
|
||||
.call(spline_stack.y_axis);
|
||||
|
||||
spline_stack.x_scale.domain(data.domain_extent);
|
||||
spline_stack.svg.select(".d3-chart-domain.d3-chart-axis").call(spline_stack.x_axis);
|
||||
|
||||
var stack = spline_stack.svg.selectAll(".d3-chart-spline-stack")
|
||||
.data(data.series);
|
||||
[stack.enter().append("path"), stack].forEach((paths)=>{
|
||||
spline_stack.applyData(paths);
|
||||
});
|
||||
stack.exit().remove();
|
||||
|
||||
if (spline_stack.include_dots){
|
||||
data.series.forEach((series)=>{
|
||||
var dots = spline_stack.svg.selectAll(".d3-chart-spline-dot." + series.css_class)
|
||||
.data(series.values);
|
||||
[dots.enter().append("circle"), dots].forEach((circles)=>{
|
||||
spline_stack.applyDots(series, circles);
|
||||
});
|
||||
dots.exit().remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
applyData(paths){
|
||||
var spline_stack = this;
|
||||
paths
|
||||
.attr("class", function(series){ return "d3-chart-spline-stack " + series.css_class;})
|
||||
.attr("d", function(series){ return spline_stack.fnArea(series.values); })
|
||||
.style("fill", function(series){ return spline_stack.fnColor(series.title); })
|
||||
.attr('opacity', 1);
|
||||
}
|
||||
|
||||
applyDots(series, circles){
|
||||
var spline_stack = this;
|
||||
circles
|
||||
.attr('class', 'd3-chart-spline-dot ' + series.css_class)
|
||||
.attr("r", 2)
|
||||
.attr("cx", function(d, i){ return spline_stack.x_scale(d.x); })
|
||||
.attr("cy", function(d, i){ return spline_stack.y_scale(d.y + d.y0); })
|
||||
.attr("title", function(d, i){ return spline_stack.titleizeDatum(series, d); })
|
||||
.style("fill", spline_stack.fnColor(series.title))
|
||||
.attr('opacity', 1)
|
||||
.attr('stroke-width', 0)
|
||||
.attr('stroke', '#fff');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default SplineStackChart;
|
||||
133
client/d3/sliders/date_range.js
vendored
Normal file
133
client/d3/sliders/date_range.js
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
import Chart from './../base';
|
||||
|
||||
class DateRange extends Chart {
|
||||
|
||||
|
||||
get chart_options(){
|
||||
return Object.assign(Object.assign({}, Chart.DEFAULTS), {
|
||||
outer_width: 600,
|
||||
outer_height: 250,
|
||||
margin: {top: 20, left: 10, bottom: 20, right: 10},
|
||||
});
|
||||
}
|
||||
|
||||
defineAxes(){
|
||||
var date_range = this;
|
||||
|
||||
date_range.x_scale = d3.time.scale()
|
||||
.range([0, date_range.width])
|
||||
.clamp(true);
|
||||
|
||||
date_range.x_axis = d3.svg.axis()
|
||||
.scale(date_range.x_scale)
|
||||
.orient("bottom")
|
||||
.ticks(d3.time.weeks, 1)
|
||||
//.tickFormat(function(d) { return d + "°"; })
|
||||
.tickSize(1)
|
||||
.outerTickSize(1)
|
||||
.tickPadding(12)
|
||||
|
||||
date_range.svg.append("g")
|
||||
.attr("class", "d3-chart-domain")
|
||||
.attr("transform", "translate(0," + date_range.height / 2 + ")");
|
||||
}
|
||||
|
||||
afterAxes(){
|
||||
var date_range = this;
|
||||
|
||||
date_range.slider = date_range.svg.append("g")
|
||||
.attr("class", "d3-chart-slider");
|
||||
|
||||
date_range.min_handle = date_range.slider.append("circle")
|
||||
.attr("class", "d3-chart-min-handle")
|
||||
.attr("transform", "translate(0," + date_range.height / 2 + ")")
|
||||
.attr("r", 9);
|
||||
|
||||
date_range.max_handle = date_range.slider.append("circle")
|
||||
.attr("class", "d3-chart-max-handle")
|
||||
.attr("transform", "translate(0," + date_range.height / 2 + ")")
|
||||
.attr("r", 9);
|
||||
|
||||
date_range.brush = d3.svg.brush()
|
||||
.x(date_range.x_scale);
|
||||
|
||||
date_range.slider
|
||||
.call(date_range.brush)
|
||||
//.select(".background")
|
||||
// .attr("height", date_range.height);
|
||||
|
||||
date_range.slider.call(date_range.brush)
|
||||
.selectAll(".extent,.resize")
|
||||
.remove();
|
||||
}
|
||||
|
||||
drawData(data){
|
||||
var date_range = this;
|
||||
date_range.x_scale.domain([data.abs_min, data.abs_max]);
|
||||
|
||||
date_range.svg.select(".d3-chart-domain")
|
||||
.call(date_range.x_axis);
|
||||
|
||||
date_range.min_handle.attr('cx', date_range.x_scale(data.current_min));
|
||||
date_range.max_handle.attr('cx', date_range.x_scale(data.current_max));
|
||||
|
||||
date_range.brush.extent([data.current_min, data.current_min])
|
||||
.on("brush", ()=>{
|
||||
DateRange.handleBrush(date_range, eval('this'));
|
||||
});
|
||||
|
||||
date_range.slider
|
||||
.call(date_range.brush.event)
|
||||
.transition() // gratuitous intro!
|
||||
.duration(750)
|
||||
.call(date_range.brush.extent([data.current_min, data.current_min]))
|
||||
.call(date_range.brush.event);
|
||||
|
||||
}
|
||||
|
||||
static handleBrush(date_range, elem){
|
||||
var date = date_range.brush.extent()[0],
|
||||
current_min = parseInt(date_range.min_handle.attr('cx')),
|
||||
current_max = parseInt(date_range.max_handle.attr('cx'));
|
||||
|
||||
if (!current_min && !current_max) return false
|
||||
if (d3.event.sourceEvent) { // not a programmatic event
|
||||
date = date_range.x_scale.invert(d3.mouse(elem)[0]);
|
||||
date_range.brush.extent([date, date]);
|
||||
}
|
||||
|
||||
var value = date_range.x_scale(date);
|
||||
|
||||
if (value < current_max && value > current_min){
|
||||
if (Math.abs(value - current_min) < Math.abs(value - current_max)){
|
||||
date_range.min_handle.attr('cx', value);
|
||||
current_min = value;
|
||||
} else {
|
||||
date_range.max_handle.attr('cx', value);
|
||||
current_max = value;
|
||||
}
|
||||
} else if (value >= current_max){
|
||||
date_range.max_handle.attr('cx', value);
|
||||
current_max = value;
|
||||
if (d3.event.sourceEvent && date_range.maxDelta){
|
||||
var new_current_min = date_range.maxDelta(date, date_range.x_scale.invert(current_min));
|
||||
if (new_current_min) date_range.min_handle.attr('cx', date_range.x_scale(new_current_min));
|
||||
}
|
||||
} else {
|
||||
date_range.min_handle.attr('cx', value);
|
||||
current_min = value;
|
||||
if (d3.event.sourceEvent && date_range.maxDelta){
|
||||
var new_current_max = date_range.maxDelta(date, date_range.x_scale.invert(current_max));
|
||||
if (new_current_max) date_range.max_handle.attr('cx', date_range.x_scale(new_current_max));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (d3.event.sourceEvent && date_range.onRangeUpdated) {
|
||||
date_range.onRangeUpdated(date_range.x_scale.invert(current_min), date_range.x_scale.invert(current_max));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DateRange;
|
||||
Reference in New Issue
Block a user