UNPKG

billboard.js

Version:

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

181 lines (180 loc) • 6.35 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 { window as win } from "../../module/browser.js"; import { isFunction, isObjectType } from "../../module/util/type-checks.js"; import { setTextValue } from "../../module/util/dom.js"; import { $ARC } from "../../config/classes.js"; import { select } from "d3-selection"; //#region src/ChartInternal/internals/text.arc.ts /** * Copyright (c) 2017 ~ present NAVER Corp. * billboard.js project is licensed under the MIT license */ const BREAK_POINT_OFFSET = 15; const DEFAULT_LINE_DISTANCE = 20; const TEXT_VERTICAL_OFFSET = .35; /** * Get the first matching arc chart type * @this {object} ChartInternal context * @param {boolean} excludeMultiGauge Whether to exclude multi gauge type * @returns {string|undefined} Chart type or undefined * @private */ function getArcType(excludeMultiGauge = false) { const $$ = this; return [ "donut", "pie", "polar", "gauge" ].find((type) => $$.hasType(type) && !(type === "gauge" && excludeMultiGauge && $$.hasMultiArcGauge())); } /** * Get label line configuration (line and text) * @this {object} ChartInternal context * @returns {LabelLineConfig} Configuration with chartType, line (show, distance) and text (formatter) * @private */ function getConfig() { const $$ = this; const { config } = $$; const chartType = getArcType.call($$, true); const lineConfig = chartType && config[`${chartType}_label_line`]; const isValidConfig = isObjectType(lineConfig); const defaultFormatter = (value, ratio, id) => id; const line = { show: lineConfig === true || isValidConfig && lineConfig?.show !== false, distance: isValidConfig && lineConfig?.distance || DEFAULT_LINE_DISTANCE }; if (lineConfig === true) return { chartType, line, text: { formatter: defaultFormatter } }; if (!isValidConfig) return { chartType, line: { show: false, distance: DEFAULT_LINE_DISTANCE }, text: { formatter: null } }; let formatter = defaultFormatter; if (isFunction(lineConfig.text)) formatter = lineConfig.text; else if (lineConfig.text === false) formatter = null; return { chartType, line, text: { formatter } }; } /** * Calculate label with line positions for arc data * @this {object} ChartInternal context * @param {object} d Data object * @param {number} lineDistance Horizontal line distance (from getConfig) * @returns {object|null} Object containing startPoint, breakPoint, endPoint, isRight, and midAngle * @private */ function getLinePosition(d, lineDistance) { const $$ = this; const { state } = $$; const updated = $$.updateAngle(d); if (!updated) return null; let { outerRadius } = $$.getRadius(d); let arcOuterRadius = outerRadius; if ($$.hasType("polar")) { arcOuterRadius = $$.getPolarOuterRadius(d, outerRadius); outerRadius = state.radius; } let midAngle = (updated.startAngle + updated.endAngle) / 2; if (Math.abs(updated.endAngle - updated.startAngle - 2 * Math.PI) < .01) midAngle = Math.PI / 2; const sinAngle = Math.sin(midAngle); const cosAngle = -Math.cos(midAngle); const startPoint = { x: sinAngle * arcOuterRadius, y: cosAngle * arcOuterRadius }; const breakRadius = outerRadius + BREAK_POINT_OFFSET; const breakPoint = { x: sinAngle * breakRadius, y: cosAngle * breakRadius }; const isRight = sinAngle >= 0; return { startPoint, breakPoint, endPoint: { x: breakPoint.x + lineDistance * (isRight ? 1 : -1), y: breakPoint.y }, isRight, midAngle }; } /** * Check if label with line type is enabled for arc charts * @this {object} ChartInternal context * @returns {boolean} Whether label with lines are enabled * @private */ function isLabelWithLine() { return getConfig.call(this).line.show; } /** * Render connector lines and text for label with lines * @this {object} ChartInternal context * @param {number} duration Transition duration * @private */ function redrawArcLabelLines(duration) { const $$ = this; const { $el: { arcs }, $T } = $$; const { line: lineConfig, text: textConfig } = getConfig.call($$); const lineDistance = lineConfig.distance; let cachedFontSize = null; arcs.selectAll(`.${$ARC.chartArc}`).each(function(d) { const g = select(this); const linePos = getLinePosition.call($$, d, lineDistance); const { ratio, meetsThreshold, updated } = d._cache ?? {}; if (!updated || !linePos) return; const isVisible = $$.isTargetToShow(d.data.id) && meetsThreshold; const { startPoint, breakPoint, endPoint, isRight } = linePos; const points = `${startPoint.x},${startPoint.y} ${breakPoint.x},${breakPoint.y} ${endPoint.x},${endPoint.y}`; if (lineConfig.show) { let line = g.select(`.${$ARC.arcLabelLine}`); if (line.empty()) line = g.append("polyline").attr("class", $ARC.arcLabelLine); $T(line, duration).attr("points", points).style("stroke", $$.color(d.data)).style("opacity", isVisible ? null : "0"); } let labelLineText = g.select(`.${$ARC.arcLabelLineText}`); if (labelLineText.empty()) labelLineText = g.append("text").attr("class", $ARC.arcLabelLineText).style("pointer-events", "none"); if (isVisible) { const { value } = updated; const { id } = d.data; const text = (textConfig.formatter ?? $$.getArcLabelConfig("format") ?? $$.defaultArcValueFormat)(value, ratio, id).toString(); setTextValue(labelLineText, text, [-1, 1], false); const pos = { x: endPoint.x + 5 * (isRight ? 1 : -1), y: endPoint.y }; labelLineText.style("text-anchor", isRight ? "start" : "end"); const textNode = labelLineText.node(); const tspanNodes = textNode?.querySelectorAll("tspan"); if (cachedFontSize === null) cachedFontSize = parseFloat(win.getComputedStyle(textNode).fontSize) || 12; if (tspanNodes && tspanNodes.length > 1) { const centerOffset = (tspanNodes.length - 3) / 2; pos.y += (-centerOffset + TEXT_VERTICAL_OFFSET) * cachedFontSize; } else pos.y += TEXT_VERTICAL_OFFSET * cachedFontSize; $T(labelLineText, duration).attr("transform", `translate(${pos.x},${pos.y})`).style("opacity", null).style("fill", $$.updateTextColor.bind($$)(d)); } else $T(labelLineText, duration).style("opacity", "0"); }); } //#endregion export { isLabelWithLine, redrawArcLabelLines };