UNPKG

oecd-simple-charts

Version:

D3 charting library for creating pearl charts, stacked bar charts and box plots

216 lines (189 loc) 6.52 kB
import { select as d3Select } from 'd3-selection'; import { scaleLinear as d3ScaleLinear } from 'd3-scale'; import { sum as d3Sum } from 'd3-array'; import OECDChart from './OECDChart'; /** * A stacked chart component * * @example * var StackedChartExample = new OECDCharts.StackedChart({ * container: '#StackedChartExample', * title: 'Stacked Bar Chart', * renderInfoButton: true, * data: [ * { * values: [1,2,3,4,5], * barLabels: ['0%', '100%'], * colors: ['#fddd5d', '#900c3f'], * stackLabels: ['I', 'II', 'III', 'IV', 'V'] * }, * { * values: [2,4,6,8,20], * barLabels: ['0%', '100%'], * colors: ['#fddd5d', '#189aa8'] * } * ] * }); * @constructor * @param {object} options - The options object for the stacked chart * @param {string} options.container - The DOM element to use as container * @param {string} options.title - The title to display * @param {bool} [options.renderInfoButton = false] - The info-Icon for the tooltip, renders after the title * @param {int} [options.fontSize = 14] - The font-size for the labels in px * @param {int} [options.marginTop = 15] - The space between the bars in px * @param {int} [options.barHeight = 30] -The height of a bar in px * @param {array} options.data - The data as array * @param {array} options.data.values - The values to display as stacked bar chart * @param {array} options.data.barLabels - The labels to display left and right to the chart * @param {array} options.data.colors - Colors for the min and max value of the stacked bar chart * @param {array} options.data.stackLabels - Labels for the stacked elements */ class StackedChart extends OECDChart { constructor(options = {}) { super(); this.defaultOptions = { container: null, extent: [0, 100], data: [], fontSize: 14, marginTop: 15, barHeight: 30, marginLeft: 30, marginRight: 45, labelOffset: 5 }; this.init(options); } render() { const { data, container, extent, marginTop, barHeight, fontSize, marginLeft, marginRight } = this.options; const d3Container = d3Select(container); const dimensions = d3Container.node().getBoundingClientRect(); const outerWidth = dimensions.width; const innerWidth = outerWidth - marginLeft - marginRight; const innerHeight = ((barHeight + marginTop) * data.length); this.removeSelections(['.stacked-chart__svg']); this.x = d3ScaleLinear().range([0, innerWidth]).domain(extent); this.colorScale = d3ScaleLinear(); const svg = d3Container .classed('OECDCharts__StackedChart', true) .append('svg') .classed('stacked-chart__svg', true) .attr('width', outerWidth) .attr('height', innerHeight); this.chartWrapper = svg.append('g') .attr('class', 'stacked-chart') .attr('transform', `translate(${marginLeft}, 0)`); this.update(data); } /** * @memberof StackedChart * @param {array} data - an array containing objects with the new data * @example * StackedChartExample.update([ * { * values: [1,10,3,4,5], * barLabels: ['0%', '100%'], * colors: ['#fddd5d', '#900c3f'], * stackLabels: ['1', '2', '3', '4', '5'] * }, * { * values: [2,4,10,15,20], * barLabels: ['0%', '100%'], * colors: ['#fddd5d', '#189aa8'] * } * ]); */ update(_data) { this.options.data = _data; const data = StackedChart.parseData(_data); StackedChart.validateData(_data); const chart = this.getChart(data); this.getBars(chart); this.getStackedLabels(chart); this.getBarLabels(chart); } getChart(_data) { const chart = this.chartWrapper.selectAll('.stacked-chart__bar') .data(_data, d => JSON.stringify(d)); chart.exit().remove(); return chart.enter() .append('g') .classed('stacked-chart__bar', true) .attr('transform', (d, i) => `translate(0, ${i * (this.options.barHeight + this.options.marginTop)})`); } getBars(chart) { chart.selectAll('.stacked-chart__rect') .data(data => data.values.map((value, i) => ({ value, offset: data.offset[i], colors: data.colors }))) .enter() .append('rect') .classed('stacked-chart__rect', true) .attr('width', d => this.x(d.value)) .attr('x', d => this.x(d.offset)) .attr('height', this.options.barHeight) .attr('fill', (d, i, nodes) => this.colorScale.domain([0, nodes.length]).range(d.colors)(i)); } getStackedLabels(chart) { chart.selectAll('.stacked-chart__stackedlabel') .data(data => data.stackLabels.map((stackLabel, i) => ({ stackLabel, offset: data.offset[i], value: data.values[i] }))) .enter() .append('text') .classed('stacked-chart__stackedlabel', true) .attr('y', this.options.barHeight / 2) .attr('x', d => this.x(d.offset + (d.value / 2))) .attr('text-anchor', 'middle') .attr('dy', '.35em') .attr('fill', '#fff') .text(d => d.stackLabel) .attr('font-size', this.options.fontSize); } getBarLabels(chart) { const { labelOffset, barHeight, fontSize } = this.options; chart.selectAll('.stacked-chart__barlabel') .data(d => d.barLabels) .enter() .append('text') .classed('stacked-chart__barlabel', true) .attr('y', barHeight / 2) .attr('x', (d, i, nodes) => (i === 0 ? 0 : nodes[i].parentNode.getBoundingClientRect().width)) .attr('dy', '.35em') .attr('dx', (d, i) => (i === 0 ? `-${labelOffset}px` : `${labelOffset}px`)) .attr('text-anchor', (d, i) => (i === 0 ? 'end' : 'start')) .text(d => d) .attr('font-size', fontSize); } static parseData(_data) { return _data.map((d) => { const factor = 100 / d.values.reduce((a, b) => a + b); d.values = d.values.map(value => value * factor); d.offset = d.values.map((value, i) => d3Sum(d.values.slice(0, i))); d.stackLabels = d.stackLabels || []; return d; }); } static validateData(_data) { const invalidData = _data.filter( d => (d.values.length !== d.stackLabels.length && d.stackLabels.length) ); if (invalidData.length) { throw Error('invalid data: amount of stackLabels is not matching amount of values.'); } } } export default StackedChart;