UNPKG

@toast-ui/chart

Version:

TOAST UI Application: Chart

346 lines (345 loc) 15.8 kB
import { AxisType } from "../component/axis"; import { divisors, makeTickPixelPositions, getTextHeight, getTextWidth, } from "./calculator"; import { range, isString, isUndefined, isNumber, calculateSizeWithPercentString, includes, } from "./utils"; import { ANGLE_CANDIDATES, calculateRotatedWidth, calculateRotatedHeight, } from "./geometric"; import { getDateFormat, formatDate } from "./formatDate"; import { calculateDegreeToRadian, DEGREE_360, DEGREE_0, initSectorOptions, getDefaultRadius, } from "./sector"; import { DEFAULT_LABEL_TEXT } from "../brushes/label"; import { getSemiCircleCenterY, getTotalAngle, isSemiCircle } from "./pieSeries"; import { RadialAxisType } from "../store/radialAxes"; function makeAdjustingIntervalInfo(blockCount, axisWidth, blockSize) { let remainBlockCount; let newBlockCount = Math.floor(axisWidth / blockSize); let intervalInfo = null; const interval = newBlockCount ? Math.floor(blockCount / newBlockCount) : blockCount; if (interval > 1) { // remainBlockCount : remaining block count after filling new blocks // | | | | | | | | | | | | - previous block interval // | | | | - new block interval // |*|*| - remaining block remainBlockCount = blockCount - interval * newBlockCount; if (remainBlockCount >= interval) { newBlockCount += Math.floor(remainBlockCount / interval); remainBlockCount = remainBlockCount % interval; } intervalInfo = { blockCount: newBlockCount, remainBlockCount, interval, }; } return intervalInfo; } export function getAutoAdjustingInterval(count, axisWidth, categories) { var _a; const autoInterval = { MIN_WIDTH: 90, MAX_WIDTH: 121, STEP_SIZE: 5, }; const LABEL_MARGIN = 5; if ((_a = categories) === null || _a === void 0 ? void 0 : _a[0]) { const categoryMinWidth = getTextWidth(categories[0]); if (categoryMinWidth < axisWidth / count - LABEL_MARGIN) { return 1; } } let candidates = []; divisors(count).forEach((interval) => { const intervalWidth = (interval / count) * axisWidth; if (intervalWidth >= autoInterval.MIN_WIDTH && intervalWidth <= autoInterval.MAX_WIDTH) { candidates.push({ interval, blockCount: Math.floor(count / interval), remainBlockCount: 0 }); } }); if (!candidates.length) { const blockSizeRange = range(autoInterval.MIN_WIDTH, autoInterval.MAX_WIDTH, autoInterval.STEP_SIZE); candidates = blockSizeRange.reduce((acc, blockSize) => { const candidate = makeAdjustingIntervalInfo(count, axisWidth, blockSize); return candidate ? [...acc, candidate] : acc; }, []); } let tickInterval = 1; if (candidates.length) { const candidate = candidates.reduce((acc, cur) => (cur.blockCount > acc.blockCount ? cur : acc), { blockCount: 0, interval: 1 }); tickInterval = candidate.interval; } return tickInterval; } export function isLabelAxisOnYAxis({ series, options, categories, }) { var _a, _b; return (!!series.bar || !!series.radialBar || (!!series.gauge && Array.isArray(categories) && !categories.length) || (!!series.bullet && !((_b = (_a = options) === null || _a === void 0 ? void 0 : _a.series) === null || _b === void 0 ? void 0 : _b.vertical))); } export function hasBoxTypeSeries(series) { return !!series.column || !!series.bar || !!series.boxPlot || !!series.bullet; } export function isPointOnColumn(series, options) { var _a; if (hasBoxTypeSeries(series)) { return true; } if (series.line || series.area) { return Boolean((_a = options.xAxis) === null || _a === void 0 ? void 0 : _a.pointOnColumn); } return false; } export function isSeriesUsingRadialAxes(series) { return !!series.radar || !!series.radialBar || !!series.gauge; } function getAxisNameUsingRadialAxes(labelAxisOnYAxis) { return { valueAxisName: labelAxisOnYAxis ? 'circularAxis' : 'verticalAxis', labelAxisName: labelAxisOnYAxis ? 'verticalAxis' : 'circularAxis', }; } export function getAxisName(labelAxisOnYAxis, series) { return isSeriesUsingRadialAxes(series) ? getAxisNameUsingRadialAxes(labelAxisOnYAxis) : { valueAxisName: labelAxisOnYAxis ? 'xAxis' : 'yAxis', labelAxisName: labelAxisOnYAxis ? 'yAxis' : 'xAxis', }; } export function getSizeKey(labelAxisOnYAxis) { return { valueSizeKey: labelAxisOnYAxis ? 'width' : 'height', labelSizeKey: labelAxisOnYAxis ? 'height' : 'width', }; } export function getLimitOnAxis(labels) { const values = labels.map((label) => Number(label)); return { min: Math.min(...values), max: Math.max(...values), }; } export function hasSecondaryYAxis(options) { var _a; return Array.isArray((_a = options) === null || _a === void 0 ? void 0 : _a.yAxis) && options.yAxis.length === 2; } export function getYAxisOption(options) { var _a; const secondaryYAxis = hasSecondaryYAxis(options); return { yAxis: secondaryYAxis ? options.yAxis[0] : (_a = options) === null || _a === void 0 ? void 0 : _a.yAxis, secondaryYAxis: secondaryYAxis ? options.yAxis[1] : null, }; } export function getValueAxisName(options, seriesName, valueAxisName) { var _a; const { secondaryYAxis } = getYAxisOption(options); return ((_a = secondaryYAxis) === null || _a === void 0 ? void 0 : _a.chartType) === seriesName ? 'secondaryYAxis' : valueAxisName; } export function getValueAxisNames(options, valueAxisName) { if (includes([AxisType.X, AxisType.CIRCULAR, AxisType.VERTICAL], valueAxisName)) { return [valueAxisName]; } const optionsUsingYAxis = options; const { yAxis, secondaryYAxis } = getYAxisOption(optionsUsingYAxis); return secondaryYAxis ? [yAxis.chartType, secondaryYAxis.chartType].map((seriesName, index) => seriesName ? getValueAxisName(optionsUsingYAxis, seriesName, valueAxisName) : ['yAxis', 'secondaryYAxis'][index]) : [valueAxisName]; } export function getAxisTheme(theme, name) { const { xAxis, yAxis, circularAxis } = theme; let axisTheme; if (name === AxisType.X) { axisTheme = xAxis; } else if (Array.isArray(yAxis)) { axisTheme = name === AxisType.Y ? yAxis[0] : yAxis[1]; } else if (name === RadialAxisType.CIRCULAR) { axisTheme = circularAxis; } else { axisTheme = yAxis; } return axisTheme; } function getRotationDegree(distance, labelWidth, labelHeight) { let degree = 0; ANGLE_CANDIDATES.every((angle) => { const compareWidth = calculateRotatedWidth(angle, labelWidth, labelHeight); degree = angle; return compareWidth > distance; }); return distance < labelWidth ? degree : 0; } function hasYAxisMaxLabelLengthChanged(previousAxes, currentAxes, field) { var _a, _b; const prevYAxis = previousAxes[field]; const yAxis = currentAxes[field]; if (!prevYAxis && !yAxis) { return false; } return ((_a = prevYAxis) === null || _a === void 0 ? void 0 : _a.maxLabelWidth) !== ((_b = yAxis) === null || _b === void 0 ? void 0 : _b.maxLabelWidth); } function hasYAxisTypeMaxLabelChanged(previousAxes, currentAxes) { return (hasYAxisMaxLabelLengthChanged(previousAxes, currentAxes, 'yAxis') || hasYAxisMaxLabelLengthChanged(previousAxes, currentAxes, 'secondaryYAxis')); } function hasXAxisSizeChanged(previousAxes, currentAxes) { const { maxHeight: prevMaxHeight } = previousAxes.xAxis; const { maxHeight } = currentAxes.xAxis; return prevMaxHeight !== maxHeight; } export function hasAxesLayoutChanged(previousAxes, currentAxes) { return (hasYAxisTypeMaxLabelChanged(previousAxes, currentAxes) || hasXAxisSizeChanged(previousAxes, currentAxes)); } export function getRotatableOption(options) { var _a, _b, _c, _d; return _d = (_c = (_b = (_a = options) === null || _a === void 0 ? void 0 : _a.xAxis) === null || _b === void 0 ? void 0 : _b.label) === null || _c === void 0 ? void 0 : _c.rotatable, (_d !== null && _d !== void 0 ? _d : true); } export function getViewAxisLabels(axisData, axisSize) { var _a, _b, _c, _d; const { labels, pointOnColumn, labelDistance, tickDistance, labelInterval, tickInterval, tickCount, scale, } = axisData; let axisSizeAppliedRatio = axisSize; let additional = 0; let labelAdjustment = 0; if (scale) { const sizeRatio = (_b = (_a = scale) === null || _a === void 0 ? void 0 : _a.sizeRatio, (_b !== null && _b !== void 0 ? _b : 1)); const positionRatio = (_d = (_c = scale) === null || _c === void 0 ? void 0 : _c.positionRatio, (_d !== null && _d !== void 0 ? _d : 0)); axisSizeAppliedRatio = axisSize * sizeRatio; additional = axisSize * positionRatio; } else { const interval = labelInterval === tickInterval ? labelInterval : 1; labelAdjustment = pointOnColumn ? ((labelDistance !== null && labelDistance !== void 0 ? labelDistance : tickDistance * interval)) / 2 : 0; } const relativePositions = makeTickPixelPositions(axisSizeAppliedRatio, tickCount, additional); return labels.reduce((acc, text, index) => { const offsetPos = relativePositions[index] + labelAdjustment; const needRender = !(index % labelInterval) && offsetPos <= axisSize; return needRender ? [...acc, { offsetPos, text }] : acc; }, []); } export function makeTitleOption(title) { if (isUndefined(title)) { return title; } const defaultOption = { text: '', offsetX: 0, offsetY: 0 }; return isString(title) ? Object.assign(Object.assign({}, defaultOption), { text: title }) : Object.assign(Object.assign({}, defaultOption), title); } export function getAxisFormatter(options, axisName) { var _a, _b, _c; const axisOptions = Object.assign(Object.assign({}, getYAxisOption(options)), { xAxis: options.xAxis }); return _c = (_b = (_a = axisOptions[axisName]) === null || _a === void 0 ? void 0 : _a.label) === null || _b === void 0 ? void 0 : _b.formatter, (_c !== null && _c !== void 0 ? _c : ((value) => value)); } export function getLabelsAppliedFormatter(labels, options, dateType, axisName) { var _a, _b; const dateFormatter = getDateFormat((_b = (_a = options) === null || _a === void 0 ? void 0 : _a[axisName]) === null || _b === void 0 ? void 0 : _b.date); const formattedLabels = dateType && dateFormatter ? labels.map((label) => formatDate(dateFormatter, new Date(label))) : labels; const formatter = getAxisFormatter(options, axisName); return formattedLabels.map((label, index) => formatter(label, { index, labels, axisName })); } export function makeRotationData(maxLabelWidth, maxLabelHeight, distance, rotatable) { const degree = getRotationDegree(distance, maxLabelWidth, maxLabelHeight); if (!rotatable || degree === 0) { return { needRotateLabel: false, radian: 0, rotationHeight: maxLabelHeight, }; } return { needRotateLabel: degree > 0, radian: calculateDegreeToRadian(degree, 0), rotationHeight: calculateRotatedHeight(degree, maxLabelWidth, maxLabelHeight), }; } export function getMaxLabelSize(labels, xMargin, font = DEFAULT_LABEL_TEXT) { const maxLengthLabel = labels.reduce((acc, cur) => (acc.length > cur.length ? acc : cur), ''); return { maxLabelWidth: getTextWidth(maxLengthLabel, font) + xMargin, maxLabelHeight: getTextHeight(maxLengthLabel, font), }; } export function getLabelXMargin(axisName, options) { var _a, _b, _c, _d; if (axisName === 'xAxis') { return 0; } const axisOptions = getYAxisOption(options); return Math.abs((_d = (_c = (_b = (_a = axisOptions) === null || _a === void 0 ? void 0 : _a[axisName]) === null || _b === void 0 ? void 0 : _b.label) === null || _c === void 0 ? void 0 : _c.margin, (_d !== null && _d !== void 0 ? _d : 0))); } export function getInitAxisIntervalData(isLabelAxis, params) { var _a, _b, _c, _d, _e, _f; const { axis, categories, layout, isCoordinateTypeChart } = params; const tickInterval = (_b = (_a = axis) === null || _a === void 0 ? void 0 : _a.tick) === null || _b === void 0 ? void 0 : _b.interval; const labelInterval = (_d = (_c = axis) === null || _c === void 0 ? void 0 : _c.label) === null || _d === void 0 ? void 0 : _d.interval; const existIntervalOptions = isNumber(tickInterval) || isNumber(labelInterval); const needAdjustInterval = isLabelAxis && !isNumber((_f = (_e = axis) === null || _e === void 0 ? void 0 : _e.scale) === null || _f === void 0 ? void 0 : _f.stepSize) && !params.shift && !existIntervalOptions && !isCoordinateTypeChart; const initTickInterval = needAdjustInterval ? getInitTickInterval(categories, layout) : 1; const initLabelInterval = needAdjustInterval ? initTickInterval : 1; const axisData = { tickInterval: (tickInterval !== null && tickInterval !== void 0 ? tickInterval : initTickInterval), labelInterval: (labelInterval !== null && labelInterval !== void 0 ? labelInterval : initLabelInterval), }; return axisData; } function getInitTickInterval(categories, layout) { if (!categories || !layout) { return 1; } const { width } = layout.xAxis; const count = categories.length; return getAutoAdjustingInterval(count, width, categories); } export function getDefaultRadialAxisData(options, plot, maxLabelWidth = 0, maxLabelHeight = 0, isLabelOnVerticalAxis = false) { var _a; const centerX = plot.width / 2; if (isLabelOnVerticalAxis) { const { startAngle, endAngle, clockwise } = initSectorOptions((_a = options) === null || _a === void 0 ? void 0 : _a.series); const isSemiCircular = isSemiCircle(clockwise, startAngle, endAngle); return { isSemiCircular, axisSize: getDefaultRadius(plot, isSemiCircular, maxLabelWidth, maxLabelHeight), centerX, centerY: isSemiCircular ? getSemiCircleCenterY(plot.height, clockwise) : plot.height / 2, totalAngle: getTotalAngle(clockwise, startAngle, endAngle), drawingStartAngle: startAngle, clockwise, startAngle, endAngle, }; } return { isSemiCircular: false, axisSize: getDefaultRadius(plot, false, maxLabelWidth, maxLabelHeight), centerX, centerY: plot.height / 2, totalAngle: DEGREE_360, drawingStartAngle: DEGREE_0, clockwise: true, startAngle: DEGREE_0, endAngle: DEGREE_360, }; } export function getRadiusInfo(axisSize, radiusRange, count = 1) { var _a, _b, _c, _d; const innerRadius = calculateSizeWithPercentString(axisSize, (_b = (_a = radiusRange) === null || _a === void 0 ? void 0 : _a.inner, (_b !== null && _b !== void 0 ? _b : 0))); const outerRadius = calculateSizeWithPercentString(axisSize, (_d = (_c = radiusRange) === null || _c === void 0 ? void 0 : _c.outer, (_d !== null && _d !== void 0 ? _d : axisSize))); return { radiusRanges: makeTickPixelPositions(outerRadius - innerRadius, count, innerRadius) .splice(innerRadius === 0 ? 1 : 0, count) .reverse(), innerRadius, outerRadius, }; } export function isDateType(options, axisName) { var _a; return !!((_a = options[axisName]) === null || _a === void 0 ? void 0 : _a.date); }