@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
1,198 lines (964 loc) • 35.4 kB
JavaScript
import { drawing as draw } from '@progress/kendo-drawing';
import { ChartElement, Box } from '../../core';
import Crosshair from '../crosshair/crosshair';
import Pane from '../pane';
import { hasValue } from '../utils';
import SeriesBinder from '../series-binder';
import { WHITE, BLACK, X, Y, COORD_PRECISION, TOP, BOTTOM, LEFT, RIGHT, START, END, INHERIT } from '../../common/constants';
import {
append, deepExtend, defined, getSpacing, getTemplate, inArray, isFunction, isString,
limitValue, round, setDefaultOptions, last, cycleIndex
} from '../../common';
import { TRENDLINE_SERIES } from '../constants';
const visiblePoint = (point) => point.options.visible !== false;
class PlotAreaBase extends ChartElement {
constructor(series, options, chartService) {
super(options);
this.initFields(series, options);
this.series = series;
this.initSeries();
this.charts = [];
this.options.legend = this.options.legend || {};
this.options.legend.data = [];
this.axes = [];
this.crosshairs = [];
this.chartService = chartService;
this.originalOptions = options;
this.originalSeries = series;
this._bindCache = new WeakMap();
this.createPanes();
this.render();
this.createCrosshairs();
}
initFields() { }
initSeries() {
const series = this.series;
for (let i = 0; i < series.length; i++) {
series[i].index = i;
}
}
bindPoint(series, pointIx, item) {
let cached = this._bindCache.get(series);
if (!cached) {
cached = [];
this._bindCache.set(series, cached);
}
let data = cached[pointIx];
if (!data) {
data = cached[pointIx] = SeriesBinder.current.bindPoint(series, pointIx, item);
}
return data;
}
createPanes() {
const titleOptions = this.options.title || {};
const paneDefaults = this.options.paneDefaults;
const paneOptions = this.options.panes || [];
const panesLength = Math.max(paneOptions.length, 1);
const panes = [];
const defaults = deepExtend({
title: {
color: titleOptions.color
}
}, paneDefaults);
for (let i = 0; i < panesLength; i++) {
const options = deepExtend({}, defaults, paneOptions[i]);
if (isString(options.title)) {
options.title = deepExtend({ text: options.title }, defaults.title);
}
const currentPane = new Pane(options);
currentPane.paneIndex = i;
panes.push(currentPane);
this.append(currentPane);
}
this.panes = panes;
}
crosshairOptions(axis) {
return axis.options.crosshair;
}
createCrosshairs(panes = this.panes) {
for (let i = 0; i < panes.length; i++) {
const pane = panes[i];
for (let j = 0; j < pane.axes.length; j++) {
const axis = pane.axes[j];
const options = this.crosshairOptions(axis);
if (options && options.visible) {
const currentCrosshair = new Crosshair(this.chartService, axis, options);
this.crosshairs.push(currentCrosshair);
pane.content.append(currentCrosshair);
}
}
}
}
removeCrosshairs(pane) {
const crosshairs = this.crosshairs;
const axes = pane.axes;
for (let i = crosshairs.length - 1; i >= 0; i--) {
for (let j = 0; j < axes.length; j++) {
if (crosshairs[i].axis === axes[j]) {
crosshairs.splice(i, 1);
break;
}
}
}
}
hideCrosshairs() {
const crosshairs = this.crosshairs;
for (let idx = 0; idx < crosshairs.length; idx++) {
crosshairs[idx].hide();
}
}
findPane(name) {
const panes = this.panes;
let matchingPane;
for (let i = 0; i < panes.length; i++) {
if (panes[i].options.name === name) {
matchingPane = panes[i];
break;
}
}
return matchingPane || panes[0];
}
findPointPane(point) {
const panes = this.panes;
let matchingPane;
for (let i = 0; i < panes.length; i++) {
if (panes[i].box.containsPoint(point)) {
matchingPane = panes[i];
break;
}
}
return matchingPane;
}
appendAxis(axis) {
const pane = this.findPane(axis.options.pane);
pane.appendAxis(axis);
this.axes.push(axis);
axis.plotArea = this;
}
removeAxis(axisToRemove) {
const filteredAxes = [];
for (let i = 0; i < this.axes.length; i++) {
const axis = this.axes[i];
if (axisToRemove !== axis) {
filteredAxes.push(axis);
} else {
axis.destroy();
}
}
this.axes = filteredAxes;
}
appendChart(chart, pane) {
this.charts.push(chart);
if (pane) {
pane.appendChart(chart);
} else {
this.append(chart);
}
}
removeChart(chartToRemove) {
const filteredCharts = [];
for (let i = 0; i < this.charts.length; i++) {
const chart = this.charts[i];
if (chart !== chartToRemove) {
filteredCharts.push(chart);
} else {
chart.destroy();
}
}
this.charts = filteredCharts;
}
addToLegend(series) {
const count = series.length;
const legend = this.options.legend;
const labels = legend.labels || {};
const inactiveItems = legend.inactiveItems || {};
const inactiveItemsLabels = inactiveItems.labels || {};
const data = [];
for (let i = 0; i < count; i++) {
const currentSeries = series[i];
const seriesVisible = currentSeries.visible !== false;
if (currentSeries.visibleInLegend === false) {
continue;
}
let text = currentSeries.name;
const labelTemplate = seriesVisible ? getTemplate(labels) : getTemplate(inactiveItemsLabels) || getTemplate(labels);
if (labelTemplate) {
text = labelTemplate({
text: hasValue(text) ? text : "",
series: currentSeries
});
}
const defaults = currentSeries._defaults;
let color = currentSeries.color;
if (isFunction(color) && defaults) {
color = defaults.color;
}
let itemLabelOptions, markerColor;
if (seriesVisible) {
itemLabelOptions = {};
markerColor = color;
} else {
itemLabelOptions = {
color: inactiveItemsLabels.color,
font: inactiveItemsLabels.font
};
markerColor = inactiveItems.markers.color;
}
if (hasValue(text) && text !== "") {
data.push({
text: text,
labels: itemLabelOptions,
markerColor: markerColor,
series: currentSeries,
active: seriesVisible
});
}
}
append(legend.data, data);
}
groupAxes(panes) {
const xAxes = [];
const yAxes = [];
for (let paneIx = 0; paneIx < panes.length; paneIx++) {
const paneAxes = panes[paneIx].axes;
for (let axisIx = 0; axisIx < paneAxes.length; axisIx++) {
const axis = paneAxes[axisIx];
if (axis.options.vertical) {
yAxes.push(axis);
} else {
xAxes.push(axis);
}
}
}
return { x: xAxes, y: yAxes, any: xAxes.concat(yAxes) };
}
groupSeriesByPane() {
const series = this.series;
const seriesByPane = {};
for (let i = 0; i < series.length; i++) {
const currentSeries = series[i];
const pane = this.seriesPaneName(currentSeries);
if (seriesByPane[pane]) {
seriesByPane[pane].push(currentSeries);
} else {
seriesByPane[pane] = [currentSeries];
}
}
return seriesByPane;
}
filterVisibleSeries(series) {
const result = [];
for (let i = 0; i < series.length; i++) {
const currentSeries = series[i];
if (currentSeries.visible !== false) {
result.push(currentSeries);
}
}
return result;
}
reflow(targetBox) {
const options = this.options.plotArea;
const panes = this.panes;
const margin = getSpacing(options.margin);
this.box = targetBox.clone().unpad(margin);
this.reflowArea(panes);
if (this.ensureLabelsFit(panes)) {
this.reflowArea(panes);
}
}
reflowArea(panes) {
this.reflowPanes();
this.detachLabels();
this.reflowAxes(panes);
this.reflowCharts(panes);
}
ensureLabelsFit(panes) {
let change = false;
panes.forEach((pane) => {
const unclipBox = pane.unclipBox();
const clipBox = pane.chartContainer._clipBox();
const padding = getSpacing(pane.options.padding || {});
if (unclipBox.y1 < clipBox.y1 + padding.top) {
change = true;
padding.top = clipBox.y1 - unclipBox.y1 + padding.top;
}
if (unclipBox.y2 > clipBox.y2 - padding.bottom) {
change = true;
padding.bottom = unclipBox.y2 - clipBox.y2 + padding.bottom;
}
if (unclipBox.x1 < clipBox.x1 + padding.left) {
change = true;
padding.left = clipBox.x1 - unclipBox.x1 + padding.left;
}
if (unclipBox.x2 > clipBox.x2 - padding.right) {
change = true;
padding.right = unclipBox.x2 - clipBox.x2 + padding.right;
}
if (change) {
pane.options.padding = padding;
}
});
return change;
}
redraw(panes) {
const panesArray = [].concat(panes);
this.initSeries();
//prevents leak during partial redraws. the cached gradients observers retain reference to the destroyed elements.
const root = this.getRoot();
if (root) {
root.cleanGradients();
}
for (let i = 0; i < panesArray.length; i++) {
this.removeCrosshairs(panesArray[i]);
panesArray[i].empty();
}
this._bindCache = new WeakMap();
this.render(panesArray);
this.detachLabels();
this.reflowAxes(this.panes);
this.reflowCharts(panesArray);
this.createCrosshairs(panesArray);
for (let i = 0; i < panesArray.length; i++) {
panesArray[i].refresh();
}
}
axisCrossingValues(axis, crossingAxes) {
const options = axis.options;
const crossingValues = [].concat(
options.axisCrossingValues || options.axisCrossingValue
);
const valuesToAdd = crossingAxes.length - crossingValues.length;
const defaultValue = crossingValues[0] || 0;
for (let i = 0; i < valuesToAdd; i++) {
crossingValues.push(defaultValue);
}
return crossingValues;
}
alignAxisTo(axis, targetAxis, crossingValue, targetCrossingValue) {
const slot = axis.getSlot(crossingValue, crossingValue, true);
const slotEdge = axis.options.reverse ? 2 : 1;
const targetSlot = targetAxis.getSlot(targetCrossingValue, targetCrossingValue, true);
const targetEdge = targetAxis.options.reverse ? 2 : 1;
const axisBox = axis.box.translate(
targetSlot[X + targetEdge] - slot[X + slotEdge],
targetSlot[Y + targetEdge] - slot[Y + slotEdge]
);
if (axis.pane !== targetAxis.pane) {
axisBox.translate(0, axis.pane.box.y1 - targetAxis.pane.box.y1);
}
axis.reflow(axisBox);
}
alignAxes(xAxes, yAxes) {
const xAnchor = xAxes[0];
const yAnchor = yAxes[0];
const xAnchorCrossings = this.axisCrossingValues(xAnchor, yAxes);
const yAnchorCrossings = this.axisCrossingValues(yAnchor, xAxes);
const leftAnchors = {};
const rightAnchors = {};
const topAnchors = {};
const bottomAnchors = {};
for (let i = 0; i < yAxes.length; i++) {
const axis = yAxes[i];
const pane = axis.pane;
const paneId = pane.id;
const visible = axis.options.visible !== false;
// Locate pane anchor, if any, and use its axisCrossingValues
const anchor = paneAnchor(xAxes, pane) || xAnchor;
let anchorCrossings = xAnchorCrossings;
if (anchor !== xAnchor) {
anchorCrossings = this.axisCrossingValues(anchor, yAxes);
}
this.alignAxisTo(axis, anchor, yAnchorCrossings[i], anchorCrossings[i]);
if (axis.options._overlap) {
continue;
}
if (round(axis.lineBox().x1) === round(anchor.lineBox().x1)) {
// Push the axis to the left the previous y-axis so they don't overlap
if (leftAnchors[paneId]) {
axis.reflow(axis.box
.alignTo(leftAnchors[paneId].box, LEFT)
.translate(-axis.options.margin, 0)
);
}
if (visible) {
leftAnchors[paneId] = axis;
}
}
if (round(axis.lineBox().x2) === round(anchor.lineBox().x2)) {
// Flip the labels on the right if we're at the right end of the pane
if (!axis._mirrored) {
axis.options.labels.mirror = !axis.options.labels.mirror;
axis._mirrored = true;
}
this.alignAxisTo(axis, anchor, yAnchorCrossings[i], anchorCrossings[i]);
// Push the axis to the right the previous y-axis so they don't overlap
if (rightAnchors[paneId]) {
axis.reflow(axis.box
.alignTo(rightAnchors[paneId].box, RIGHT)
.translate(axis.options.margin, 0)
);
}
if (visible) {
rightAnchors[paneId] = axis;
}
}
// Locate pane anchor, if any, and align the axis to it
const paneYAnchor = paneAnchor(yAxes, pane) || yAnchor;
if (paneYAnchor !== axis) {
axis.alignTo(paneYAnchor);
axis.reflow(axis.box);
}
}
for (let i = 0; i < xAxes.length; i++) {
const axis = xAxes[i];
const pane = axis.pane;
const paneId = pane.id;
const visible = axis.options.visible !== false;
// Locate pane anchor and use its axisCrossingValues
const anchor = paneAnchor(yAxes, pane) || yAnchor;
let anchorCrossings = yAnchorCrossings;
if (anchor !== yAnchor) {
anchorCrossings = this.axisCrossingValues(anchor, xAxes);
}
this.alignAxisTo(axis, anchor, xAnchorCrossings[i], anchorCrossings[i]);
if (axis.options._overlap) {
continue;
}
if (round(axis.lineBox().y1) === round(anchor.lineBox().y1)) {
// Flip the labels on top if we're at the top of the pane
if (!axis._mirrored) {
axis.options.labels.mirror = !axis.options.labels.mirror;
axis._mirrored = true;
}
this.alignAxisTo(axis, anchor, xAnchorCrossings[i], anchorCrossings[i]);
// Push the axis above the previous x-axis so they don't overlap
if (topAnchors[paneId]) {
axis.reflow(axis.box
.alignTo(topAnchors[paneId].box, TOP)
.translate(0, -axis.options.margin)
);
}
if (visible) {
topAnchors[paneId] = axis;
}
}
if (round(axis.lineBox().y2, COORD_PRECISION) === round(anchor.lineBox().y2, COORD_PRECISION)) {
// Push the axis below the previous x-axis so they don't overlap
if (bottomAnchors[paneId]) {
axis.reflow(axis.box
.alignTo(bottomAnchors[paneId].box, BOTTOM)
.translate(0, axis.options.margin)
);
}
if (visible) {
bottomAnchors[paneId] = axis;
}
}
if (i !== 0) {
axis.alignTo(xAnchor);
axis.reflow(axis.box);
}
}
}
shrinkAxisWidth(panes) {
const axes = this.groupAxes(panes).any;
const axisBox = axisGroupBox(axes);
let overflowX = 0;
for (let i = 0; i < panes.length; i++) {
const currentPane = panes[i];
if (currentPane.axes.length > 0) {
overflowX = Math.max(
overflowX,
axisBox.width() - currentPane.contentBox.width()
);
}
}
if (overflowX !== 0) {
for (let i = 0; i < axes.length; i++) {
const currentAxis = axes[i];
if (!currentAxis.options.vertical) {
currentAxis.reflow(currentAxis.box.shrink(overflowX, 0));
}
}
}
}
shrinkAxisHeight(panes) {
let shrinked;
for (let i = 0; i < panes.length; i++) {
const currentPane = panes[i];
const axes = currentPane.axes;
const overflowY = Math.max(0, axisGroupBox(axes).height() - currentPane.contentBox.height());
if (overflowY !== 0) {
for (let j = 0; j < axes.length; j++) {
const currentAxis = axes[j];
if (currentAxis.options.vertical) {
currentAxis.reflow(
currentAxis.box.shrink(0, overflowY)
);
}
}
shrinked = true;
}
}
return shrinked;
}
fitAxes(panes) {
const axes = this.groupAxes(panes).any;
let offsetX = 0;
for (let i = 0; i < panes.length; i++) {
const currentPane = panes[i];
const paneAxes = currentPane.axes;
const paneBox = currentPane.contentBox;
if (paneAxes.length > 0) {
const axisBox = axisGroupBox(paneAxes);
// OffsetY is calculated and applied per pane
const offsetY = Math.max(paneBox.y1 - axisBox.y1, paneBox.y2 - axisBox.y2);
// OffsetX is calculated and applied globally
offsetX = Math.max(offsetX, paneBox.x1 - axisBox.x1);
for (let j = 0; j < paneAxes.length; j++) {
const currentAxis = paneAxes[j];
currentAxis.reflow(
currentAxis.box.translate(0, offsetY)
);
}
}
}
for (let i = 0; i < axes.length; i++) {
const currentAxis = axes[i];
currentAxis.reflow(
currentAxis.box.translate(offsetX, 0)
);
}
}
reflowAxes(panes) {
const axes = this.groupAxes(panes);
for (let i = 0; i < panes.length; i++) {
this.reflowPaneAxes(panes[i]);
}
if (axes.x.length > 0 && axes.y.length > 0) {
this.alignAxes(axes.x, axes.y);
this.shrinkAxisWidth(panes);
this.autoRotateAxisLabels(axes);
this.alignAxes(axes.x, axes.y);
if (this.shrinkAxisWidth(panes)) {
this.alignAxes(axes.x, axes.y);
}
this.shrinkAxisHeight(panes);
this.alignAxes(axes.x, axes.y);
if (this.shrinkAxisHeight(panes)) {
this.alignAxes(axes.x, axes.y);
}
this.fitAxes(panes);
}
}
autoRotateAxisLabels(groupedAxes) {
const { panes } = this;
const axes = allPaneAxes(panes);
let rotated;
for (let idx = 0; idx < axes.length; idx++) {
const axis = axes[idx];
if (axis.autoRotateLabels()) {
rotated = true;
}
}
if (rotated) {
for (let idx = 0; idx < panes.length; idx++) {
this.reflowPaneAxes(panes[idx]);
}
if (groupedAxes.x.length > 0 && groupedAxes.y.length > 0) {
this.alignAxes(groupedAxes.x, groupedAxes.y);
this.shrinkAxisWidth(panes);
}
}
}
reflowPaneAxes(pane) {
const axes = pane.axes;
const length = axes.length;
if (length > 0) {
for (let i = 0; i < length; i++) {
axes[i].reflow(pane.contentBox);
}
}
}
reflowCharts(panes) {
const charts = this.charts;
const count = charts.length;
const box = this.box;
for (let i = 0; i < count; i++) {
const chartPane = charts[i].pane;
if (!chartPane || inArray(chartPane, panes)) {
charts[i].reflow(box);
}
}
}
reflowPanes() {
const { box, panes } = this;
const panesLength = panes.length;
let remainingHeight = box.height();
let autoHeightPanes = 0;
let top = box.y1;
for (let i = 0; i < panesLength; i++) {
const currentPane = panes[i];
const height = currentPane.options.height;
currentPane.options.width = box.width();
if (!currentPane.options.height) {
autoHeightPanes++;
} else {
if (height.indexOf && height.indexOf("%")) {
const percents = parseInt(height, 10) / 100;
currentPane.options.height = percents * box.height();
}
currentPane.reflow(box.clone());
remainingHeight -= currentPane.options.height;
}
}
for (let i = 0; i < panesLength; i++) {
const currentPane = panes[i];
if (!currentPane.options.height) {
currentPane.options.height = remainingHeight / autoHeightPanes;
}
}
for (let i = 0; i < panesLength; i++) {
const currentPane = panes[i];
const paneBox = box
.clone()
.move(box.x1, top);
currentPane.reflow(paneBox);
top += currentPane.options.height;
}
}
backgroundBox() {
const axes = this.axes;
const axesCount = axes.length;
let box;
for (let i = 0; i < axesCount; i++) {
const axisA = axes[i];
for (let j = 0; j < axesCount; j++) {
const axisB = axes[j];
if (axisA.options.vertical !== axisB.options.vertical) {
const lineBox = axisA.lineBox().clone().wrap(axisB.lineBox());
if (!box) {
box = lineBox;
} else {
box = box.wrap(lineBox);
}
}
}
}
return box || this.box;
}
chartsBoxes() {
const panes = this.panes;
const boxes = [];
for (let idx = 0; idx < panes.length; idx++) {
boxes.push(panes[idx].chartsBox());
}
return boxes;
}
addBackgroundPaths(multipath) {
const boxes = this.chartsBoxes();
for (let idx = 0; idx < boxes.length; idx++) {
multipath.paths.push(draw.Path.fromRect(boxes[idx].toRect()));
}
}
backgroundContainsPoint(point) {
const boxes = this.chartsBoxes();
for (let idx = 0; idx < boxes.length; idx++) {
if (boxes[idx].containsPoint(point)) {
return true;
}
}
}
createVisual() {
super.createVisual();
const options = this.options.plotArea;
let { opacity, background, border = {} } = options;
if (isTransparent(background)) {
background = WHITE;
opacity = 0;
}
const bg = this._bgVisual = new draw.MultiPath({
fill: {
color: background,
opacity: opacity
},
stroke: {
color: border.width ? border.color : "",
width: border.width,
dashType: border.dashType
},
zIndex: -1
});
this.addBackgroundPaths(bg);
this.appendVisual(bg);
}
pointsByCategoryIndex(categoryIndex) {
const charts = this.charts;
const result = [];
if (categoryIndex !== null) {
for (let i = 0; i < charts.length; i++) {
const chart = charts[i];
if (chart.pane.options.name === "_navigator") {
continue;
}
const points = charts[i].categoryPoints[categoryIndex];
if (points && points.length) {
for (let j = 0; j < points.length; j++) {
const point = points[j];
if (point && defined(point.value) && point.value !== null) {
result.push(point);
}
}
}
}
}
return result;
}
pointsBySeriesIndex(seriesIndex) {
return this.filterPoints(function(point) {
return point.series.index === seriesIndex;
});
}
pointsByPointIndex(pointIndex) {
return this.filterPoints(function(point) {
return point.getIndex() === pointIndex;
});
}
pointsBySeriesName(name) {
return this.filterPoints(function(point) {
return point.series.name === name;
});
}
filterPoints(callback) {
const charts = this.charts;
const result = [];
for (let i = 0; i < charts.length; i++) {
const chart = charts[i];
const points = chart.points;
for (let j = 0; j < points.length; j++) {
const point = points[j];
if (point && point.visible !== false && callback(point)) {
result.push(point);
}
}
}
return result;
}
findPoint(callback) {
const charts = this.charts;
for (let i = 0; i < charts.length; i++) {
const chart = charts[i];
const points = chart.points;
for (let j = 0; j < points.length; j++) {
const point = points[j];
if (point && point.visible !== false && callback(point)) {
return point;
}
}
}
}
paneByPoint(point) {
const panes = this.panes;
for (let i = 0; i < panes.length; i++) {
const pane = panes[i];
if (pane.box.containsPoint(point)) {
return pane;
}
}
}
detachLabels() {
const axes = this.groupAxes(this.panes);
const xAxes = axes.x;
const yAxes = axes.y;
this.detachAxisGroupLabels(yAxes, xAxes);
this.detachAxisGroupLabels(xAxes, yAxes);
}
detachAxisGroupLabels(axes, crossingAxes) {
let labelAxisCount = 0;
for (let i = 0; i < axes.length; i++) {
const axis = axes[i];
const pane = axis.pane;
const anchor = paneAnchor(crossingAxes, pane) || crossingAxes[0];
const axisIndex = i + labelAxisCount;
const labelAxis = this.createLabelAxis(axis, axisIndex, anchor);
if (labelAxis) {
labelAxisCount++;
const pos = pane.axes.indexOf(axis) + labelAxisCount;
pane.appendAxisAt(labelAxis, pos);
}
}
}
createLabelAxis(axis, axisIndex, anchor) {
const labelOptions = axis.options.labels;
const position = labelOptions.position;
const onAxis = position !== END && position !== START;
const visible = labelOptions.visible;
if (onAxis || visible === false) {
return null;
}
const allAxes = this.groupAxes(this.panes);
const crossingAxes = anchor.options.vertical ? allAxes.x : allAxes.y;
const anchorCrossings = this.axisCrossingValues(anchor, crossingAxes);
const end = position === END;
const range = anchor.range();
const edge = end ? range.max : range.min;
const crossingValue = limitValue(anchorCrossings[axisIndex], range.min, range.max);
if (crossingValue - edge === 0) {
return null;
}
anchorCrossings.splice(axisIndex + 1, 0, edge);
anchor.options.axisCrossingValues = anchorCrossings;
const labelAxis = axis.clone();
axis.clear();
labelAxis.options.name = undefined;
labelAxis.options.line.visible = false;
labelAxis.options.crosshair = undefined;
labelAxis.options.notes = undefined;
labelAxis.options.plotBands = undefined;
return labelAxis;
}
isTrendline(series) {
return series && inArray(series.type, TRENDLINE_SERIES);
}
trendlineFactory() { /* abstract */ }
createTrendlineSeries() {
const modifiedSeries = [];
this.series = this.series.map(series => {
if (!this.isTrendline(series)) {
return series;
}
const forSeries = this.seriesByName(series.for);
if (!forSeries) {
throw new Error('Invalid Configuration: Unable to locate linked series ' +
`"${series.for}" for trendline "${series.name}".`);
}
const valueFields = SeriesBinder.current.valueFields(forSeries);
const field = last(valueFields); // Use the last field for multi-field series
const trendlineSeries = this.trendlineFactory(Object.assign({}, {field}, series), forSeries);
if (trendlineSeries) {
if (forSeries.visible === false) {
trendlineSeries.visible = false;
}
if (trendlineSeries.color === INHERIT) {
trendlineSeries.color = forSeries.color;
}
modifiedSeries.push(trendlineSeries);
}
return trendlineSeries;
}).filter(series => series !== null);
return modifiedSeries;
}
seriesByName(name) {
return this.series.find(series => series.name === name);
}
getFirstPoint() {
for (let i = 0; i < this.series.length; i++) {
const points = this.pointsBySeriesIndex(i);
const point = points.find(visiblePoint);
if (point) {
return point;
}
}
}
getPointBelow(point) {
return this._getNextPoint(point, this._pointsByVertical, 1);
}
getPointAbove(point) {
return this._getNextPoint(point, this._pointsByVertical, -1);
}
getPointToTheRight(point) {
return this._getNextPoint(point, this._pointsByHorizontal, 1);
}
getPointToTheLeft(point) {
return this._getNextPoint(point, this._pointsByHorizontal, -1);
}
_getNextPoint(point, getPointsFunc, increment) {
let points = getPointsFunc.call(this, point).filter(visiblePoint);
const pointIndex = points.indexOf(point);
let nextIndex = pointIndex + increment;
const loopPoints = (direction) => {
// loop over to last non-empty collection
let result;
let offset = 0;
do {
offset += direction;
result = getPointsFunc.call(this, point, offset).filter(visiblePoint);
} while (result.length === 0);
return result;
};
if (nextIndex < 0) {
points = loopPoints(-1);
return points.at(-1);
} else if (nextIndex >= points.length) {
points = loopPoints(1);
return points.at(0);
}
return points[nextIndex];
}
_pointsByVertical(basePoint) {
return this.pointsByPointIndex(basePoint.getIndex());
}
_pointsByHorizontal(basePoint, offset = 0) {
let index = cycleIndex(basePoint.series.index + offset, this.series.length);
return this.pointsBySeriesIndex(index);
}
}
function isSingleAxis(axis) {
return !axis.pane.axes.some((a) =>
a.options.vertical === axis.options.vertical && a !== axis && a.options.visible !== false
);
}
function axisGroupBox(axes) {
const length = axes.length;
let box;
for (let i = 0; i < length; i++) {
const axis = axes[i];
const visible = axis.options.visible !== false;
if (visible || isSingleAxis(axis)) {
const axisBox = visible ? axis.contentBox() : axis.lineBox();
if (!box) {
box = axisBox.clone();
} else {
box.wrap(axisBox);
}
}
}
return box || new Box();
}
function paneAnchor(axes, pane) {
for (let i = 0; i < axes.length; i++) {
const anchor = axes[i];
if (anchor && anchor.pane === pane) {
return anchor;
}
}
}
function isTransparent(color) {
return color === "" || color === null || color === "none" || color === "transparent" || !defined(color);
}
const allPaneAxes = (panes) => panes.reduce((acc, pane) => acc.concat(pane.axes), []);
setDefaultOptions(PlotAreaBase, {
series: [],
plotArea: {
margin: {}
},
background: "",
border: {
color: BLACK,
width: 0
},
paneDefaults: {
title: {}
},
legend: {
inactiveItems: {
labels: {
color: "#919191"
},
markers: {
color: "#919191"
}
}
}
});
export default PlotAreaBase;