billboard.js
Version:
Re-usable easy interface JavaScript chart library, based on D3 v4+
845 lines (844 loc) • 27.3 kB
JavaScript
/*!
* 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` | <optional> | Sets the opacity of the region as value between 0 and 1 |
* | text | Function | | <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 | | <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 };