UNPKG

oecd-simple-charts

Version:

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

1,406 lines (1,228 loc) 46.9 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-selection'), require('d3-dispatch'), require('d3-axis'), require('d3-format'), require('d3-scale'), require('d3-transition'), require('d3-array')) : typeof define === 'function' && define.amd ? define(['exports', 'd3-selection', 'd3-dispatch', 'd3-axis', 'd3-format', 'd3-scale', 'd3-transition', 'd3-array'], factory) : (factory((global.OECDCharts = {}),global.d3Selection,global.d3Dispatch,global.d3Axis,global.d3Format,global.d3Scale,global.d3Transition,global.d3Array)); }(this, (function (exports,d3Selection,d3Dispatch,d3Axis,d3Format,d3Scale,d3Transition,d3Array) { 'use strict'; /** * Checks if `value` is the * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @since 0.1.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is an object, else `false`. * @example * * _.isObject({}); * // => true * * _.isObject([1, 2, 3]); * // => true * * _.isObject(_.noop); * // => true * * _.isObject(null); * // => false */ function isObject(value) { var type = typeof value; return value != null && (type == 'object' || type == 'function'); } /** Detect free variable `global` from Node.js. */ var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; /** Detect free variable `self`. */ var freeSelf = typeof self == 'object' && self && self.Object === Object && self; /** Used as a reference to the global object. */ var root = freeGlobal || freeSelf || Function('return this')(); /** * Gets the timestamp of the number of milliseconds that have elapsed since * the Unix epoch (1 January 1970 00:00:00 UTC). * * @static * @memberOf _ * @since 2.4.0 * @category Date * @returns {number} Returns the timestamp. * @example * * _.defer(function(stamp) { * console.log(_.now() - stamp); * }, _.now()); * // => Logs the number of milliseconds it took for the deferred invocation. */ var now = function() { return root.Date.now(); }; /** Built-in value references. */ var Symbol$1 = root.Symbol; /** Used for built-in method references. */ var objectProto = Object.prototype; /** Used to check objects for own properties. */ var hasOwnProperty = objectProto.hasOwnProperty; /** * Used to resolve the * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) * of values. */ var nativeObjectToString = objectProto.toString; /** Built-in value references. */ var symToStringTag = Symbol$1 ? Symbol$1.toStringTag : undefined; /** * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. * * @private * @param {*} value The value to query. * @returns {string} Returns the raw `toStringTag`. */ function getRawTag(value) { var isOwn = hasOwnProperty.call(value, symToStringTag), tag = value[symToStringTag]; try { value[symToStringTag] = undefined; var unmasked = true; } catch (e) {} var result = nativeObjectToString.call(value); if (unmasked) { if (isOwn) { value[symToStringTag] = tag; } else { delete value[symToStringTag]; } } return result; } /** Used for built-in method references. */ var objectProto$1 = Object.prototype; /** * Used to resolve the * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) * of values. */ var nativeObjectToString$1 = objectProto$1.toString; /** * Converts `value` to a string using `Object.prototype.toString`. * * @private * @param {*} value The value to convert. * @returns {string} Returns the converted string. */ function objectToString(value) { return nativeObjectToString$1.call(value); } /** `Object#toString` result references. */ var nullTag = '[object Null]', undefinedTag = '[object Undefined]'; /** Built-in value references. */ var symToStringTag$1 = Symbol$1 ? Symbol$1.toStringTag : undefined; /** * The base implementation of `getTag` without fallbacks for buggy environments. * * @private * @param {*} value The value to query. * @returns {string} Returns the `toStringTag`. */ function baseGetTag(value) { if (value == null) { return value === undefined ? undefinedTag : nullTag; } return (symToStringTag$1 && symToStringTag$1 in Object(value)) ? getRawTag(value) : objectToString(value); } /** * Checks if `value` is object-like. A value is object-like if it's not `null` * and has a `typeof` result of "object". * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is object-like, else `false`. * @example * * _.isObjectLike({}); * // => true * * _.isObjectLike([1, 2, 3]); * // => true * * _.isObjectLike(_.noop); * // => false * * _.isObjectLike(null); * // => false */ function isObjectLike(value) { return value != null && typeof value == 'object'; } /** `Object#toString` result references. */ var symbolTag = '[object Symbol]'; /** * Checks if `value` is classified as a `Symbol` primitive or object. * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. * @example * * _.isSymbol(Symbol.iterator); * // => true * * _.isSymbol('abc'); * // => false */ function isSymbol(value) { return typeof value == 'symbol' || (isObjectLike(value) && baseGetTag(value) == symbolTag); } /** Used as references for various `Number` constants. */ var NAN = 0 / 0; /** Used to match leading and trailing whitespace. */ var reTrim = /^\s+|\s+$/g; /** Used to detect bad signed hexadecimal string values. */ var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; /** Used to detect binary string values. */ var reIsBinary = /^0b[01]+$/i; /** Used to detect octal string values. */ var reIsOctal = /^0o[0-7]+$/i; /** Built-in method references without a dependency on `root`. */ var freeParseInt = parseInt; /** * Converts `value` to a number. * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to process. * @returns {number} Returns the number. * @example * * _.toNumber(3.2); * // => 3.2 * * _.toNumber(Number.MIN_VALUE); * // => 5e-324 * * _.toNumber(Infinity); * // => Infinity * * _.toNumber('3.2'); * // => 3.2 */ function toNumber(value) { if (typeof value == 'number') { return value; } if (isSymbol(value)) { return NAN; } if (isObject(value)) { var other = typeof value.valueOf == 'function' ? value.valueOf() : value; value = isObject(other) ? (other + '') : other; } if (typeof value != 'string') { return value === 0 ? value : +value; } value = value.replace(reTrim, ''); var isBinary = reIsBinary.test(value); return (isBinary || reIsOctal.test(value)) ? freeParseInt(value.slice(2), isBinary ? 2 : 8) : (reIsBadHex.test(value) ? NAN : +value); } /** Error message constants. */ var FUNC_ERROR_TEXT = 'Expected a function'; /* Built-in method references for those with the same name as other `lodash` methods. */ var nativeMax = Math.max, nativeMin = Math.min; /** * Creates a debounced function that delays invoking `func` until after `wait` * milliseconds have elapsed since the last time the debounced function was * invoked. The debounced function comes with a `cancel` method to cancel * delayed `func` invocations and a `flush` method to immediately invoke them. * Provide `options` to indicate whether `func` should be invoked on the * leading and/or trailing edge of the `wait` timeout. The `func` is invoked * with the last arguments provided to the debounced function. Subsequent * calls to the debounced function return the result of the last `func` * invocation. * * **Note:** If `leading` and `trailing` options are `true`, `func` is * invoked on the trailing edge of the timeout only if the debounced function * is invoked more than once during the `wait` timeout. * * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred * until to the next tick, similar to `setTimeout` with a timeout of `0`. * * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) * for details over the differences between `_.debounce` and `_.throttle`. * * @static * @memberOf _ * @since 0.1.0 * @category Function * @param {Function} func The function to debounce. * @param {number} [wait=0] The number of milliseconds to delay. * @param {Object} [options={}] The options object. * @param {boolean} [options.leading=false] * Specify invoking on the leading edge of the timeout. * @param {number} [options.maxWait] * The maximum time `func` is allowed to be delayed before it's invoked. * @param {boolean} [options.trailing=true] * Specify invoking on the trailing edge of the timeout. * @returns {Function} Returns the new debounced function. * @example * * // Avoid costly calculations while the window size is in flux. * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); * * // Invoke `sendMail` when clicked, debouncing subsequent calls. * jQuery(element).on('click', _.debounce(sendMail, 300, { * 'leading': true, * 'trailing': false * })); * * // Ensure `batchLog` is invoked once after 1 second of debounced calls. * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); * var source = new EventSource('/stream'); * jQuery(source).on('message', debounced); * * // Cancel the trailing debounced invocation. * jQuery(window).on('popstate', debounced.cancel); */ function debounce(func, wait, options) { var lastArgs, lastThis, maxWait, result, timerId, lastCallTime, lastInvokeTime = 0, leading = false, maxing = false, trailing = true; if (typeof func != 'function') { throw new TypeError(FUNC_ERROR_TEXT); } wait = toNumber(wait) || 0; if (isObject(options)) { leading = !!options.leading; maxing = 'maxWait' in options; maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; trailing = 'trailing' in options ? !!options.trailing : trailing; } function invokeFunc(time) { var args = lastArgs, thisArg = lastThis; lastArgs = lastThis = undefined; lastInvokeTime = time; result = func.apply(thisArg, args); return result; } function leadingEdge(time) { // Reset any `maxWait` timer. lastInvokeTime = time; // Start the timer for the trailing edge. timerId = setTimeout(timerExpired, wait); // Invoke the leading edge. return leading ? invokeFunc(time) : result; } function remainingWait(time) { var timeSinceLastCall = time - lastCallTime, timeSinceLastInvoke = time - lastInvokeTime, timeWaiting = wait - timeSinceLastCall; return maxing ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting; } function shouldInvoke(time) { var timeSinceLastCall = time - lastCallTime, timeSinceLastInvoke = time - lastInvokeTime; // Either this is the first call, activity has stopped and we're at the // trailing edge, the system time has gone backwards and we're treating // it as the trailing edge, or we've hit the `maxWait` limit. return (lastCallTime === undefined || (timeSinceLastCall >= wait) || (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); } function timerExpired() { var time = now(); if (shouldInvoke(time)) { return trailingEdge(time); } // Restart the timer. timerId = setTimeout(timerExpired, remainingWait(time)); } function trailingEdge(time) { timerId = undefined; // Only invoke if we have `lastArgs` which means `func` has been // debounced at least once. if (trailing && lastArgs) { return invokeFunc(time); } lastArgs = lastThis = undefined; return result; } function cancel() { if (timerId !== undefined) { clearTimeout(timerId); } lastInvokeTime = 0; lastArgs = lastCallTime = lastThis = timerId = undefined; } function flush() { return timerId === undefined ? result : trailingEdge(now()); } function debounced() { var time = now(), isInvoking = shouldInvoke(time); lastArgs = arguments; lastThis = this; lastCallTime = time; if (isInvoking) { if (timerId === undefined) { return leadingEdge(lastCallTime); } if (maxing) { // Handle invocations in a tight loop. timerId = setTimeout(timerExpired, wait); return invokeFunc(lastCallTime); } } if (timerId === undefined) { timerId = setTimeout(timerExpired, wait); } return result; } debounced.cancel = cancel; debounced.flush = flush; return debounced; } var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; var createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }; var possibleConstructorReturn = function (self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }; var OECDChart = function () { function OECDChart() { var _this = this; classCallCheck(this, OECDChart); this.baseDefaultOptions = {}; this.dispatch = d3Dispatch.dispatch('infoclick'); this.on = function (eventName, cb) { return _this.dispatch.on(eventName, cb); }; this.handleInfoClick = this.handleInfoClick.bind(this); this.onResize = this.onResize.bind(this); var debouncedResize = debounce(this.onResize, 250); window.addEventListener('resize', debouncedResize); } createClass(OECDChart, [{ key: 'init', value: function init() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; this.options = _extends({}, this.baseDefaultOptions, this.defaultOptions, options); var d3Container = d3Selection.select(this.options.container); d3Container.classed('oecd-chart__container', true); if (this.options.title || this.options.renderInfoButton) { var titleContainer = d3Container.append('div').attr('class', 'oecd-chart__title-container'); if (this.options.title) { titleContainer.append('div').attr('class', 'oecd-chart__title').text(this.options.title); } if (this.options.renderInfoButton) { titleContainer.append('div').attr('class', 'oecd-chart__info').html('<svg class="oecd-chart__info-icon" width="25" height="25" viewBox="0 0 40 40"><path d="M18.4 15v-3.4h3.3V15h-3.3zM20 33.4q5.5 0 9.4-4t4-9.3-4-9.3-9.4-4-9.4 4-4 9.4 4 9.5 9.4 4zm0-30q7 0 11.8 5t5 11.7-5 12T20 36.7t-11.8-5-5-11.7 5-11.7 11.8-5zm-1.6 25v-10h3.3v10h-3.3z"/></svg>').on('click', this.handleInfoClick); } } this.render(options); } }, { key: 'handleInfoClick', value: function handleInfoClick() { this.dispatch.call('infoclick'); } }, { key: 'onResize', value: function onResize() { var containerWidth = d3Selection.select(this.options.container).node().clientWidth; if (containerWidth !== window.innerWidth) this.render(this.options); } // we call this in render of a specific chart to clear the svg // for drawing with new dimensions }, { key: 'removeSelections', value: function removeSelections(selectors) { var _this2 = this; selectors.forEach(function (sel) { return d3Selection.select(_this2.options.container).selectAll(sel).remove(); }); } }, { key: 'toggleTitle', value: function toggleTitle() { var titleEl = document.querySelector(this.options.container + ' .oecd-chart__title-container'); titleEl.classList.toggle('oecd-chart__title-container--hidden'); } }]); return OECDChart; }(); /** * A pearl chart component. * * @example * var callbackFunc = function(data) { * console.log(data); * } * * var PearlChartExample = new OECDCharts.PearlChart({ * container: '#PearlChartExample', * extent: [300, 600], * title: 'Pearl Chart', * renderInfoButton: true, * showTicks: true, * showLabels: false, * colorLabels: true, * callback: callbackFunc, * data: [ * { * value: 410, * color: '#900c3f' * }, * { * value: 520, * color: '#189aa8' * } * ], * labelFormat: function(val) { * return Math.round(val) + '$'; * } * }); * @constructor * @param {object} options - The options object for the pearl 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.radius = 10] - The radius for the pearl in px * @param {int} [options.ticks = 4] - The number of ticks displayed under the pearl chart, will only be used if tickValues is not set * @param {array} options.tickValues - An array of numbers that are displayed as ticks * @param {bool} [options.showTicks = true] - Hide or show ticks * @param {function} options.callback - A function that is called on circle click * @param {function} [options.labelFormat = val => Math.round(val * 10) / 10] - A function for formatting circle labels * @param {function} [options.showLabels = false] - Hide or show circle labels * @param {function} [options.colorLabels = false] - Fill labels in circle color or black * @param {array} options.data - The data as array. i.e.: * ``` * [ * { * value: 410, * color: '#900c3f' * }, * { * value: 520, * color: '#189aa8' * } * ] * ``` */ var PearlChart = function (_OECDChart) { inherits(PearlChart, _OECDChart); function PearlChart() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; classCallCheck(this, PearlChart); var _this = possibleConstructorReturn(this, (PearlChart.__proto__ || Object.getPrototypeOf(PearlChart)).call(this)); _this.defaultOptions = { container: null, extent: [0, 100], data: [], height: 55, marginLeft: 35, marginRight: 35, fontSize: 14, radius: 10, labelOffset: 5, ticks: 4, tickValues: null, showLabels: false, colorLabels: false, callback: null, showTicks: true, labelFormat: function labelFormat(val) { return Math.round(val * 10) / 10; } }; _this.init(options); return _this; } createClass(PearlChart, [{ key: 'render', value: function render() { var _options = this.options, data = _options.data, height = _options.height, container = _options.container, extent = _options.extent, marginLeft = _options.marginLeft, marginRight = _options.marginRight, labelOffset = _options.labelOffset; var d3Container = d3Selection.select(container); var outerWidth = d3Container.node().clientWidth; var innerWidth = outerWidth - marginLeft - marginRight; var innerHeight = height + labelOffset; this.removeSelections(['.pearlchart__svg']); this.scale = d3Scale.scaleLinear().domain(extent).range([0, innerWidth]); var svg = d3Container.classed('OECDCharts__PearlChart', true).append('svg').classed('pearlchart__svg', true).attr('width', outerWidth).attr('height', innerHeight); this.chartWrapper = svg.append('g').classed('pearlchart__chart', true).attr('transform', 'translate(' + (marginLeft - labelOffset) + ', ' + (this.options.title || this.options.renderInfoButton ? -10 : 10) + ')'); this.getAxis({ chartWrapper: this.chartWrapper, extent: extent, innerWidth: innerWidth, innerHeight: innerHeight, labelOffset: labelOffset }); this.nodesWrapper = this.chartWrapper.append('g').classed('pearlchart__nodes', true); this.update(data); } /** * @memberof PearlChart * @param {array} data - an array containing objects with the new data * @example * PearlChartExample.update([ * { * value: 490, * color: '#900c3f' * }, * { * value: 820, * color: '#189aa8' * } * ]); */ }, { key: 'update', value: function update(_data) { this.options.data = _data; var data = PearlChart.parseData(_data); var transitionFunc = d3Transition.transition().duration(750); var _options2 = this.options, labelOffset = _options2.labelOffset, radius = _options2.radius, height = _options2.height; var innerHeight = height + labelOffset; this.getCircles(data, innerHeight, radius, transitionFunc, labelOffset); } }, { key: 'getAxis', value: function getAxis() { var _this2 = this; var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.defaultOptions; var chartWrapper = options.chartWrapper, extent = options.extent, innerWidth = options.innerWidth, innerHeight = options.innerHeight, labelOffset = options.labelOffset; // render background line var axis = chartWrapper.append('g').classed('pearlchart__x-axis', true); axis.append('line').attr('x1', 0).attr('x2', innerWidth).attr('y1', innerHeight / 2).attr('y2', innerHeight / 2); // render axis labels var axisLabel = axis.selectAll('.pearlchart__axis-label').data(extent); axisLabel.exit().remove(); axisLabel.enter().append('text').classed('pearlchart__axis-label', true).text(function (d) { return d; }).attr('font-size', this.options.fontSize).attr('x', function (d, i) { return _this2.scale.range()[i]; }).attr('dx', function (d, i) { return i === 0 ? '-' + labelOffset + 'px' : labelOffset + 'px'; }).attr('y', innerHeight / 2).attr('dy', labelOffset).attr('text-anchor', function (d, i) { return i === 0 ? 'end' : 'start'; }); var xAxis = d3Axis.axisBottom(this.scale).tickFormat(d3Format.format('.0f')).tickSize(15); if (this.options.tickValues) { xAxis.tickValues(this.options.tickValues); } else { xAxis.ticks(this.options.ticks); } axis.append('g').classed('pearlchart__axis-ticks', true).style('display', this.options.showTicks ? 'block' : 'none').attr('transform', 'translate(0, ' + (this.options.radius * 2 + 10) + ')').call(xAxis); // remove first and last axis label to avoid duplicates with the extent labels if (+axis.select('.tick:last-of-type').text() === this.options.extent[1]) { axis.select('.tick:last-of-type').style('display', 'none'); } if (+axis.select('.tick').text() === this.options.extent[0]) { axis.select('.tick').style('display', 'none'); } axis.selectAll('pearlchart__axis-ticks').style('font-size', this.options.fontSize * 0.8); } }, { key: 'getCircles', value: function getCircles(_data, innerHeight, radius, transition, labelOffset) { var _this3 = this; var circles = this.nodesWrapper.selectAll('.pearlchart__circle-wrapper').data(_data, function (d) { return d.value; }); circles.exit().remove(); circles.transition(transition).style('fill', function (d) { return d.color; }); var circle = circles.enter().append('g').classed('pearlchart__circle-wrapper', true).classed('clickable', this.options.callback !== null).on('mouseenter', function (d, i, nodes) { d3Selection.select(nodes[i]).select('.pearlchart__circle-tooltip').style('display', 'block'); }).on('mouseleave', function (d, i, nodes) { d3Selection.select(nodes[i]).select('.pearlchart__circle-tooltip').style('display', 'none'); }).on('click', function () { if (_this3.options.callback) { _this3.options.callback(_this3.options.data); } }); circle.append('circle').classed('pearlchart__circle', true).attr('cx', function (d) { return _this3.scale(d.value); }).attr('cy', innerHeight / 2).attr('r', radius).style('fill', function (d) { return d.color; }); if (this.options.showLabels) { circle.append('text').classed('pearlchart__circle-label', true).attr('font-size', this.options.fontSize).attr('x', function (d) { return _this3.scale(d.value); }).attr('y', innerHeight / 2).attr('dy', -(radius * 2) + labelOffset).text(function (d) { return _this3.options.labelFormat(d.value); }).attr('text-anchor', 'middle').style('fill', function (d) { return !_this3.options.colorLabels ? '#000' : d.color; }); } else { circle.append('text').classed('pearlchart__circle-tooltip', true).attr('font-size', this.options.fontSize).attr('x', function (d) { return _this3.scale(d.value); }).attr('y', innerHeight / 2).attr('dy', -(radius * 2) + labelOffset).text(function (d) { return _this3.options.labelFormat(d.value); }).attr('text-anchor', 'middle').style('fill', function (d) { return !_this3.options.colorLabels ? '#000' : d.color; }).style('display', 'none'); } } }], [{ key: 'parseData', value: function parseData(_data) { return _data.map(function (d) { d.color = d.color || '#777777'; return d; }); } }]); return PearlChart; }(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 */ var StackedChart = function (_OECDChart) { inherits(StackedChart, _OECDChart); function StackedChart() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; classCallCheck(this, StackedChart); var _this = possibleConstructorReturn(this, (StackedChart.__proto__ || Object.getPrototypeOf(StackedChart)).call(this)); _this.defaultOptions = { container: null, extent: [0, 100], data: [], fontSize: 14, marginTop: 15, barHeight: 30, marginLeft: 30, marginRight: 45, labelOffset: 5 }; _this.init(options); return _this; } createClass(StackedChart, [{ key: 'render', value: function render() { var _options = this.options, data = _options.data, container = _options.container, extent = _options.extent, marginTop = _options.marginTop, barHeight = _options.barHeight, fontSize = _options.fontSize, marginLeft = _options.marginLeft, marginRight = _options.marginRight; var d3Container = d3Selection.select(container); var dimensions = d3Container.node().getBoundingClientRect(); var outerWidth = dimensions.width; var innerWidth = outerWidth - marginLeft - marginRight; var innerHeight = (barHeight + marginTop) * data.length; this.removeSelections(['.stacked-chart__svg']); this.x = d3Scale.scaleLinear().range([0, innerWidth]).domain(extent); this.colorScale = d3Scale.scaleLinear(); var 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'] * } * ]); */ }, { key: 'update', value: function update(_data) { this.options.data = _data; var data = StackedChart.parseData(_data); StackedChart.validateData(_data); var chart = this.getChart(data); this.getBars(chart); this.getStackedLabels(chart); this.getBarLabels(chart); } }, { key: 'getChart', value: function getChart(_data) { var _this2 = this; var chart = this.chartWrapper.selectAll('.stacked-chart__bar').data(_data, function (d) { return JSON.stringify(d); }); chart.exit().remove(); return chart.enter().append('g').classed('stacked-chart__bar', true).attr('transform', function (d, i) { return 'translate(0, ' + i * (_this2.options.barHeight + _this2.options.marginTop) + ')'; }); } }, { key: 'getBars', value: function getBars(chart) { var _this3 = this; chart.selectAll('.stacked-chart__rect').data(function (data) { return data.values.map(function (value, i) { return { value: value, offset: data.offset[i], colors: data.colors }; }); }).enter().append('rect').classed('stacked-chart__rect', true).attr('width', function (d) { return _this3.x(d.value); }).attr('x', function (d) { return _this3.x(d.offset); }).attr('height', this.options.barHeight).attr('fill', function (d, i, nodes) { return _this3.colorScale.domain([0, nodes.length]).range(d.colors)(i); }); } }, { key: 'getStackedLabels', value: function getStackedLabels(chart) { var _this4 = this; chart.selectAll('.stacked-chart__stackedlabel').data(function (data) { return data.stackLabels.map(function (stackLabel, i) { return { stackLabel: 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', function (d) { return _this4.x(d.offset + d.value / 2); }).attr('text-anchor', 'middle').attr('dy', '.35em').attr('fill', '#fff').text(function (d) { return d.stackLabel; }).attr('font-size', this.options.fontSize); } }, { key: 'getBarLabels', value: function getBarLabels(chart) { var _options2 = this.options, labelOffset = _options2.labelOffset, barHeight = _options2.barHeight, fontSize = _options2.fontSize; chart.selectAll('.stacked-chart__barlabel').data(function (d) { return d.barLabels; }).enter().append('text').classed('stacked-chart__barlabel', true).attr('y', barHeight / 2).attr('x', function (d, i, nodes) { return i === 0 ? 0 : nodes[i].parentNode.getBoundingClientRect().width; }).attr('dy', '.35em').attr('dx', function (d, i) { return i === 0 ? '-' + labelOffset + 'px' : labelOffset + 'px'; }).attr('text-anchor', function (d, i) { return i === 0 ? 'end' : 'start'; }).text(function (d) { return d; }).attr('font-size', fontSize); } }], [{ key: 'parseData', value: function parseData(_data) { return _data.map(function (d) { var factor = 100 / d.values.reduce(function (a, b) { return a + b; }); d.values = d.values.map(function (value) { return value * factor; }); d.offset = d.values.map(function (value, i) { return d3Array.sum(d.values.slice(0, i)); }); d.stackLabels = d.stackLabels || []; return d; }); } }, { key: 'validateData', value: function validateData(_data) { var invalidData = _data.filter(function (d) { return 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.'); } } }]); return StackedChart; }(OECDChart); /** * A BoxPlot component * @example * var BoxPlotExample = new OECDCharts.BoxPlot({ * container: '#BoxPlotExample', * title: 'Box Plot', * extent: [350, 650], * step: 50, * renderInfoButton: true, * data: [ * { * values: [480, 500, 530], * colors: ['#fddd5d', '#C7754E', '#900c3f'], * labelLeft: { * text: 'male low' * }, * labelRight: { * text: 'male top' * } * }, * { * values: [400, 520, 550], * colors: ['#aad356', '#61B77F', '#189aa8'] * } * ] * }); * @constructor * @param {object} options - The options object for the Box Plot * @param {string} options.container - The DOM element to use as container * @param {string} options.title - The title to display * @param {array} options.extent - The min and max value for generating the x-axis * @param {number} options.step - Indicates the stepsize for the x-axis ticks * @param {string} options.legend - HTML code for the legend * @param {bool} [options.renderInfoButton = false] - The info-icon for the tooltip, renders after the title * @param {int} [options.fontSize = 12] - The font-size for the labels in px * @param {int} [options.markerHeight = 30] - The height of the marker in px * @param {int} [options.markerHeight = 10] - The width of the marker in px * @param {int} [options.radius = 10] -The radius for the pearl in px * @param {array} options.data - The data as array * @param {array} options.data.values - The values to display * @param {array} options.data.colors - The colors for the elements * @param {object} options.data.labelLeft - (optional) Label for the left marker * @param {string} options.data.labelLeft.label - (optional) Text for the left marker * @param {string} options.data.labelLeft.icon - (optional) Path to icon for the left marker * @param {object} options.data.labelRight - (optional) Label for the right marker * @param {string} options.data.labelRight.label - (optional) Text for the right marker * @param {string} options.data.labelRight.icon - (optional) Path to icon for the right marker */ var BoxPlot = function (_OECDChart) { inherits(BoxPlot, _OECDChart); function BoxPlot() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; classCallCheck(this, BoxPlot); var _this = possibleConstructorReturn(this, (BoxPlot.__proto__ || Object.getPrototypeOf(BoxPlot)).call(this)); _this.defaultOptions = { container: null, extent: null, data: [], step: 50, legend: null, innerMarginTop: 10, innerMarginBottom: 10, innerMarginLeft: 5, innerMarginRight: 5, markerHeight: 30, markerWidth: 10, radius: 10, fontSize: 12 }; _this.init(options); return _this; } createClass(BoxPlot, [{ key: 'render', value: function render(options) { this.update(this.options.data); } /** * @memberof BoxPlot * @param {array} data - an array containing objects with the new data * @example * BoxPlotExample.update([ * { * values: [400, 550, 580], * colors: ['#fddd5d', '#C7754E', '#900c3f'], * labelLeft: { * text: 'new label left', * }, * labelRight: { * text: 'new label right', * } * }, * { * values: [400, 520, 570], * colors: ['#aad356', '#61B77F', '#189aa8'] * } * ]); */ }, { key: 'update', value: function update(_data) { this.options.data = _data; var data = BoxPlot.parseData(_data); BoxPlot.validateData(data); this.getChart(data); var markerGroup = this.getMarkerGroups(data); this.getMarkers(markerGroup); this.getLabels(markerGroup); } }, { key: 'getChart', value: function getChart(data) { var _options = this.options, container = _options.container, extent = _options.extent, step = _options.step, legend = _options.legend, markerHeight = _options.markerHeight, innerMarginTop = _options.innerMarginTop, innerMarginBottom = _options.innerMarginBottom, innerMarginLeft = _options.innerMarginLeft, innerMarginRight = _options.innerMarginRight; var d3Container = d3Selection.select(container); var dimensions = d3Container.node().getBoundingClientRect(); var outerWidth = dimensions.width; var innerWidth = outerWidth - innerMarginLeft - innerMarginRight; this.removeSelections(['.boxplot__svg', '.boxplot__legend']); this.x = d3Scale.scaleLinear().range([0, innerWidth - innerMarginRight - innerMarginLeft]).domain(extent); var height = (markerHeight + 20) * data.length + innerMarginTop + innerMarginBottom; d3Container.append('div').attr('class', 'boxplot__legend').html(legend); this.svg = d3Container.classed('OECDCharts__BoxPlot', true).append('svg').classed('boxplot__svg', true).attr('width', innerWidth).attr('height', height).append('g').attr('transform', 'translate(' + innerMarginLeft + ', 0)'); var ticks = (extent[1] - extent[0]) / step; var axis = d3Axis.axisBottom(this.x).ticks(ticks).tickSize(-height); this.svg.append('g').classed('boxplot__axis', true).attr('transform', 'translate(' + innerMarginLeft + ', ' + (height - innerMarginTop - innerMarginBottom) + ')').call(axis); } }, { key: 'getMarkerGroups', value: function getMarkerGroups(_data) { var _this2 = this; var markerGroup = this.svg.selectAll('.boxplot__marker-group').data(_data, function (d) { return JSON.stringify(d); }); markerGroup.exit().remove(); return markerGroup.enter().append('g').classed('boxplot__marker-group', true).attr('transform', function (d, i) { return 'translate(0, ' + ((_this2.options.markerHeight + 20) * i + 1) + ')'; }); } }, { key: 'getMarkers', value: function getMarkers(markerGroup) { var _this3 = this; markerGroup.append('line').classed('marker-group__line', true).attr('x1', function (d) { return _this3.x(d.values[0]) + 5; }).attr('x2', function (d) { return _this3.x(d.values[2]) + 5; }).attr('y1', this.options.markerHeight / 2).attr('y2', this.options.markerHeight / 2).style('stroke', function (d) { return d.colors[1]; }); markerGroup.selectAll('.marker-group__rect').data(function (d) { return [0, 2].map(function (i) { return { value: d.values[i], color: d.colors[i] }; }); }).enter().append('rect').classed('marker-group__rect', true).attr('height', this.options.markerHeight).attr('width', this.options.markerWidth).attr('x', function (d) { return _this3.x(d.value) + 5 - _this3.options.markerWidth / 2; }).style('fill', function (d) { return d.color; }); markerGroup.selectAll('.marker-group__circle').data(function (d) { return [d]; }).enter().append('circle').classed('marker-group__circle', true).attr('r', this.options.radius).attr('cx', function (d) { return _this3.x(d.values[1]) + 5; }).attr('cy', this.options.markerHeight / 2).style('fill', function (d) { return d.colors[1]; }); } }, { key: 'getLabels', value: function getLabels(markerGroup) { this.getLabel(markerGroup, 'left'); this.getLabel(markerGroup, 'right'); } }, { key: 'getLabel', value: function getLabel(markerGroup, pos) { var _this4 = this; var left = pos === 'left'; var label = markerGroup.filter(function (d) { return left ? d.labelLeft : d.labelRight; }).selectAll('.marker-group__label--' + pos).data(function (d) { return [d]; }).enter().append('g').classed('marker-group__label--' + pos, true); // add label text label.filter(function (d) { return left ? d.labelLeft.text : d.labelRight.text; }).selectAll('.marker-group__label--text-' + pos).data(function (d) { var _ref = left ? d.labelLeft : d.labelRight, text = _ref.text, icon = _ref.icon; return [{ text: text, icon: icon, value: left ? d.values[0] : d.values[2] }]; }).enter().append('text').classed('marker-group__label--text-' + pos, true).attr('y', this.options.markerHeight / 2 + this.options.fontSize / 4).attr('x', function (d) { var offset = d.icon ? 30 : 5; return left ? _this4.x(d.value) - offset : _this4.x(d.value) + offset + 10; }).text(function (d) { return d.text; }).attr('text-anchor', left ? 'end' : 'start').attr('font-size', this.options.fontSize); // add label icon label.filter(function (d) { return left ? d.labelLeft.icon : d.labelRight.icon; }).selectAll('.marker-group__label--icon-' + pos).data(function (d) { var _ref2 = left ? d.labelLeft : d.labelRight, icon = _ref2.icon; return [{ icon: icon, value: left ? d.values[0] : d.values[2] }]; }).enter().append('svg:image').classed('marker-group__label--icon-' + pos, true).attr('y', this.options.markerHeight / 2 - 10).attr('x', function (d) { return left ? _this4.x(d.value) - 25 : _this4.x(d.value) + 15; }).attr('xlink:href', function (d) { return d.icon; }).attr('width', 20).attr('height', 20); BoxPlot.arrangeLabels(markerGroup, this.svg); } }], [{ key: 'arrangeLabels', value: function arrangeLabels(container, svgContainer) { var svgBB = svgContainer.node().getBoundingClientRect(); container.selectAll('.marker-group__label--left').each(function (node, i, nodes) { var labelNode = nodes[i]; var bb = labelNode.getBoundingClientRect(); if (bb.left <= svgBB.left) { d3Selection.select(labelNode).attr('transform', 'translate(' + (bb.width + 20) + ', ' + bb.height / 1.1 + ')'); } }); container.selectAll('.marker-group__label--right').each(function (node, i, nodes) { var labelNode = nodes[i]; var bb = labelNode.getBoundingClientRect(); if (bb.right >= svgBB.right) { d3Selection.select(labelNode).attr('transform', 'translate(-' + (bb.width + 20) + ', ' + bb.height / 1.1 + ')'); } }); } }, { key: 'parseData', value: function parseData(_data) { return _data.map(function (d) { d.colors = d.colors || ['#f0f0f0', '#555', '#000']; return d; }); } }, { key: 'validateData', value: function validateData(_data) { var invalidData = _data.filter(function (d) { return d.values && d.values.length !== 3; }); if (invalidData.length) { throw Error('invalid data: values needs three items.'); } } }]); return BoxPlot; }(OECDChart); /* eslint-disable */ exports.PearlChart = PearlChart; exports.StackedChart = StackedChart; exports.BoxPlot = BoxPlot; Object.defineProperty(exports, '__esModule', { value: true }); }))); //# sourceMappingURL=oecd-simple-charts.js.map