devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
437 lines (430 loc) • 16.9 kB
JavaScript
/**
* DevExtreme (cjs/__internal/viz/m_pie_chart.js)
* Version: 24.2.6
* Build date: Mon Mar 17 2025
*
* Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _component_registrator = _interopRequireDefault(require("../../core/component_registrator"));
var _common = require("../../core/utils/common");
var _extend2 = require("../../core/utils/extend");
var _iterator = require("../../core/utils/iterator");
var _type = require("../../core/utils/type");
var _consts = _interopRequireDefault(require("../../viz/components/consts"));
var _annotations = require("../../viz/core/annotations");
var _center_template = require("../../viz/core/center_template");
var _utils = require("../../viz/core/utils");
var _range = require("../../viz/translators/range");
var _translator1d = require("../../viz/translators/translator1d");
var _m_base_chart = require("./chart_components/m_base_chart");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : {
default: e
}
}
const {
states: states
} = _consts.default;
const seriesSpacing = _consts.default.pieSeriesSpacing;
const OPTIONS_FOR_REFRESH_SERIES = ["startAngle", "innerRadius", "segmentsDirection", "type"];
const NORMAL_STATE = states.normalMark;
const HOVER_STATE = states.hoverMark;
const SELECTED_STATE = states.selectedMark;
const MAX_RESOLVE_ITERATION_COUNT = 5;
const LEGEND_ACTIONS = [states.resetItem, states.applyHover, states.applySelected, states.applySelected];
function shiftInColumnFunction(box, length) {
return {
x: box.x,
y: box.y - length
}
}
function dividePoints(series, points) {
return series.getVisiblePoints().reduce(((r, point) => {
const angle = (0, _utils.normalizeAngle)(point.middleAngle);
(angle <= 90 || angle >= 270 ? r.right : r.left).push(point);
return r
}), points || {
left: [],
right: []
})
}
function resolveOverlappedLabels(points, shiftCallback, inverseDirection, canvas) {
let overlapped = false;
if (inverseDirection) {
points.left.reverse();
points.right.reverse()
}
overlapped = _m_base_chart.overlapping.resolveLabelOverlappingInOneDirection(points.left, canvas, false, false, shiftCallback);
return _m_base_chart.overlapping.resolveLabelOverlappingInOneDirection(points.right, canvas, false, false, shiftCallback) || overlapped
}
function getLegendItemAction(points) {
let state = NORMAL_STATE;
points.forEach((point => {
var _point$series;
const seriesOptions = null === (_point$series = point.series) || void 0 === _point$series ? void 0 : _point$series.getOptions();
let pointState = point.fullState;
if ("none" === (null === seriesOptions || void 0 === seriesOptions ? void 0 : seriesOptions.hoverMode)) {
pointState &= ~HOVER_STATE
}
if ("none" === (null === seriesOptions || void 0 === seriesOptions ? void 0 : seriesOptions.selectionMode)) {
pointState &= ~SELECTED_STATE
}
state |= pointState
}));
return LEGEND_ACTIONS[state]
}
function correctPercentValue(value) {
if ((0, _type.isNumeric)(value)) {
if (value > 1) {
value = 1
} else if (value < 0) {
value = 0
}
} else {
value = void 0
}
return value
}
const pieSizeEqualizer = function() {
function removeFromList(list, item) {
return list.filter((li => li !== item))
}
let pies = [];
let timers = {};
return {
queue(pie) {
const group = pie.getSizeGroup();
pies = (list = pies, item = pie, removeFromList(list, item).concat(item));
var list, item;
clearTimeout(timers[group]);
timers[group] = setTimeout((() => {
! function(group, allPies) {
const pies = allPies.filter((p => p._isVisible() && p.getSizeGroup() === group));
const minRadius = Math.min.apply(null, pies.map((p => p.getSizeGroupLayout().radius)));
const minPie = pies.filter((p => p.getSizeGroupLayout().radius === minRadius));
pies.forEach((p => p.render({
force: true,
sizeGroupLayout: minPie.length ? minPie[0].getSizeGroupLayout() : {}
})))
}(group, pies)
}))
},
remove(pie) {
pies = removeFromList(pies, pie);
if (!pies.length) {
timers = {}
}
}
}
}();
const dxPieChart = _m_base_chart.BaseChart.inherit({
_themeSection: "pie",
_layoutManagerOptions() {
return (0, _extend2.extend)(true, {}, this.callBase(), {
piePercentage: correctPercentValue(this._themeManager.getOptions("diameter")),
minPiePercentage: correctPercentValue(this._themeManager.getOptions("minDiameter"))
})
},
_optionChangesMap: {
diameter: "REINIT",
minDiameter: "REINIT",
sizeGroup: "REINIT"
},
_disposeCore() {
pieSizeEqualizer.remove(this);
this.callBase()
},
_groupSeries() {
var _series$;
const {
series: series
} = this;
this._groupsData = {
groups: [{
series: series,
valueOptions: {
valueType: "numeric"
}
}],
argumentOptions: null === (_series$ = series[0]) || void 0 === _series$ ? void 0 : _series$.getOptions()
}
},
getArgumentAxis: () => null,
_getValueAxis() {
const translator = (new _translator1d.Translator1D).setCodomain(360, 0);
return {
getTranslator: () => translator,
setBusinessRange(range) {
translator.setDomain(range.min, range.max)
}
}
},
_populateBusinessRange() {
this.series.map((series => {
const range = new _range.Range;
range.addRange(series.getRangeData().val);
series.getValueAxis().setBusinessRange(range);
return range
}))
},
_specialProcessSeries() {
(0, _iterator.each)(this.series, ((_, singleSeries) => {
singleSeries.arrangePoints()
}))
},
_checkPaneName: () => true,
_processSingleSeries(singleSeries) {
this.callBase(singleSeries);
singleSeries.arrangePoints()
},
_handleSeriesDataUpdated() {
let maxPointCount = 0;
this.series.forEach((s => {
maxPointCount = Math.max(s.getPointsCount(), maxPointCount)
}));
this.series.forEach((s => {
s.setMaxPointsCount(maxPointCount)
}));
this.callBase()
},
_getLegendOptions(item) {
const legendItem = this.callBase(item);
const {
legendData: legendData
} = legendItem;
legendData.argument = item.argument;
legendData.argumentIndex = item.argumentIndex;
legendData.points = [item];
return legendItem
},
_getLegendTargets() {
const itemsByArgument = {};
(this.series || []).forEach((series => {
series.getPoints().forEach((point => {
const argument = point.argument.valueOf();
const index = series.getPointsByArg(argument).indexOf(point);
const key = argument.valueOf().toString() + index;
itemsByArgument[key] = itemsByArgument[key] || [];
const argumentCount = itemsByArgument[key].push(point);
point.index = itemsByArgument[key][argumentCount - 2] ? itemsByArgument[key][argumentCount - 2].index : Object.keys(itemsByArgument).length - 1;
point.argumentIndex = index
}))
}));
const items = [];
(0, _iterator.each)(itemsByArgument, ((_, points) => {
points.forEach(((point, index) => {
if (0 === index) {
items.push(this._getLegendOptions(point));
return
}
const item = items[items.length - 1];
item.legendData.points.push(point);
if (!item.visible) {
item.visible = point.isVisible()
}
}))
}));
return items
},
_getLayoutTargets() {
return [{
canvas: this._canvas
}]
},
_getLayoutSeries(series, drawOptions) {
let layout;
const canvas = this._canvas;
let drawnLabels = false;
layout = this.layoutManager.applyPieChartSeriesLayout(canvas, series, true);
series.forEach((singleSeries => {
singleSeries.correctPosition(layout, canvas);
drawnLabels = singleSeries.drawLabelsWOPoints() || drawnLabels
}));
if (drawnLabels) {
layout = this.layoutManager.applyPieChartSeriesLayout(canvas, series, drawOptions.hideLayoutLabels)
}
series.forEach((singleSeries => {
singleSeries.hideLabels()
}));
this._sizeGroupLayout = {
x: layout.centerX,
y: layout.centerY,
radius: layout.radiusOuter,
drawOptions: drawOptions
};
return layout
},
_getLayoutSeriesForEqualPies(series, sizeGroupLayout) {
const canvas = this._canvas;
const layout = this.layoutManager.applyEqualPieChartLayout(series, sizeGroupLayout);
series.forEach((s => {
s.correctPosition(layout, canvas);
s.drawLabelsWOPoints()
}));
this.layoutManager.correctPieLabelRadius(series, layout, canvas);
return layout
},
_updateSeriesDimensions(drawOptions) {
const visibleSeries = this._getVisibleSeries();
const lengthVisibleSeries = visibleSeries.length;
let innerRad;
let delta;
let layout;
const {
sizeGroupLayout: sizeGroupLayout
} = drawOptions;
if (lengthVisibleSeries) {
layout = sizeGroupLayout ? this._getLayoutSeriesForEqualPies(visibleSeries, sizeGroupLayout) : this._getLayoutSeries(visibleSeries, drawOptions);
delta = (layout.radiusOuter - layout.radiusInner - seriesSpacing * (lengthVisibleSeries - 1)) / lengthVisibleSeries;
innerRad = layout.radiusInner;
this._setGeometry(layout);
visibleSeries.forEach((singleSeries => {
singleSeries.correctRadius({
radiusInner: innerRad,
radiusOuter: innerRad + delta
});
innerRad += delta + seriesSpacing
}))
}
},
_renderSeries(drawOptions, isRotated, isLegendInside) {
this._calculateSeriesLayout(drawOptions, isRotated);
if (!drawOptions.sizeGroupLayout && this.getSizeGroup()) {
pieSizeEqualizer.queue(this);
this._clearCanvas();
return
}
this._renderSeriesElements(drawOptions, isLegendInside)
},
_getCenter() {
return this._center
},
getInnerRadius() {
return this._innerRadius
},
_getLegendCallBack() {
const legend = this._legend;
const items = this._getLegendTargets().map((i => i.legendData));
return target => {
items.forEach((data => {
const points = [];
const callback = legend.getActionCallback({
index: data.id
});
this.series.forEach((series => {
const seriesPoints = series.getPointsByKeys(data.argument, data.argumentIndex);
points.push.apply(points, seriesPoints)
}));
if (target && target.argument === data.argument && target.argumentIndex === data.argumentIndex) {
points.push(target)
}
callback(getLegendItemAction(points))
}))
}
},
_locateLabels(resolveLabelOverlapping) {
let iterationCount = 0;
let labelsWereOverlapped;
let wordWrapApplied;
do {
wordWrapApplied = this._adjustSeriesLabels("shift" === resolveLabelOverlapping);
labelsWereOverlapped = this._resolveLabelOverlapping(resolveLabelOverlapping)
} while ((labelsWereOverlapped || wordWrapApplied) && ++iterationCount < 5)
},
_adjustSeriesLabels(moveLabelsFromCenter) {
return this.series.reduce(((r, s) => s.adjustLabels(moveLabelsFromCenter) || r), false)
},
_applyExtraSettings: _common.noop,
_resolveLabelOverlappingShift() {
const inverseDirection = "anticlockwise" === this.option("segmentsDirection");
const seriesByPosition = this.series.reduce(((r, s) => {
(r[s.getOptions().label.position] || r.outside).push(s);
return r
}), {
inside: [],
columns: [],
outside: []
});
let labelsOverlapped = false;
const shiftFunction = (box, length) => (0, _utils.getVerticallyShiftedAngularCoords)(box, -length, this._center);
if (seriesByPosition.inside.length > 0) {
const pointsToResolve = seriesByPosition.inside.reduce(((r, singleSeries) => {
const visiblePoints = singleSeries.getVisiblePoints();
return visiblePoints.reduce(((r, point) => {
r.left.push(point);
return r
}), r)
}), {
left: [],
right: []
});
labelsOverlapped = resolveOverlappedLabels(pointsToResolve, shiftInColumnFunction, inverseDirection, this._canvas) || labelsOverlapped
}
labelsOverlapped = seriesByPosition.columns.reduce(((r, singleSeries) => resolveOverlappedLabels(dividePoints(singleSeries), shiftInColumnFunction, inverseDirection, this._canvas) || r), labelsOverlapped);
if (seriesByPosition.outside.length > 0) {
labelsOverlapped = resolveOverlappedLabels(seriesByPosition.outside.reduce(((r, singleSeries) => dividePoints(singleSeries, r)), null), shiftFunction, inverseDirection, this._canvas) || labelsOverlapped
}
return labelsOverlapped
},
_setGeometry(_ref) {
let {
centerX: x,
centerY: y,
radiusInner: radiusInner
} = _ref;
this._center = {
x: x,
y: y
};
this._innerRadius = radiusInner
},
_disposeSeries() {
this.callBase.apply(this, arguments);
this._abstractSeries = null
},
_legendDataField: "point",
_legendItemTextField: "argument",
_applyPointMarkersAutoHiding: _common.noop,
_renderTrackers: _common.noop,
_trackerType: "PieTracker",
_createScrollBar: _common.noop,
_updateAxesLayout: _common.noop,
_applyClipRects: _common.noop,
_appendAdditionalSeriesGroups: _common.noop,
_prepareToRender: _common.noop,
_isLegendInside: _common.noop,
_renderAxes: _common.noop,
_shrinkAxes: _common.noop,
_isRotated: _common.noop,
_seriesPopulatedHandlerCore: _common.noop,
_reinitAxes: _common.noop,
_correctAxes: _common.noop,
_getExtraOptions() {
return {
startAngle: this.option("startAngle"),
innerRadius: this.option("innerRadius"),
segmentsDirection: this.option("segmentsDirection"),
type: this.option("type")
}
},
getSizeGroup() {
return this._themeManager.getOptions("sizeGroup")
},
getSizeGroupLayout() {
return this._sizeGroupLayout || {}
}
});
(0, _iterator.each)(OPTIONS_FOR_REFRESH_SERIES, ((_, name) => {
dxPieChart.prototype._optionChangesMap[name] = "REFRESH_SERIES_DATA_INIT"
}));
dxPieChart.addPlugin(_center_template.plugins.pieChart);
dxPieChart.addPlugin(_annotations.plugins.core);
dxPieChart.addPlugin(_annotations.plugins.pieChart);
(0, _component_registrator.default)("dxPieChart", dxPieChart);
var _default = exports.default = dxPieChart;