ag-charts-community
Version:
Advanced Charting / Charts supporting Javascript / Typescript / React / Angular / Vue
422 lines • 16.3 kB
JavaScript
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;
};
import { Selection } from "../../../scene/selection";
import { SeriesTooltip, Series } from "../series";
import { extent } from "../../../util/array";
import { LinearScale } from "../../../scale/linearScale";
import { reactive } from "../../../util/observable";
import { CartesianSeries, CartesianSeriesMarker } from "./cartesianSeries";
import { ChartAxisDirection } from "../../chartAxis";
import { getMarker } from "../../marker/util";
import { toTooltipHtml } from "../../chart";
import { ContinuousScale } from "../../../scale/continuousScale";
import { sanitizeHtml } from "../../../util/sanitize";
import { Label } from "../../label";
import { Text } from "../../../scene/shape/text";
import { HdpiCanvas } from "../../../canvas/hdpiCanvas";
import { isContinuous } from "../../../util/value";
export class ScatterSeriesTooltip extends SeriesTooltip {
}
__decorate([
reactive('change')
], ScatterSeriesTooltip.prototype, "renderer", void 0);
export class ScatterSeries extends CartesianSeries {
constructor() {
super();
this.xDomain = [];
this.yDomain = [];
this.xData = [];
this.yData = [];
this.sizeData = [];
this.sizeScale = new LinearScale();
this.nodeData = [];
this.markerSelection = Selection.select(this.pickGroup).selectAll();
this.labelData = [];
this.labelSelection = Selection.select(this.group).selectAll();
this.marker = new CartesianSeriesMarker();
this.label = new Label();
this._fill = '#c16068';
this._stroke = '#874349';
this._strokeWidth = 2;
this._fillOpacity = 1;
this._strokeOpacity = 1;
this.xKey = '';
this.yKey = '';
this.xName = '';
this.yName = '';
this.sizeName = 'Size';
this.labelName = 'Label';
this.tooltip = new ScatterSeriesTooltip();
const { marker, label } = this;
marker.addPropertyListener('shape', this.onMarkerShapeChange, this);
marker.addEventListener('change', this.scheduleUpdate, this);
this.addPropertyListener('xKey', () => this.xData = []);
this.addPropertyListener('yKey', () => this.yData = []);
this.addPropertyListener('sizeKey', () => this.sizeData = []);
label.enabled = false;
label.addEventListener('change', this.scheduleUpdate, this);
label.addEventListener('dataChange', this.scheduleData, this);
}
/**
* @deprecated Use {@link marker.fill} instead.
*/
set fill(value) {
if (this._fill !== value) {
this._fill = value;
this.scheduleUpdate();
}
}
get fill() {
return this._fill;
}
/**
* @deprecated Use {@link marker.stroke} instead.
*/
set stroke(value) {
if (this._stroke !== value) {
this._stroke = value;
this.scheduleUpdate();
}
}
get stroke() {
return this._stroke;
}
/**
* @deprecated Use {@link marker.strokeWidth} instead.
*/
set strokeWidth(value) {
if (this._strokeWidth !== value) {
this._strokeWidth = value;
this.scheduleUpdate();
}
}
get strokeWidth() {
return this._strokeWidth;
}
/**
* @deprecated Use {@link marker.fillOpacity} instead.
*/
set fillOpacity(value) {
if (this._fillOpacity !== value) {
this._fillOpacity = value;
this.scheduleUpdate();
}
}
get fillOpacity() {
return this._fillOpacity;
}
/**
* @deprecated Use {@link marker.strokeOpacity} instead.
*/
set strokeOpacity(value) {
if (this._strokeOpacity !== value) {
this._strokeOpacity = value;
this.scheduleUpdate();
}
}
get strokeOpacity() {
return this._strokeOpacity;
}
onHighlightChange() {
this.updateMarkerNodes();
}
onMarkerShapeChange() {
this.markerSelection = this.markerSelection.setData([]);
this.markerSelection.exit.remove();
this.fireEvent({ type: 'legendChange' });
}
setColors(fills, strokes) {
this.fill = fills[0];
this.stroke = strokes[0];
this.marker.fill = fills[0];
this.marker.stroke = strokes[0];
}
processData() {
const { xKey, yKey, sizeKey, labelKey, xAxis, yAxis, marker, label } = this;
if (!xAxis || !yAxis) {
return false;
}
const data = xKey && yKey && this.data ? this.data : [];
this.xData = data.map(d => d[xKey]);
this.yData = data.map(d => d[yKey]);
this.sizeData = sizeKey ? data.map(d => d[sizeKey]) : [];
const font = label.getFont();
this.labelData = labelKey ? data.map(d => {
const text = String(d[labelKey]);
const size = HdpiCanvas.getTextSize(text, font);
return Object.assign({ text }, size);
}) : [];
this.sizeScale.domain = marker.domain ? marker.domain : extent(this.sizeData, isContinuous) || [1, 1];
if (xAxis.scale instanceof ContinuousScale) {
this.xDomain = this.fixNumericExtent(extent(this.xData, isContinuous), 'x');
}
else {
this.xDomain = this.xData;
}
if (yAxis.scale instanceof ContinuousScale) {
this.yDomain = this.fixNumericExtent(extent(this.yData, isContinuous), 'y');
}
else {
this.yDomain = this.yData;
}
return true;
}
getDomain(direction) {
if (direction === ChartAxisDirection.X) {
return this.xDomain;
}
else {
return this.yDomain;
}
}
getNodeData() {
return this.nodeData;
}
getLabelData() {
return this.nodeData;
}
fireNodeClickEvent(event, datum) {
this.fireEvent({
type: 'nodeClick',
event,
series: this,
datum: datum.datum,
xKey: this.xKey,
yKey: this.yKey,
sizeKey: this.sizeKey
});
}
createNodeData() {
const { chart, data, visible, xAxis, yAxis } = this;
if (!(chart && data && visible && xAxis && yAxis) || chart.layoutPending || chart.dataPending) {
return [];
}
const xScale = xAxis.scale;
const yScale = yAxis.scale;
const isContinuousX = xScale instanceof ContinuousScale;
const isContinuousY = yScale instanceof ContinuousScale;
const xOffset = (xScale.bandwidth || 0) / 2;
const yOffset = (yScale.bandwidth || 0) / 2;
const { xData, yData, sizeData, sizeScale, marker } = this;
const nodeData = [];
sizeScale.range = [marker.size, marker.maxSize];
for (let i = 0; i < xData.length; i++) {
const xy = this.checkDomainXY(xData[i], yData[i], isContinuousX, isContinuousY);
if (!xy) {
continue;
}
const x = xScale.convert(xy[0]) + xOffset;
const y = yScale.convert(xy[1]) + yOffset;
if (!this.checkRangeXY(x, y, xAxis, yAxis)) {
continue;
}
nodeData.push({
series: this,
datum: data[i],
point: { x, y },
size: sizeData.length ? sizeScale.convert(sizeData[i]) : marker.size,
label: this.labelData[i]
});
}
return this.nodeData = nodeData;
}
update() {
this.updatePending = false;
this.updateSelections();
this.updateNodes();
}
updateSelections() {
if (!this.nodeDataPending) {
return;
}
this.nodeDataPending = false;
this.createNodeData();
this.updateMarkerSelection();
this.updateLabelSelection();
}
updateNodes() {
this.group.visible = this.visible;
this.updateMarkerNodes();
this.updateLabelNodes();
}
updateLabelSelection() {
const placedLabels = this.chart && this.chart.placeLabels().get(this) || [];
const updateLabels = this.labelSelection.setData(placedLabels);
updateLabels.exit.remove();
const enterLabels = updateLabels.enter.append(Text);
this.labelSelection = updateLabels.merge(enterLabels);
}
updateMarkerSelection() {
const MarkerShape = getMarker(this.marker.shape);
const updateMarkers = this.markerSelection.setData(this.nodeData);
updateMarkers.exit.remove();
const enterMarkers = updateMarkers.enter.append(MarkerShape);
this.markerSelection = updateMarkers.merge(enterMarkers);
}
updateLabelNodes() {
const { label } = this;
this.labelSelection.each((text, datum) => {
text.text = datum.text;
text.fill = label.color;
text.x = datum.x;
text.y = datum.y;
text.fontStyle = label.fontStyle;
text.fontWeight = label.fontWeight;
text.fontSize = label.fontSize;
text.fontFamily = label.fontFamily;
text.textAlign = 'left';
text.textBaseline = 'top';
});
}
updateMarkerNodes() {
if (!this.chart) {
return;
}
const { marker, xKey, yKey, strokeWidth, fillOpacity, strokeOpacity, fill: seriesFill, stroke: seriesStroke, chart: { highlightedDatum }, sizeScale, sizeData, highlightStyle: { fill: deprecatedFill, stroke: deprecatedStroke, strokeWidth: deprecatedStrokeWidth, item: { fill: highlightedFill = deprecatedFill, stroke: highlightedStroke = deprecatedStroke, strokeWidth: highlightedDatumStrokeWidth = deprecatedStrokeWidth, } } } = this;
const markerStrokeWidth = marker.strokeWidth !== undefined ? marker.strokeWidth : strokeWidth;
const { formatter } = marker;
sizeScale.range = [marker.size, marker.maxSize];
this.markerSelection.each((node, datum, index) => {
const isDatumHighlighted = datum === highlightedDatum;
const fill = isDatumHighlighted && highlightedFill !== undefined ? highlightedFill : marker.fill || seriesFill;
const stroke = isDatumHighlighted && highlightedStroke !== undefined ? highlightedStroke : marker.stroke || seriesStroke;
const strokeWidth = isDatumHighlighted && highlightedDatumStrokeWidth !== undefined
? highlightedDatumStrokeWidth
: this.getStrokeWidth(markerStrokeWidth, datum);
const size = sizeData.length ? sizeScale.convert(sizeData[index]) : marker.size;
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.fillOpacity = marker.fillOpacity !== undefined ? marker.fillOpacity : fillOpacity;
node.strokeOpacity = marker.strokeOpacity !== undefined ? marker.strokeOpacity : strokeOpacity;
node.translationX = datum.point.x;
node.translationY = datum.point.y;
node.opacity = this.getOpacity(datum);
node.zIndex = isDatumHighlighted ? Series.highlightedZIndex : index;
node.visible = marker.enabled && node.size > 0;
});
}
getTooltipHtml(nodeDatum) {
const { xKey, yKey, xAxis, yAxis } = this;
if (!xKey || !yKey || !xAxis || !yAxis) {
return '';
}
const { fill: seriesFill, stroke: seriesStroke, marker, tooltip, xName, yName, sizeKey, sizeName, labelKey, labelName } = this;
const fill = marker.fill || seriesFill;
const stroke = marker.stroke || seriesStroke;
const strokeWidth = this.getStrokeWidth(marker.strokeWidth || this.strokeWidth, nodeDatum);
const { formatter } = this.marker;
let format = undefined;
if (formatter) {
format = formatter({
datum: nodeDatum,
xKey,
yKey,
fill,
stroke,
strokeWidth,
size: nodeDatum.size,
highlighted: false
});
}
const color = format && format.fill || fill || 'gray';
const title = this.title || yName;
const datum = nodeDatum.datum;
const xValue = datum[xKey];
const yValue = datum[yKey];
const xString = sanitizeHtml(xAxis.formatDatum(xValue));
const yString = sanitizeHtml(yAxis.formatDatum(yValue));
let content = `<b>${sanitizeHtml(xName || xKey)}</b>: ${xString}<br>` +
`<b>${sanitizeHtml(yName || yKey)}</b>: ${yString}`;
if (sizeKey) {
content += `<br><b>${sanitizeHtml(sizeName || sizeKey)}</b>: ${sanitizeHtml(datum[sizeKey])}`;
}
if (labelKey) {
content = `<b>${sanitizeHtml(labelName || labelKey)}</b>: ${sanitizeHtml(datum[labelKey])}<br>` + content;
}
const defaults = {
title,
backgroundColor: color,
content
};
const { renderer: tooltipRenderer } = tooltip;
if (tooltipRenderer) {
return toTooltipHtml(tooltipRenderer({
datum,
xKey,
xValue,
xName,
yKey,
yValue,
yName,
sizeKey,
sizeName,
labelKey,
labelName,
title,
color
}), defaults);
}
return toTooltipHtml(defaults);
}
listSeriesItems(legendData) {
const { id, data, xKey, yKey, yName, title, visible, marker, fill, stroke, fillOpacity, strokeOpacity } = this;
if (data && data.length && xKey && yKey) {
legendData.push({
id,
itemId: undefined,
enabled: visible,
label: {
text: title || yName || yKey
},
marker: {
shape: marker.shape,
fill: marker.fill || fill || 'rgba(0, 0, 0, 0)',
stroke: marker.stroke || stroke || 'rgba(0, 0, 0, 0)',
fillOpacity: marker.fillOpacity !== undefined ? marker.fillOpacity : fillOpacity,
strokeOpacity: marker.strokeOpacity !== undefined ? marker.strokeOpacity : strokeOpacity
}
});
}
}
}
ScatterSeries.className = 'ScatterSeries';
ScatterSeries.type = 'scatter';
__decorate([
reactive('layoutChange')
], ScatterSeries.prototype, "title", void 0);
__decorate([
reactive('dataChange')
], ScatterSeries.prototype, "xKey", void 0);
__decorate([
reactive('dataChange')
], ScatterSeries.prototype, "yKey", void 0);
__decorate([
reactive('dataChange')
], ScatterSeries.prototype, "sizeKey", void 0);
__decorate([
reactive('dataChange')
], ScatterSeries.prototype, "labelKey", void 0);
//# sourceMappingURL=scatterSeries.js.map