UNPKG

billboard.js

Version:

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

1,019 lines (1,004 loc) 33 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.0.1 * @requires billboard.js * @summary billboard.js plugin */ import { interpolateHslLong } from 'd3-interpolate'; import { scaleSequential, scaleSymlog, scaleSequentialLog } from 'd3-scale'; import { axisRight } from 'd3-axis'; /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ /** * CSS class names definition * @private */ const $TOOLTIP = { tooltip: "bb-tooltip", tooltipName: "bb-tooltip-name" }; /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license * @ignore */ 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) { 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))); /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ /** * Window object * @private */ /* eslint-disable no-new-func, no-undef */ /** * 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")(); } const win = getGlobal(); const doc = win?.document; /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license * @ignore */ /** * 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; // if fails to parse, try by new Date() // https://github.com/naver/billboard.js/issues/1714 parsedDate = format.dataTime(config.data_xFormat)(date) ?? new Date(date); } else if (isNumber(date) && !isNaN(date)) { parsedDate = new Date(+date); } if (!parsedDate || isNaN(+parsedDate)) { console && console.error && console.error(`Failed to parse x '${date}' to Date object`); } return parsedDate; } /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license * @ignore */ // ==================================== // Internal Helper (Not Exported) // ==================================== /** * 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["getBBox"](); // cache per API: getBoundingClientRect(viewport coords) and getBBox(local coords) // return different values for the same node and must not share one slot const cacheKey = "rectBBox"; if (forceEval) { return _(node); } else { // will cache the value if the element is not a SVGElement or the width is not set const needEvaluate = !(cacheKey in node) || (node.hasAttribute("width") && node[cacheKey].width !== +(node.getAttribute("width") || 0)); return needEvaluate ? (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); } // emulate event ({ mouse: (() => { const getParams = () => ({ bubbles: false, cancelable: false, screenX: 0, screenY: 0, clientX: 0, clientY: 0 }); try { // eslint-disable-next-line no-new new MouseEvent("t"); return (el, eventType, params = getParams()) => { el.dispatchEvent(new MouseEvent(eventType, params)); }; } catch { // Polyfills DOM4 MouseEvent return (el, eventType, params = getParams()) => { const mouseEvent = doc.createEvent("MouseEvent"); // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/initMouseEvent mouseEvent.initMouseEvent(eventType, params.bubbles, params.cancelable, win, 0, // the event's mouse click count params.screenX, params.screenY, params.clientX, params.clientY, false, false, false, false, 0, null); el.dispatchEvent(mouseEvent); }; } })()}); /** * 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; } return undefined; }; Object.keys(thisConfig).forEach(key => { target = config; keys = key.split("_"); read = find(); if (isDefined(read)) { thisConfig[key] = read; } }); // only should run in the ChartInternal context if (this.api) { this.state.orgConfig = config; } } /** * 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 */ class Plugin { $$; options; config; static version = "4.0.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]; }); } } /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ /** * CSS class names definition * @private */ var CLASS = { colorScale: "bb-colorscale", stanfordElements: "bb-stanford-elements", stanfordLine: "bb-stanford-line", stanfordLines: "bb-stanford-lines", stanfordRegion: "bb-stanford-region", stanfordRegions: "bb-stanford-regions" }; /** * 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) { // ray-casting algorithm based on // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html 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; const intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); if (intersect) { 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 }; } /** * 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) { { return (n) => Math.round(n).toString(); } } /** * Stanford diagram plugin color scale class * @class ColorScale * @param {Stanford} owner Stanford instance * @private */ class ColorScale { 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", CLASS.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)); // Legend Axis 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, 1000, 10000, 100000, 1000000, 10000000]); } else if (isFunction(scaleFormat)) { legendAxis.tickFormat(scaleFormat); } else { legendAxis.tickFormat(format()); } // Draw Axis 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) // Power of Ten .text(10) .append("tspan") .attr("dy", "-.7em") // https://bl.ocks.org/mbostock/6738229 .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; } } /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ // @ts-nocheck /** * Stanford diagram plugin element class * @class ColorScale * @param {Stanford} owner Stanford instance * @private */ class Elements { owner; constructor(owner) { this.owner = owner; // MEMO: Avoid blocking eventRect const elements = owner.$$.$el.main.select(".bb-chart") .append("g") .attr("class", CLASS.stanfordElements); elements.append("g").attr("class", CLASS.stanfordLines); elements.append("g").attr("class", CLASS.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($$); // Stanford-Lines const stanfordLine = main.select(`.${CLASS.stanfordLines}`) .style("shape-rendering", "geometricprecision") .selectAll(`.${CLASS.stanfordLine}`) .data(this.owner.config.lines); // exit stanfordLine.exit().transition() .duration(duration) .style("opacity", "0") .remove(); // enter const stanfordLineEnter = stanfordLine.enter().append("g"); stanfordLineEnter.append("line") .style("opacity", "0"); stanfordLineEnter .merge(stanfordLine) .attr("class", d => CLASS.stanfordLine + (d.class ? ` ${d.class}` : "")) .select("line") .transition() .duration(duration) .attr("x1", d => { const v = isRotated ? yvCustom(d, "y1") : xvCustom(d, "x1"); return v; }) .attr("x2", d => (isRotated ? yvCustom(d, "y2") : xvCustom(d, "x2"))) .attr("y1", d => { const v = isRotated ? xvCustom(d, "x1") : yvCustom(d, "y1"); return v; }) .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($$); // Stanford-Regions let stanfordRegion = main.select(`.${CLASS.stanfordRegions}`) .selectAll(`.${CLASS.stanfordRegion}`) .data(this.owner.config.regions); // exit stanfordRegion.exit().transition() .duration(duration) .style("opacity", "0") .remove(); // enter 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); // update stanfordRegion .attr("class", d => CLASS.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 : 0.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; const yScale = d.axis && d.axis === "y2" ? $$.scale.y2 : $$.scale.y; const value = xyValue ? d[xyValue] : $$.getBaseValue(d); return yScale(value); } } /** * 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 */ class Options { 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: undefined, /** * 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: undefined, scale_max: undefined, scale_width: 20, scale_format: undefined, /** * 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: [] }; } } /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ // @ts-nocheck /** * 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: 1, l: 0.5, 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-scale](https://github.com/d3/d3-scale) * - [d3-brush](https://github.com/d3/d3-brush) * - [d3-axis](https://github.com/d3/d3-axis) * @class plugin-stanford * @requires d3-selection * @requires d3-interpolate * @requires d3-scale * @requires d3-brush * @requires d3-axis * @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({ ... }) * ] * }) */ class Stanford extends Plugin { colorScale; elements; constructor(options) { super(options); this.config = new Options(); return this; } $beforeInit() { const { $$ } = this; // override on config values & methods $$.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 = undefined; d.maxEpochs = undefined; d.colors = undefined; d.colorscale = undefined; }); } initStanfordData() { const { config } = this; const target = this.$$.data.targets[0]; // TODO STANFORD see if (data.js -> orderTargets)+ can be used instead // Make larger values appear on top target.values.sort(compareEpochs); // Get min/max epochs 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), hsl(0)); target.colorscale = scaleSequentialLog(target.colors) .domain([target.minEpochs, target.maxEpochs]); } getStanfordPointColor(d) { const target = this.data.targets[0]; return target.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 $$ = this; const target = $$.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 }; } } export { Stanford as default };