ag-charts-community
Version:
Advanced Charting / Charts supporting Javascript / Typescript / React / Angular / Vue
458 lines • 18 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("../../../scene/shape/path");
const continuousScale_1 = require("../../../scale/continuousScale");
const selection_1 = require("../../../scene/selection");
const group_1 = require("../../../scene/group");
const series_1 = require("../series");
const array_1 = require("../../../util/array");
const node_1 = require("../../../scene/node");
const text_1 = require("../../../scene/shape/text");
const cartesianSeries_1 = require("./cartesianSeries");
const chartAxis_1 = require("../../chartAxis");
const util_1 = require("../../marker/util");
const observable_1 = require("../../../util/observable");
const chart_1 = require("../../chart");
const string_1 = require("../../../util/string");
const label_1 = require("../../label");
const sanitize_1 = require("../../../util/sanitize");
const value_1 = require("../../../util/value");
class LineSeriesLabel extends label_1.Label {
}
__decorate([
observable_1.reactive('change')
], LineSeriesLabel.prototype, "formatter", void 0);
class LineSeriesTooltip extends series_1.SeriesTooltip {
}
__decorate([
observable_1.reactive('change')
], LineSeriesTooltip.prototype, "renderer", void 0);
__decorate([
observable_1.reactive('change')
], LineSeriesTooltip.prototype, "format", void 0);
exports.LineSeriesTooltip = LineSeriesTooltip;
class LineSeries extends cartesianSeries_1.CartesianSeries {
constructor() {
super();
this.xDomain = [];
this.yDomain = [];
this.xData = [];
this.yData = [];
this.lineNode = new path_1.Path();
// We use groups for this selection even though each group only contains a marker ATM
// because in the future we might want to add label support as well.
this.nodeSelection = selection_1.Selection.select(this.pickGroup).selectAll();
this.nodeData = [];
this.marker = new cartesianSeries_1.CartesianSeriesMarker();
this.label = new LineSeriesLabel();
this.stroke = '#874349';
this.lineDash = [0];
this.lineDashOffset = 0;
this.strokeWidth = 2;
this.strokeOpacity = 1;
this.tooltip = new LineSeriesTooltip();
this._xKey = '';
this.xName = '';
this._yKey = '';
this.yName = '';
const lineNode = this.lineNode;
lineNode.fill = undefined;
lineNode.lineJoin = 'round';
lineNode.pointerEvents = node_1.PointerEvents.None;
// Make line render before markers in the pick group.
this.group.insertBefore(lineNode, this.pickGroup);
this.addEventListener('update', this.scheduleUpdate);
const { marker, label } = this;
marker.fill = '#c16068';
marker.stroke = '#874349';
marker.addPropertyListener('shape', this.onMarkerShapeChange, this);
marker.addEventListener('change', this.scheduleUpdate, this);
label.enabled = false;
label.addEventListener('change', this.scheduleUpdate, this);
}
onMarkerShapeChange() {
this.nodeSelection = this.nodeSelection.setData([]);
this.nodeSelection.exit.remove();
this.fireEvent({ type: 'legendChange' });
}
setColors(fills, strokes) {
this.stroke = fills[0];
this.marker.stroke = strokes[0];
this.marker.fill = fills[0];
}
set xKey(value) {
if (this._xKey !== value) {
this._xKey = value;
this.xData = [];
this.scheduleData();
}
}
get xKey() {
return this._xKey;
}
set yKey(value) {
if (this._yKey !== value) {
this._yKey = value;
this.yData = [];
this.scheduleData();
}
}
get yKey() {
return this._yKey;
}
processData() {
const { xAxis, yAxis, xKey, yKey, xData, yData } = this;
const data = xKey && yKey && this.data ? this.data : [];
if (!xAxis || !yAxis) {
return false;
}
const isContinuousX = xAxis.scale instanceof continuousScale_1.ContinuousScale;
const isContinuousY = yAxis.scale instanceof continuousScale_1.ContinuousScale;
xData.length = 0;
yData.length = 0;
for (let i = 0, n = data.length; i < n; i++) {
const datum = data[i];
const x = datum[xKey];
const y = datum[yKey];
if (isContinuousX) {
xData.push(x);
}
else {
// i.e. category axis
xData.push(value_1.isDiscrete(x) ? x : String(x));
}
if (isContinuousY) {
yData.push(y);
}
else {
yData.push(value_1.isDiscrete(y) ? y : String(y));
}
}
this.xDomain = isContinuousX ? this.fixNumericExtent(array_1.extent(xData, value_1.isContinuous), 'x') : xData;
this.yDomain = isContinuousY ? this.fixNumericExtent(array_1.extent(yData, value_1.isContinuous), 'y') : yData;
return true;
}
getDomain(direction) {
if (direction === chartAxis_1.ChartAxisDirection.X) {
return this.xDomain;
}
return this.yDomain;
}
onHighlightChange() {
this.updateNodes();
}
resetHighlight() {
this.lineNode.strokeWidth = this.strokeWidth;
}
update() {
this.updatePending = false;
this.updateSelections();
this.updateNodes();
}
updateSelections() {
if (!this.nodeDataPending) {
return;
}
this.nodeDataPending = false;
this.updateLinePath(); // this will create node data too
this.updateNodeSelection();
}
updateLinePath() {
const { data, xAxis, yAxis } = this;
if (!data || !xAxis || !yAxis) {
return;
}
const { xData, yData, lineNode, label } = this;
const xScale = xAxis.scale;
const yScale = yAxis.scale;
const isContinuousX = xScale instanceof continuousScale_1.ContinuousScale;
const isContinuousY = yScale instanceof continuousScale_1.ContinuousScale;
const xOffset = (xScale.bandwidth || 0) / 2;
const yOffset = (yScale.bandwidth || 0) / 2;
const linePath = lineNode.path;
const nodeData = [];
linePath.clear();
let moveTo = true;
let prevXInRange = undefined;
let nextXYDatums = undefined;
for (let i = 0; i < xData.length; i++) {
const xyDatums = nextXYDatums || this.checkDomainXY(xData[i], yData[i], isContinuousX, isContinuousY);
if (!xyDatums) {
prevXInRange = undefined;
moveTo = true;
}
else {
const [xDatum, yDatum] = xyDatums;
const x = xScale.convert(xDatum) + xOffset;
if (isNaN(x)) {
prevXInRange = undefined;
moveTo = true;
continue;
}
const tolerance = (xScale.bandwidth || (this.marker.size * 0.5 + (this.marker.strokeWidth || 0))) + 1;
nextXYDatums = this.checkDomainXY(xData[i + 1], yData[i + 1], isContinuousX, isContinuousY);
const xInRange = xAxis.inRangeEx(x, 0, tolerance);
const nextXInRange = nextXYDatums && xAxis.inRangeEx(xScale.convert(nextXYDatums[0]) + xOffset, 0, tolerance);
if (xInRange === -1 && nextXInRange === -1) {
moveTo = true;
continue;
}
if (xInRange === 1 && prevXInRange === 1) {
moveTo = true;
continue;
}
prevXInRange = xInRange;
const y = yScale.convert(yDatum) + yOffset;
if (moveTo) {
linePath.moveTo(x, y);
moveTo = false;
}
else {
linePath.lineTo(x, y);
}
let labelText;
if (label.formatter) {
labelText = label.formatter({ value: yDatum });
}
else {
labelText = typeof yDatum === 'number' && isFinite(yDatum) ? yDatum.toFixed(2) : yDatum ? String(yDatum) : '';
}
nodeData.push({
series: this,
datum: data[i],
point: { x, y },
label: labelText ? {
text: labelText,
fontStyle: label.fontStyle,
fontWeight: label.fontWeight,
fontSize: label.fontSize,
fontFamily: label.fontFamily,
textAlign: 'center',
textBaseline: 'bottom',
fill: label.color
} : undefined
});
}
}
// Used by marker nodes and for hit-testing even when not using markers
// when `chart.tooltip.tracking` is true.
this.nodeData = nodeData;
}
updateNodeSelection() {
const { marker } = this;
const nodeData = marker.shape ? this.nodeData : [];
const MarkerShape = util_1.getMarker(marker.shape);
const updateSelection = this.nodeSelection.setData(nodeData);
updateSelection.exit.remove();
const enterSelection = updateSelection.enter.append(group_1.Group);
enterSelection.append(MarkerShape);
enterSelection.append(text_1.Text);
this.nodeSelection = updateSelection.merge(enterSelection);
}
updateNodes() {
this.group.visible = this.visible;
this.updateLineNode();
this.updateMarkerNodes();
this.updateTextNodes();
}
updateLineNode() {
const { lineNode } = this;
lineNode.stroke = this.stroke;
lineNode.strokeWidth = this.getStrokeWidth(this.strokeWidth);
lineNode.strokeOpacity = this.strokeOpacity;
lineNode.lineDash = this.lineDash;
lineNode.lineDashOffset = this.lineDashOffset;
lineNode.opacity = this.getOpacity();
}
updateMarkerNodes() {
if (!this.chart) {
return;
}
const { marker, xKey, yKey, stroke: lineStroke, chart: { highlightedDatum }, highlightStyle: { fill: deprecatedFill, stroke: deprecatedStroke, strokeWidth: deprecatedStrokeWidth, item: { fill: highlightedFill = deprecatedFill, stroke: highlightedStroke = deprecatedStroke, strokeWidth: highlightedDatumStrokeWidth = deprecatedStrokeWidth, } } } = this;
const { size, formatter } = marker;
const markerStrokeWidth = marker.strokeWidth !== undefined ? marker.strokeWidth : this.strokeWidth;
const MarkerShape = util_1.getMarker(marker.shape);
this.nodeSelection.selectByClass(MarkerShape)
.each((node, datum) => {
const isDatumHighlighted = datum === highlightedDatum;
const fill = isDatumHighlighted && highlightedFill !== undefined ? highlightedFill : marker.fill;
const stroke = isDatumHighlighted && highlightedStroke !== undefined ? highlightedStroke : marker.stroke || lineStroke;
const strokeWidth = isDatumHighlighted && highlightedDatumStrokeWidth !== undefined
? highlightedDatumStrokeWidth
: markerStrokeWidth;
let format = undefined;
if (formatter) {
format = formatter({
datum: datum.datum,
xKey,
yKey,
fill,
stroke,
strokeWidth,
size,
highlighted: isDatumHighlighted
});
}
node.fill = format && format.fill || fill;
node.stroke = format && format.stroke || stroke;
node.strokeWidth = format && format.strokeWidth !== undefined
? format.strokeWidth
: strokeWidth;
node.size = format && format.size !== undefined
? format.size
: size;
node.translationX = datum.point.x;
node.translationY = datum.point.y;
node.opacity = this.getOpacity(datum);
node.visible = marker.enabled && node.size > 0;
});
}
updateTextNodes() {
this.nodeSelection.selectByClass(text_1.Text)
.each((text, datum) => {
const { point, label } = datum;
const { enabled: labelEnabled, fontStyle, fontWeight, fontSize, fontFamily, color } = this.label;
if (label && labelEnabled) {
text.fontStyle = fontStyle;
text.fontWeight = fontWeight;
text.fontSize = fontSize;
text.fontFamily = fontFamily;
text.textAlign = label.textAlign;
text.textBaseline = label.textBaseline;
text.text = label.text;
text.x = point.x;
text.y = point.y - 10;
text.fill = color;
text.visible = true;
}
else {
text.visible = false;
}
});
}
getNodeData() {
return this.nodeData;
}
fireNodeClickEvent(event, datum) {
this.fireEvent({
type: 'nodeClick',
event,
series: this,
datum: datum.datum,
xKey: this.xKey,
yKey: this.yKey
});
}
getTooltipHtml(nodeDatum) {
const { xKey, yKey, xAxis, yAxis } = this;
if (!xKey || !yKey || !xAxis || !yAxis) {
return '';
}
const { xName, yName, tooltip, marker } = this;
const { renderer: tooltipRenderer, format: tooltipFormat } = tooltip;
const datum = nodeDatum.datum;
const xValue = datum[xKey];
const yValue = datum[yKey];
const xString = xAxis.formatDatum(xValue);
const yString = yAxis.formatDatum(yValue);
const title = sanitize_1.sanitizeHtml(this.title || yName);
const content = sanitize_1.sanitizeHtml(xString + ': ' + yString);
const { formatter: markerFormatter, fill, stroke, strokeWidth: markerStrokeWidth, size } = marker;
const strokeWidth = markerStrokeWidth !== undefined ? markerStrokeWidth : this.strokeWidth;
let format = undefined;
if (markerFormatter) {
format = markerFormatter({
datum,
xKey,
yKey,
fill,
stroke,
strokeWidth,
size,
highlighted: false
});
}
const color = format && format.fill || fill;
const defaults = {
title,
backgroundColor: color,
content
};
if (tooltipFormat || tooltipRenderer) {
const params = {
datum,
xKey,
xValue,
xName,
yKey,
yValue,
yName,
title,
color
};
if (tooltipFormat) {
return chart_1.toTooltipHtml({
content: string_1.interpolate(tooltipFormat, params)
}, defaults);
}
if (tooltipRenderer) {
return chart_1.toTooltipHtml(tooltipRenderer(params), defaults);
}
}
return chart_1.toTooltipHtml(defaults);
}
listSeriesItems(legendData) {
const { id, data, xKey, yKey, yName, visible, title, marker, stroke, strokeOpacity } = this;
if (data && data.length && xKey && yKey) {
legendData.push({
id: id,
itemId: undefined,
enabled: visible,
label: {
text: title || yName || yKey
},
marker: {
shape: marker.shape,
fill: marker.fill || 'rgba(0, 0, 0, 0)',
stroke: marker.stroke || stroke || 'rgba(0, 0, 0, 0)',
fillOpacity: 1,
strokeOpacity
}
});
}
}
}
LineSeries.className = 'LineSeries';
LineSeries.type = 'line';
__decorate([
observable_1.reactive('layoutChange')
], LineSeries.prototype, "title", void 0);
__decorate([
observable_1.reactive('update')
], LineSeries.prototype, "stroke", void 0);
__decorate([
observable_1.reactive('update')
], LineSeries.prototype, "lineDash", void 0);
__decorate([
observable_1.reactive('update')
], LineSeries.prototype, "lineDashOffset", void 0);
__decorate([
observable_1.reactive('update')
], LineSeries.prototype, "strokeWidth", void 0);
__decorate([
observable_1.reactive('update')
], LineSeries.prototype, "strokeOpacity", void 0);
__decorate([
observable_1.reactive('update')
], LineSeries.prototype, "xName", void 0);
__decorate([
observable_1.reactive('update')
], LineSeries.prototype, "yName", void 0);
exports.LineSeries = LineSeries;
//# sourceMappingURL=lineSeries.js.map