UNPKG

billboard.js

Version:

Re-usable easy interface JavaScript chart library, based on D3 v4+

845 lines (844 loc) • 27.3 kB
/*! * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license * * billboard.js, JavaScript chart library * https://naver.github.io/billboard.js/ * * @version 4.1.1 * @requires billboard.js * @summary billboard.js plugin */ import { interpolateHslLong } from "d3-interpolate"; import { scaleSequential, scaleSequentialLog, scaleSymlog } from "d3-scale"; import { axisRight } from "d3-axis"; //#region src/config/classes.ts const $TOOLTIP = { tooltip: "bb-tooltip", tooltipContainer: "bb-tooltip-container", tooltipName: "bb-tooltip-name" }; //#endregion //#region src/module/util/type-checks.ts const isFunction = (v) => typeof v === "function"; const isString = (v) => typeof v === "string"; const isNumber = (v) => typeof v === "number"; const isUndefined = (v) => typeof v === "undefined"; const isDefined = (v) => typeof v !== "undefined"; const isObjectType = (v) => typeof v === "object"; const isEmptyObject = (obj) => { for (const x in obj) if (Object.prototype.hasOwnProperty.call(obj, x)) return false; return true; }; const isEmpty = (o) => isUndefined(o) || o === null || isString(o) && o.length === 0 || isObjectType(o) && !(o instanceof Date) && isEmptyObject(o) || isNumber(o) && isNaN(o); //#endregion //#region src/module/browser.ts /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ /** * Window object * @private */ /** * Get global object * @returns {object} window object * @private */ function getGlobal() { return typeof globalThis === "object" && globalThis !== null && globalThis.Object === Object && globalThis || typeof self === "object" && self !== null && self.Object === Object && self || Function("return this")(); } /** * Get fallback object * @param {object} w global object * @returns {Array} fallback object array * @private */ function getFallback(w) { const hasRAF = typeof w?.requestAnimationFrame === "function" && typeof w?.cancelAnimationFrame === "function"; const hasRIC = typeof w?.requestIdleCallback === "function" && typeof w?.cancelIdleCallback === "function"; const request = (cb) => setTimeout(cb, 1); const cancel = (id) => clearTimeout(id); return [ hasRAF ? w.requestAnimationFrame : request, hasRAF ? w.cancelAnimationFrame : cancel, hasRIC ? w.requestIdleCallback : request, hasRIC ? w.cancelIdleCallback : cancel ]; } const win = getGlobal(); const doc = win?.document; const [requestAnimationFrame, cancelAnimationFrame, requestIdleCallback, cancelIdleCallback] = getFallback(win); /** * Get range * @param {number} start Start number * @param {number} end End number * @param {number} step Step number * @returns {Array} * @private */ const getRange = (start, end, step = 1) => { const res = []; const n = Math.max(0, Math.ceil((end - start) / step)) | 0; for (let i = 0; i < n; i++) res.push(start + i * step); return res; }; /** * Get parsed date value * (It must be called in 'ChartInternal' context) * @param {Date|string|number} date Value of date to be parsed * @returns {Date} * @private */ function parseDate(date) { let parsedDate; if (date instanceof Date) parsedDate = date; else if (isString(date)) { const { config, format } = this; parsedDate = format.dataTime(config.data_xFormat)(date) ?? new Date(date); } else if (isNumber(date) && !isNaN(date)) parsedDate = /* @__PURE__ */ new Date(+date); if (!parsedDate || isNaN(+parsedDate)) console && console.error && console.error(`Failed to parse x '${date}' to Date object`); return parsedDate; } //#endregion //#region src/module/util/dom.ts /** * Get boundingClientRect or BBox with caching. * Internal helper for getBoundingRect() and getBBox() * @param {boolean} relativeViewport Relative to viewport - true: will use .getBoundingClientRect(), false: will use .getBBox() * @param {SVGElement} node Target element * @param {boolean} forceEval Force evaluation * @returns {object} * @private */ function _getRect(relativeViewport, node, forceEval = false) { const _ = (n) => n[relativeViewport ? "getBoundingClientRect" : "getBBox"](); const cacheKey = relativeViewport ? "rectClient" : "rectBBox"; if (forceEval) return _(node); else return !(cacheKey in node) || node.hasAttribute("width") && node[cacheKey].width !== +(node.getAttribute("width") || 0) ? node[cacheKey] = _(node) : node[cacheKey]; } /** * Get BBox. * @param {SVGElement} node Target element * @param {boolean} forceEval Force evaluation * @returns {object} * @private */ function getBBox(node, forceEval = false) { return _getRect(false, node, forceEval); } (() => { const getParams = () => ({ bubbles: false, cancelable: false, screenX: 0, screenY: 0, clientX: 0, clientY: 0 }); try { new MouseEvent("t"); return (el, eventType, params = getParams()) => { el.dispatchEvent(new MouseEvent(eventType, params)); }; } catch { return (el, eventType, params = getParams()) => { const mouseEvent = doc.createEvent("MouseEvent"); mouseEvent.initMouseEvent(eventType, params.bubbles, params.cancelable, win, 0, params.screenX, params.screenY, params.clientX, params.clientY, false, false, false, false, 0, null); el.dispatchEvent(mouseEvent); }; } })(); //#endregion //#region src/config/config.ts /** * Load configuration option * @param {object} config User's generation config value * @private */ function loadConfig(config) { const thisConfig = this.config; let target; let keys; let read; const find = () => { const key = keys.shift(); if (key && target && isObjectType(target) && key in target) { target = target[key]; return find(); } else if (!key) return target; }; Object.keys(thisConfig).forEach((key) => { target = config; keys = key.split("_"); read = find(); if (isDefined(read)) thisConfig[key] = read; }); if (this.api) this.state.orgConfig = config; } //#endregion //#region src/Plugin/Plugin.ts /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ /** * Base class to generate billboard.js plugin * @class Plugin */ /** * Version info string for plugin * @name version * @static * @memberof Plugin * @type {string} * @example * bb.plugin.stanford.version; // ex) 1.9.0 */ var Plugin = class { $$; options; config; static version = "4.1.1"; /** * Constructor * @param {Any} options config option object * @private */ constructor(options = {}) { this.options = options; } /** * Load plugin config from options * @private */ loadConfig() { loadConfig.call(this, this.options); } /** * Lifecycle hook for 'beforeInit' phase. * @private */ $beforeInit() {} /** * Lifecycle hook for 'init' phase. * @private */ $init() {} /** * Lifecycle hook for 'afterInit' phase. * @private */ $afterInit() {} /** * Lifecycle hook for 'redraw' phase. * @private */ $redraw() {} /** * Lifecycle hook for 'willDestroy' phase. * @private */ $willDestroy() { Object.keys(this).forEach((key) => { this[key] = null; delete this[key]; }); } }; //#endregion //#region src/Plugin/stanford/classes.ts /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ /** * CSS class names definition * @private */ var classes_default = { colorScale: "bb-colorscale", stanfordElements: "bb-stanford-elements", stanfordLine: "bb-stanford-line", stanfordLines: "bb-stanford-lines", stanfordRegion: "bb-stanford-region", stanfordRegions: "bb-stanford-regions" }; //#endregion //#region src/Plugin/stanford/util.ts /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license * @ignore */ /** * Check if point is in region * @param {object} point Point * @param {Array} region Region * @returns {boolean} * @private */ function pointInRegion(point, region) { const x = point.x; const y = point.value; let inside = false; for (let i = 0, j = region.length - 1; i < region.length; j = i++) { const xi = region[i].x; const yi = region[i].y; const xj = region[j].x; const yj = region[j].y; if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) inside = !inside; } return inside; } /** * Compare epochs * @param {object} a Target * @param {object} b Source * @returns {number} * @private */ function compareEpochs(a, b) { if (a.epochs < b.epochs) return -1; if (a.epochs > b.epochs) return 1; return 0; } /** * Get region area * @param {Array} points Points * @returns {number} * @private */ function getRegionArea(points) { let area = 0; let point1; let point2; for (let i = 0, l = points.length, j = l - 1; i < l; j = i, i++) { point1 = points[i]; point2 = points[j]; area += point1.x * point2.y; area -= point1.y * point2.x; } area /= 2; return area; } /** * Get centroid * @param {Array} points Points * @returns {object} * @private */ function getCentroid(points) { const area = getRegionArea(points); let x = 0; let y = 0; let f; for (let i = 0, l = points.length, j = l - 1; i < l; j = i, i++) { const point1 = points[i]; const point2 = points[j]; f = point1.x * point2.y - point2.x * point1.y; x += (point1.x + point2.x) * f; y += (point1.y + point2.y) * f; } f = area * 6; return { x: x / f, y: y / f }; } //#endregion //#region src/Plugin/stanford/ColorScale.ts /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ /** * Simple number formatter. * Supports "d" specifier (decimal notation, rounded to integer). * @param {string} specifier Format specifier * @returns {function} Formatter function */ function format(specifier) { if (specifier === "d") return (n) => Math.round(n).toString(); return (n) => String(n); } /** * Stanford diagram plugin color scale class * @class ColorScale * @param {Stanford} owner Stanford instance * @private */ var ColorScale = class { owner; colorScale; constructor(owner) { this.owner = owner; } drawColorScale() { const { $$, config } = this.owner; const target = $$.data.targets[0]; const height = $$.state.height - config.padding_bottom - config.padding_top; const barWidth = config.scale_width; const barHeight = 5; const points = getRange(config.padding_bottom, height, barHeight); const inverseScale = scaleSequential(target.colors).domain([points[points.length - 1], points[0]]); if (this.colorScale) this.colorScale.remove(); this.colorScale = $$.$el.svg.append("g").attr("width", 50).attr("height", height).attr("class", classes_default.colorScale); this.colorScale.append("g").attr("transform", `translate(0, ${config.padding_top})`).selectAll("bars").data(points).enter().append("rect").attr("y", (d, i) => i * barHeight).attr("x", 0).attr("width", barWidth).attr("height", barHeight).attr("fill", (d) => inverseScale(d)); const axisScale = scaleSymlog().domain([target.minEpochs, target.maxEpochs]).range([points[0] + config.padding_top + points[points.length - 1] + barHeight - 1, points[0] + config.padding_top]); const legendAxis = axisRight(axisScale); const scaleFormat = config.scale_format; if (scaleFormat === "pow10") legendAxis.tickValues([ 1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7 ]); else if (isFunction(scaleFormat)) legendAxis.tickFormat(scaleFormat); else legendAxis.tickFormat(format("d")); const axis = this.colorScale.append("g").attr("class", "legend axis").attr("transform", `translate(${barWidth},0)`).call(legendAxis); if (scaleFormat === "pow10") axis.selectAll(".tick text").text(null).filter((d) => d / Math.pow(10, Math.ceil(Math.log(d) / Math.LN10 - 1e-12)) === 1).text(10).append("tspan").attr("dy", "-.7em").text((d) => Math.round(Math.log(d) / Math.LN10)); this.colorScale.attr("transform", `translate(${$$.state.current.width - this.xForColorScale()}, 0)`); } xForColorScale() { return this.owner.config.padding_right + getBBox(this.colorScale.node(), true).width; } getColorScalePadding() { return this.xForColorScale() + this.owner.config.padding_left + 20; } }; //#endregion //#region src/Plugin/stanford/Elements.ts /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ /** * Stanford diagram plugin element class * @class ColorScale * @param {Stanford} owner Stanford instance * @private */ var Elements = class { owner; constructor(owner) { this.owner = owner; const elements = owner.$$.$el.main.select(".bb-chart").append("g").attr("class", classes_default.stanfordElements); elements.append("g").attr("class", classes_default.stanfordLines); elements.append("g").attr("class", classes_default.stanfordRegions); } updateStanfordLines(duration) { const { $$ } = this.owner; const { config, $el: { main } } = $$; const isRotated = config.axis_rotated; const xvCustom = this.xvCustom.bind($$); const yvCustom = this.yvCustom.bind($$); const stanfordLine = main.select(`.${classes_default.stanfordLines}`).style("shape-rendering", "geometricprecision").selectAll(`.${classes_default.stanfordLine}`).data(this.owner.config.lines); stanfordLine.exit().transition().duration(duration).style("opacity", "0").remove(); const stanfordLineEnter = stanfordLine.enter().append("g"); stanfordLineEnter.append("line").style("opacity", "0"); stanfordLineEnter.merge(stanfordLine).attr("class", (d) => classes_default.stanfordLine + (d.class ? ` ${d.class}` : "")).select("line").transition().duration(duration).attr("x1", (d) => { return isRotated ? yvCustom(d, "y1") : xvCustom(d, "x1"); }).attr("x2", (d) => isRotated ? yvCustom(d, "y2") : xvCustom(d, "x2")).attr("y1", (d) => { return isRotated ? xvCustom(d, "x1") : yvCustom(d, "y1"); }).attr("y2", (d) => isRotated ? xvCustom(d, "x2") : yvCustom(d, "y2")).transition().style("opacity", null); } updateStanfordRegions(duration) { const { $$ } = this.owner; const { config, $el: { main } } = $$; const isRotated = config.axis_rotated; const xvCustom = this.xvCustom.bind($$); const yvCustom = this.yvCustom.bind($$); const countPointsInRegion = this.owner.countEpochsInRegion.bind($$); let stanfordRegion = main.select(`.${classes_default.stanfordRegions}`).selectAll(`.${classes_default.stanfordRegion}`).data(this.owner.config.regions); stanfordRegion.exit().transition().duration(duration).style("opacity", "0").remove(); const stanfordRegionEnter = stanfordRegion.enter().append("g"); stanfordRegionEnter.append("polygon").style("opacity", "0"); stanfordRegionEnter.append("text").attr("transform", isRotated ? "rotate(-90)" : "").style("opacity", "0"); stanfordRegion = stanfordRegionEnter.merge(stanfordRegion); stanfordRegion.attr("class", (d) => classes_default.stanfordRegion + (d.class ? ` ${d.class}` : "")).select("polygon").transition().duration(duration).attr("points", (d) => d.points.map((value) => [isRotated ? yvCustom(value, "y") : xvCustom(value, "x"), isRotated ? xvCustom(value, "x") : yvCustom(value, "y")].join(",")).join(" ")).transition().style("opacity", (d) => String(d.opacity ? d.opacity : .2)); stanfordRegion.select("text").transition().duration(duration).attr("x", (d) => isRotated ? yvCustom(getCentroid(d.points), "y") : xvCustom(getCentroid(d.points), "x")).attr("y", (d) => isRotated ? xvCustom(getCentroid(d.points), "x") : yvCustom(getCentroid(d.points), "y")).text((d) => { if (d.text) { const { value, percentage } = countPointsInRegion(d.points); return d.text(value, percentage); } return ""; }).attr("text-anchor", "middle").attr("dominant-baseline", "middle").transition().style("opacity", null); } updateStanfordElements(duration = 0) { this.updateStanfordLines(duration); this.updateStanfordRegions(duration); } xvCustom(d, xyValue) { const $$ = this; const { axis, config } = $$; let value = xyValue ? d[xyValue] : $$.getBaseValue(d); if (axis.isTimeSeries()) value = parseDate.call($$, value); else if (axis.isCategorized() && isString(value)) value = config.axis_x_categories.indexOf(d.value); return $$.scale.x(value); } yvCustom(d, xyValue) { const $$ = this; return (d.axis && d.axis === "y2" ? $$.scale.y2 : $$.scale.y)(xyValue ? d[xyValue] : $$.getBaseValue(d)); } }; //#endregion //#region src/Plugin/stanford/Options.ts /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ /** * Stanford diagram plugin option class * @class StanfordOptions * @param {Options} options Stanford plugin options * @augments Plugin * @returns {StanfordOptions} * @private */ var Options = class { constructor() { return { /** * Set the color of the color scale. This function receives a value between 0 and 1, and should return a color. * @name colors * @memberof plugin-stanford * @type {function} * @default undefined * @example * colors: d3.interpolateHslLong( * d3.hsl(250, 1, 0.5), d3.hsl(0, 1, 0.5) * ) */ colors: void 0, /** * Specify the key of epochs values in the data. * @name epochs * @memberof plugin-stanford * @type {Array} * @default [] * @example * epochs: [ 1, 1, 2, 2, ... ] */ epochs: [], /** * Show additional lines anywhere on the chart. * - Each line object should consist with following options: * * | Key | Type | Description | * | --- | --- | --- | * | x1 | Number | Starting position on the x axis | * | y1 | Number | Starting position on the y axis | * | x2 | Number | Ending position on the x axis | * | y2 | Number | Ending position on the y axis | * | class | String | Optional value. Set a custom css class to this line. | * @type {Array} * @memberof plugin-stanford * @default [] * @example * lines: [ * { x1: 0, y1: 0, x2: 65, y2: 65, class: "line1" }, * { x1: 0, x2: 65, y1: 40, y2: 40, class: "line2" } * ] */ lines: [], /** * Set scale values * @name scale * @memberof plugin-stanford * @type {object} * @property {object} [scale] scale object * @property {number} [scale.min=undefined] Minimum value of the color scale. Default: lowest value in epochs * @property {number} [scale.max=undefined] Maximum value of the color scale. Default: highest value in epochs * @property {number} [scale.width=20] Width of the color scale * @property {string|function} [scale.format=undefined] Format of the axis of the color scale. Use 'pow10' to format as powers of 10 or a custom function. Example: d3.format("d") * @example * scale: { * max: 10000, * min: 1, * width: 500, * * // specify 'pow10' to format as powers of 10 * format: "pow10", * * // or specify a format function * format: function(x) { * return x +"%"; * } * }, */ scale_min: void 0, scale_max: void 0, scale_width: 20, scale_format: void 0, /** * The padding for color scale element * @name padding * @memberof plugin-stanford * @type {object} * @property {object} [padding] padding object * @property {number} [padding.top=0] Top padding value. * @property {number} [padding.right=0] Right padding value. * @property {number} [padding.bottom=0] Bottom padding value. * @property {number} [padding.left=0] Left padding value. * @example * padding: { * top: 15, * right: 0, * bottom: 0, * left: 0 * }, */ padding_top: 0, padding_right: 0, padding_bottom: 0, padding_left: 0, /** * Show additional regions anywhere on the chart. * - Each region object should consist with following options: * * | Key | Type | Default | Attributes | Description | * | --- | --- | --- | --- | --- | * | points | Array | | | Accepts a group of objects that has x and y.<br>These points should be added in a counter-clockwise fashion to make a closed polygon. | * | opacity | Number | `0.2` | &lt;optional> | Sets the opacity of the region as value between 0 and 1 | * | text | Function | | &lt;optional> | This function receives a value and percentage of the number of epochs in this region.<br>Return a string to place text in the middle of the region. | * | class | String | | &lt;optional> | Se a custom css class to this region, use the fill property in css to set a background color. | * @name regions * @memberof plugin-stanford * @type {Array} * @default [] * @example * regions: [ * { * points: [ // add points counter-clockwise * { x: 0, y: 0 }, * { x: 40, y: 40 }, * { x: 0, y: 40 }, * ], * text: function (value, percentage) { * return `Normal Operations: ${value} (${percentage}%)`; * }, * opacity: 0.2, // 0 to 1 * class: "test-polygon1" * }, * ... * ] */ regions: [] }; } }; //#endregion //#region src/Plugin/stanford/index.ts /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ /** * Creates an HSL color object. * @param {number} h Hue (0-360) * @param {number} s Saturation (0-1) * @param {number} l Lightness (0-1) * @param {number} opacity Opacity (0-1), defaults to 1 * @returns {HSLColor} HSL color object */ function hsl(h, s, l, opacity = 1) { return { h: +h, s: +s, l: +l, opacity: +opacity }; } /** * Stanford diagram plugin * - **NOTE:** * - Plugins aren't built-in. Need to be loaded or imported to be used. * - Non required modules from billboard.js core, need to be installed separately. * - Is preferable use `scatter` as data.type * - **Required modules:** * - [d3-selection](https://github.com/d3/d3-selection) * - [d3-interpolate](https://github.com/d3/d3-interpolate) * - [d3-brush](https://github.com/d3/d3-brush) * @class plugin-stanford * @requires d3-selection * @requires d3-interpolate * @requires d3-brush * @param {object} options Stanford plugin options * @augments Plugin * @returns {Stanford} * @example * // Plugin must be loaded before the use. * <script src="$YOUR_PATH/plugin/billboardjs-plugin-stanford.js"><\/script> * * var chart = bb.generate({ * data: { * columns: [ ... ], * type: "scatter" * } * ... * plugins: [ * new bb.plugin.stanford({ * colors: d3.interpolateHslLong( * d3.hsl(250, 1, 0.5), d3.hsl(0, 1, 0.5) * ), * epochs: [ 1, 1, 2, 2, ... ], * lines: [ * { x1: 0, y1: 0, x2: 65, y2: 65, class: "line1" }, * { x1: 0, x2: 65, y1: 40, y2: 40, class: "line2" } * ], * scale: { * max: 10000, * min: 1, * width: 500, * format: 'pow10', * }, * padding: { * top: 15, * right: 0, * bottom: 0, * left: 0 * }, * regions: [ * { * points: [ // add points counter-clockwise * { x: 0, y: 0 }, * { x: 40, y: 40 }, * { x: 0, y: 40 } * ], * text: function (value, percentage) { * return `Normal Operations: ${value} (${percentage}%)`; * }, * opacity: 0.2, // 0 to 1 * class: "test-polygon1" * }, * ... * ] * } * ] * }); * @example * import {bb} from "billboard.js"; * import Stanford from "billboard.js/dist/billboardjs-plugin-stanford"; * * bb.generate({ * plugins: [ * new Stanford({ ... }) * ] * }) */ var Stanford = class extends Plugin { constructor(options) { super(options); this.config = new Options(); return this; } $beforeInit() { const { $$ } = this; $$.config.data_xSort = false; $$.isMultipleX = () => true; $$.showGridFocus = () => {}; $$.labelishData = (d) => d.values; $$.opacityForCircle = () => 1; const getCurrentPadding = $$.getCurrentPadding.bind($$); $$.getCurrentPadding = () => { const padding = getCurrentPadding(); padding.right += this.colorScale ? this.colorScale.getColorScalePadding() : 0; return padding; }; } $init() { const { $$ } = this; this.loadConfig(); $$.color = this.getStanfordPointColor.bind($$); this.colorScale = new ColorScale(this); this.elements = new Elements(this); this.convertData(); this.initStanfordData(); this.setStanfordTooltip(); this.colorScale.drawColorScale(); $$.right += this.colorScale ? this.colorScale.getColorScalePadding() : 0; this.$redraw(); } $redraw(duration) { this.colorScale?.drawColorScale(); this.elements?.updateStanfordElements(duration); } getOptions() { return new Options(); } convertData() { const data = this.$$.data.targets; const epochs = this.options.epochs; data.forEach((d) => { d.values.forEach((v, i) => { v.epochs = epochs[i]; }); d.minEpochs = void 0; d.maxEpochs = void 0; d.colors = void 0; d.colorscale = void 0; }); } initStanfordData() { const { config } = this; const target = this.$$.data.targets[0]; target.values.sort(compareEpochs); let minEpoch = Infinity; let maxEpoch = -Infinity; for (let i = 0; i < target.values.length; i++) { const e = target.values[i].epochs; if (e < minEpoch) minEpoch = e; if (e > maxEpoch) maxEpoch = e; } target.minEpochs = !isNaN(config.scale_min) ? config.scale_min : minEpoch; target.maxEpochs = !isNaN(config.scale_max) ? config.scale_max : maxEpoch; target.colors = isFunction(config.colors) ? config.colors : interpolateHslLong(hsl(250, 1, .5), hsl(0, 1, .5)); target.colorscale = scaleSequentialLog(target.colors).domain([target.minEpochs, target.maxEpochs]); } getStanfordPointColor(d) { return this.data.targets[0].colorscale(d.epochs); } setStanfordTooltip() { const { config } = this.$$; if (isEmpty(config.tooltip_contents)) config.tooltip_contents = function(d, defaultTitleFormat, defaultValueFormat, color) { const { data_x } = config; let html = `<table class="${$TOOLTIP.tooltip}"><tbody>`; d.forEach((v) => { const { id = "", value = 0, epochs = 0, x = "" } = v; html += `<tr> <th>${data_x || ""}</th> <th class="value">${defaultTitleFormat(x)}</th> </tr> <tr> <th>${v.id}</th> <th class="value">${defaultValueFormat(value)}</th> </tr> <tr class="${$TOOLTIP.tooltipName}-${id}"> <td class="name"><span style="background-color:${color(v)}"></span>Epochs</td> <td class="value">${defaultValueFormat(epochs)}</td> </tr>`; }); return `${html}</tbody></table>`; }; } countEpochsInRegion(region) { const target = this.data.targets[0]; const total = target.values.reduce((accumulator, currentValue) => accumulator + Number(currentValue.epochs), 0); const value = target.values.reduce((accumulator, currentValue) => { if (pointInRegion(currentValue, region)) return accumulator + Number(currentValue.epochs); return accumulator; }, 0); return { value, percentage: value !== 0 ? +(value / total * 100).toFixed(1) : 0 }; } }; //#endregion export { Stanford as default };