UNPKG

billboard.js

Version:

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

150 lines (124 loc) 3.98 kB
/** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ import {namespaces as d3Namespaces, select as d3Select} from "d3-selection"; import {document} from "../../module/browser"; import {isFunction, isObjectType, notEmpty, sanitize, toArray} from "../../module/util"; /** * Check if point draw methods are valid * @param {string} point point type * @returns {boolean} * @private */ function _hasValidPointDrawMethods(point: string): boolean { return isObjectType(point) && isFunction(point.create) && isFunction(point.update); } /** * Insert point info defs element * @param {string} point Point element * @param {string} id Point id * @private */ function _insertPointInfoDefs(point: string, id: string): void { const $$ = this; const copyAttr = (from, target) => { const attribs = from.attributes; for (let i = 0, name; (name = attribs[i]); i++) { name = name.name; target.setAttribute(name, from.getAttribute(name)); } }; const doc = new DOMParser().parseFromString(sanitize(point), "image/svg+xml"); const node = doc.documentElement; const clone = document.createElementNS(d3Namespaces.svg, node.nodeName.toLowerCase()); clone.id = id; clone.style.fill = "inherit"; clone.style.stroke = "inherit"; copyAttr(node, clone); if (node.childNodes?.length) { const parent = d3Select(clone); if ("innerHTML" in clone) { parent.html(sanitize(node.innerHTML)); } else { toArray(node.childNodes).forEach(v => { copyAttr(v, parent.append(v.tagName).node()); }); } } $$.$el.defs.node().appendChild(clone); } export default { /** * Check if point type option is valid * @param {string} type point type * @returns {boolean} * @private */ hasValidPointType(type?: string): boolean { // For point.pattern, allow additional SVG shape tags (polygon, ellipse, use) // These will be sanitized before use return /^(circle|rect(angle)?|polygon|ellipse|use)$/i.test(type || this.config.point_type); }, /** * Check if pattern point is set to be used on legend * @returns {boolean} * @private */ hasLegendDefsPoint(): boolean { const {config} = this; return config.legend_show && config.point_pattern?.length && config.legend_usePoint; }, getDefsPointId(id: string): string { const {state: {datetimeId}} = this; return `${datetimeId}-point${id}`; }, /** * Get validated point pattern array * @returns {Array} Array of point types * @private */ getValidPointPattern(): string[] { const {config} = this; // Ensure point_type is restricted to 'circle' or 'rectangle' only const validPointType = /^(circle|rect(angle)?)$/i.test(config.point_type) ? config.point_type : "circle"; return notEmpty(config.point_pattern) ? config.point_pattern : [validPointType]; }, /** * Get generate point function * @returns {function} * @private */ generatePoint(): Function { const $$ = this; const {$el, config} = $$; const ids: string[] = []; const pattern = $$.getValidPointPattern(); return function(method, context, ...args) { return function(d) { const id: string = $$.getTargetSelectorSuffix(d.id || d.data?.id || d); const element = d3Select(this); ids.indexOf(id) < 0 && ids.push(id); let point = pattern[ids.indexOf(id) % pattern.length]; if ($$.hasValidPointType(point)) { point = $$[point]; } else if (!_hasValidPointDrawMethods(point || config.point_type)) { const pointId = $$.getDefsPointId(id); const defsPoint = $el.defs.select(`#${pointId}`); if (defsPoint.size() < 1) { _insertPointInfoDefs.call($$, point, pointId); } if (method === "create") { return $$.custom?.create.bind(context)(element, pointId, ...args); } else if (method === "update") { return $$.custom?.update.bind(context)(element, ...args); } } return point[method]?.bind(context)(element, ...args); }; }; } };