UNPKG

billboard.js

Version:

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

252 lines (251 loc) • 8.56 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 */ import { document as doc } from "../../module/browser.js"; import { isFunction, isObject, isString, notEmpty } from "../../module/util/type-checks.js"; import { sanitize } from "../../module/sanitize.js"; import { $ARC, $COLOR, $SHAPE } from "../../config/classes.js"; import { KEY } from "../../module/Cache.js"; import { select } from "d3-selection"; //#region src/ChartInternal/internals/color.ts /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ /** * Set pattern's background color * (it adds a <rect> element to simulate bg-color) * @param {SVGPatternElement} pattern SVG pattern element * @param {string} color Color string * @param {string} id ID to be set * @returns {{id: string, node: SVGPatternElement}} * @private */ const _colorizePattern = (pattern, color, id) => { const node = select(pattern.cloneNode(true)); node.attr("id", id).insert("rect", ":first-child").attr("width", node.attr("width")).attr("height", node.attr("height")).style("fill", color); return { id, node: node.node() }; }; /** * Get color pattern from CSS file * CSS should be defined as: background-image: url("#00c73c;#fa7171; ..."); * @param {d3Selection} element Chart element * @returns {Array} * @private */ function _getColorFromCss(element) { const cacheKey = KEY.colorPattern; const { body } = doc; let pattern = body[cacheKey]; if (!pattern) { const delimiter = ";"; const content = element.classed($COLOR.colorPattern, true).style("background-image"); element.classed($COLOR.colorPattern, false); if (content.indexOf(delimiter) > -1) { pattern = content.replace(/url[^#]*|["'()]|(\s|%20)/g, "").split(delimiter).map((v) => v.trim().replace(/[\"'\s]/g, "")).filter(Boolean); body[cacheKey] = pattern; } } return pattern; } const schemeCategory10 = [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" ]; var color_default = { generateColor() { const $$ = this; const { $el, config } = $$; const ids = []; const hasGradient = config.area_linearGradient || config.bar_linearGradient || config.point_radialGradient; let pattern = notEmpty(config.color_pattern) ? config.color_pattern : [..._getColorFromCss($el.chart) || schemeCategory10]; const originalColorPattern = pattern; if (isFunction(config.color_tiles)) { const tiles = config.color_tiles.bind($$.api)(); const colorizedPatterns = pattern.map((p, index) => { const color = p.replace(/[#\(\)\s,]/g, ""); const id = `${$$.state.datetimeId}-pattern-${color}-${index}`; return _colorizePattern(tiles[index % tiles.length], p, id); }); pattern = colorizedPatterns.map((p) => `url(#${p.id})`); $$.patterns = colorizedPatterns; } return function(d) { const colors = config.data_colors; const callback = config.data_color; const id = d.id || d.data?.id || d; const isLine = $$.isTypeOf(id, [ "line", "spline", "step" ]) || !config.data_types[id]; let color; if (isFunction(colors[id])) color = colors[id].bind($$.api)(d); else if (colors[id]) color = colors[id]; else { let idx = ids.indexOf(id); if (idx < 0) { ids.push(id); idx = ids.length - 1; } color = isLine ? originalColorPattern[idx % originalColorPattern.length] : pattern[idx % pattern.length]; colors[id] = color; } color = isFunction(callback) ? callback.call($$.api, color, d) : color; if (hasGradient && $el.defs) { const stop = $$.$el.defs.selectAll(`[id$='-gradient${$$.getTargetSelectorSuffix(id)}'] stop`); let hasSameColor; stop.each(function(d, i) { hasSameColor = i === 0 ? this.style.stopColor : this.style.stopColor === hasSameColor; }); hasSameColor === true && stop.attr("stop-color", color); } return color; }; }, generateLevelColor() { const { config } = this; const colors = config.color_pattern; const threshold = config.color_threshold; const asValue = threshold.unit === "value"; const max = threshold.max || 100; const values = threshold.values && threshold.values.length ? threshold.values : []; return notEmpty(threshold) ? function(value) { const v = asValue ? value : value * 100 / max; let color = colors[colors.length - 1]; for (let i = 0, l = values.length; i < l; i++) if (v <= values[i]) { color = colors[i]; break; } return color; } : null; }, /** * Append data backgound color filter definition * @param {string|object|function} color Color string, object, or function * @param {object} attr filter attribute * @private */ generateTextBGColorFilter(color, attr = { x: 0, y: 0, width: 1, height: 1 }) { const $$ = this; const { $el: { defs }, state } = $$; if (color) { let ids = []; if (isString(color)) ids.push(""); else if (isObject(color)) ids = Object.keys(color); else if (isFunction(color)) ids = $$.mapToTargetIds(); ids.forEach((v) => { const id = `${state.datetimeId}-labels-bg${$$.getTargetSelectorSuffix(v)}${isString(color) ? $$.getTargetSelectorSuffix(color) : ""}`; const colorValue = sanitize(v === "" ? color : color?.[v] || ""); if (defs.select(`#${id}`).empty()) { const filter = defs.append("filter").attr("x", attr.x).attr("y", attr.y).attr("width", attr.width).attr("height", attr.height).attr("id", id); filter.append("feFlood").attr("flood-color", colorValue); filter.append("feComposite").attr("in", "SourceGraphic"); } }); } }, /** * Get data gradient color url * @param {string} id Data id * @returns {string} * @private */ getGradienColortUrl(id) { return `url(#${this.state.datetimeId}-gradient${this.getTargetSelectorSuffix(id)})`; }, /** * Update linear/radial gradient definition * - linear: area & bar only * - radial: type which has data points only * @private */ updateLinearGradient() { const $$ = this; const { config, data: { targets }, state: { datetimeId }, $el: { defs } } = $$; targets.forEach((d) => { const id = `${datetimeId}-gradient${$$.getTargetSelectorSuffix(d.id)}`; const radialGradient = $$.hasPointType() && config.point_radialGradient; const supportedType = $$.isAreaType(d) && "area" || $$.isBarType(d) && "bar"; if ((radialGradient || supportedType) && defs.select(`#${id}`).empty()) { const color = $$.color(d); const gradient = { defs: null, stops: [] }; if (radialGradient) { const { cx = .3, cy = .3, r = .7, stops = [[ .1, color, 0 ], [ .9, color, 1 ]] } = radialGradient; gradient.stops = stops; gradient.defs = defs.append("radialGradient").attr("id", `${id}`).attr("cx", cx).attr("cy", cy).attr("r", r); } else { const isRotated = config.axis_rotated; const { x = isRotated ? [1, 0] : [0, 0], y = isRotated ? [0, 0] : [0, 1], stops = [[ 0, color, 1 ], [ 1, color, 0 ]] } = config[`${supportedType}_linearGradient`]; gradient.stops = stops; gradient.defs = defs.append("linearGradient").attr("id", `${id}`).attr("x1", x[0]).attr("x2", x[1]).attr("y1", y[0]).attr("y2", y[1]); } gradient.stops.forEach((v) => { const [offset, stopColor, stopOpacity] = v; const colorValue = isFunction(stopColor) ? stopColor.bind($$.api)(d.id) : stopColor; gradient.defs && gradient.defs.append("stop").attr("offset", offset).attr("stop-color", colorValue || color).attr("stop-opacity", stopOpacity); }); } }); }, /** * Set the data over color. * When is out, will restate in its previous color value * @param {boolean} isOver true: set overed color, false: restore * @param {number|object} d target index or data object for Arc type * @private */ setOverColor(isOver, d) { const $$ = this; const { config, $el: { main } } = $$; const onover = config.color_onover; let color = isOver ? onover : $$.color; if (isObject(color)) color = ({ id }) => id in onover ? onover[id] : $$.color(id); else if (isString(color)) color = () => onover; else if (isFunction(onover)) color = color.bind($$.api); main.selectAll(isObject(d) ? `.${$ARC.arc}${$$.getTargetSelectorSuffix(d.id)}` : `.${$SHAPE.shape}-${d}`).style("fill", color); } }; //#endregion export { color_default as default };