cats-charts 0.0.98 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/cats-charts.mjs +118 -41
- package/fesm2022/cats-charts.mjs.map +1 -1
- package/index.d.ts +3 -1
- package/package.json +1 -1
package/fesm2022/cats-charts.mjs
CHANGED
|
@@ -3987,6 +3987,17 @@ class SankeyChart {
|
|
|
3987
3987
|
legendSeries = [];
|
|
3988
3988
|
previousReactiveSignature = '';
|
|
3989
3989
|
resizeObserver;
|
|
3990
|
+
stopNodeDragPropagation = (event) => {
|
|
3991
|
+
// Labels can forward interaction to their node through __hostTarget.
|
|
3992
|
+
const node = event.target?.__hostTarget ?? event.target;
|
|
3993
|
+
if (!node?.draggable)
|
|
3994
|
+
return;
|
|
3995
|
+
// ZRender maps mouse, pointer, and touch starts to mousedown. Keep its
|
|
3996
|
+
// node dragging active while blocking ancestor drag handlers and the
|
|
3997
|
+
// browser's native drag of a draggable parent element.
|
|
3998
|
+
event.event?.stopPropagation();
|
|
3999
|
+
event.event?.preventDefault();
|
|
4000
|
+
};
|
|
3990
4001
|
/* ------------------ CHANGE HANDLING ------------------ */
|
|
3991
4002
|
ngOnChanges(changes) {
|
|
3992
4003
|
if (!this.config)
|
|
@@ -4024,11 +4035,39 @@ class SankeyChart {
|
|
|
4024
4035
|
const totalValue = this.totalSourceValue(visibleLinks, visibleNodeMeta);
|
|
4025
4036
|
this.legendSeries = this.buildLegendSeries(visibleNodeMeta, visibleLinks, palette);
|
|
4026
4037
|
const selected = this.readLegendSelected();
|
|
4027
|
-
const legendEnabled = this.isLegendEnabled();
|
|
4028
|
-
const legendConfig = styling?.legend;
|
|
4029
4038
|
const nodeOpacity = this.percentToRatio(styling?.node?.opacity, 100);
|
|
4030
4039
|
const labelTypography = styling?.labels?.typography;
|
|
4031
4040
|
const fontColor = this.withOpacity(labelTypography?.reset ? undefined : labelTypography?.color, this.percentToRatio(labelTypography?.reset ? undefined : labelTypography?.opacity, 100), '#333');
|
|
4041
|
+
const label = {
|
|
4042
|
+
show: styling?.labels?.showLabels ?? true,
|
|
4043
|
+
// Keep labels within the space between Sankey columns. ECharts then
|
|
4044
|
+
// adds an ellipsis whenever a larger font, wider node, or long name
|
|
4045
|
+
// does not fit.
|
|
4046
|
+
width: this.resolveSankeyLabelWidth(visibleNodeMeta),
|
|
4047
|
+
overflow: 'truncate',
|
|
4048
|
+
ellipsis: '...',
|
|
4049
|
+
color: fontColor,
|
|
4050
|
+
fontFamily: labelTypography?.reset ? undefined : labelTypography?.fontFamily,
|
|
4051
|
+
fontSize: this.clamp(labelTypography?.reset ? undefined : labelTypography?.fontSize, 8, 40),
|
|
4052
|
+
fontWeight: labelTypography?.reset
|
|
4053
|
+
? undefined
|
|
4054
|
+
: labelTypography?.bold
|
|
4055
|
+
? 'bold'
|
|
4056
|
+
: undefined,
|
|
4057
|
+
fontStyle: labelTypography?.reset
|
|
4058
|
+
? undefined
|
|
4059
|
+
: labelTypography?.italic
|
|
4060
|
+
? 'italic'
|
|
4061
|
+
: undefined,
|
|
4062
|
+
textDecoration: labelTypography?.reset
|
|
4063
|
+
? undefined
|
|
4064
|
+
: labelTypography?.underline
|
|
4065
|
+
? 'underline'
|
|
4066
|
+
: undefined,
|
|
4067
|
+
position: this.resolveLabelPosition(),
|
|
4068
|
+
formatter: (params) => this.formatNodeLabel(String(params.name ?? ''), visibleNodeMeta.get(String(params.name ?? '')), totalValue),
|
|
4069
|
+
};
|
|
4070
|
+
const labelMargins = this.resolveSankeyLabelMargins(visibleNodes, label, visibleNodeMeta);
|
|
4032
4071
|
this.chartOption = {
|
|
4033
4072
|
backgroundColor: this.config?.backgroundColor || 'transparent',
|
|
4034
4073
|
color: palette,
|
|
@@ -4056,11 +4095,8 @@ class SankeyChart {
|
|
|
4056
4095
|
},
|
|
4057
4096
|
series: [
|
|
4058
4097
|
{
|
|
4059
|
-
left: 16,
|
|
4060
|
-
right: 16,
|
|
4061
|
-
top: legendEnabled && legendConfig?.position === 'top' ? 32 : 16,
|
|
4062
|
-
bottom: legendEnabled && legendConfig?.position === 'bottom' ? 32 : 16,
|
|
4063
4098
|
...this.config.seriesOption,
|
|
4099
|
+
...labelMargins,
|
|
4064
4100
|
type: 'sankey',
|
|
4065
4101
|
...((styling?.node?.thickness ?? this.config.seriesOption?.nodeWidth)
|
|
4066
4102
|
? {
|
|
@@ -4077,7 +4113,7 @@ class SankeyChart {
|
|
|
4077
4113
|
return {
|
|
4078
4114
|
...n,
|
|
4079
4115
|
name: n.name,
|
|
4080
|
-
label: this.mergeSingleNodeLabel(n, nodeStyle?.label),
|
|
4116
|
+
label: this.mergeSingleNodeLabel(n, nodeStyle?.label, visibleNodeMeta.get(n.name)),
|
|
4081
4117
|
itemStyle: {
|
|
4082
4118
|
color: this.resolveNodeColor(n, visibleNodeMeta.get(n.name), index, palette),
|
|
4083
4119
|
opacity: nodeOpacity,
|
|
@@ -4114,35 +4150,7 @@ class SankeyChart {
|
|
|
4114
4150
|
...(this.config.seriesOption?.lineStyle || {}),
|
|
4115
4151
|
...(styling?.lineStyle || {}),
|
|
4116
4152
|
},
|
|
4117
|
-
label
|
|
4118
|
-
show: styling?.labels?.showLabels ?? true,
|
|
4119
|
-
// Keep labels within the space between Sankey columns. ECharts then
|
|
4120
|
-
// adds an ellipsis whenever a larger font, wider node, or long name
|
|
4121
|
-
// does not fit.
|
|
4122
|
-
width: this.resolveSankeyLabelWidth(visibleNodeMeta),
|
|
4123
|
-
overflow: 'truncate',
|
|
4124
|
-
ellipsis: '...',
|
|
4125
|
-
color: fontColor,
|
|
4126
|
-
fontFamily: labelTypography?.reset ? undefined : labelTypography?.fontFamily,
|
|
4127
|
-
fontSize: this.clamp(labelTypography?.reset ? undefined : labelTypography?.fontSize, 8, 40),
|
|
4128
|
-
fontWeight: labelTypography?.reset
|
|
4129
|
-
? undefined
|
|
4130
|
-
: labelTypography?.bold
|
|
4131
|
-
? 'bold'
|
|
4132
|
-
: undefined,
|
|
4133
|
-
fontStyle: labelTypography?.reset
|
|
4134
|
-
? undefined
|
|
4135
|
-
: labelTypography?.italic
|
|
4136
|
-
? 'italic'
|
|
4137
|
-
: undefined,
|
|
4138
|
-
textDecoration: labelTypography?.reset
|
|
4139
|
-
? undefined
|
|
4140
|
-
: labelTypography?.underline
|
|
4141
|
-
? 'underline'
|
|
4142
|
-
: undefined,
|
|
4143
|
-
align: styling?.labels?.alignment === 'default' ? undefined : styling?.labels?.alignment,
|
|
4144
|
-
formatter: (params) => this.formatNodeLabel(String(params.name ?? ''), visibleNodeMeta.get(String(params.name ?? '')), totalValue),
|
|
4145
|
-
},
|
|
4153
|
+
label,
|
|
4146
4154
|
},
|
|
4147
4155
|
],
|
|
4148
4156
|
};
|
|
@@ -4349,10 +4357,14 @@ class SankeyChart {
|
|
|
4349
4357
|
label: nodeStyle.label,
|
|
4350
4358
|
};
|
|
4351
4359
|
}
|
|
4352
|
-
mergeSingleNodeLabel(node, label) {
|
|
4353
|
-
|
|
4360
|
+
mergeSingleNodeLabel(node, label, meta) {
|
|
4361
|
+
const alignment = this.config.styling?.labels?.alignment;
|
|
4362
|
+
const defaultSourcePosition = meta?.role === 'source' &&
|
|
4363
|
+
(!alignment || alignment === 'default');
|
|
4364
|
+
if (!label && !node.label && !defaultSourcePosition)
|
|
4354
4365
|
return undefined;
|
|
4355
4366
|
return {
|
|
4367
|
+
...(defaultSourcePosition ? { position: 'right' } : {}),
|
|
4356
4368
|
...(label || {}),
|
|
4357
4369
|
...(node.label || {}),
|
|
4358
4370
|
};
|
|
@@ -4410,9 +4422,70 @@ class SankeyChart {
|
|
|
4410
4422
|
const nodeWidth = this.clamp(this.config.styling?.node?.thickness ?? this.config.seriesOption?.nodeWidth, 1, 50) ?? 20;
|
|
4411
4423
|
const horizontalPadding = 32;
|
|
4412
4424
|
const availableWidth = Math.max(chartWidth - horizontalPadding - levelCount * nodeWidth, 0);
|
|
4413
|
-
//
|
|
4414
|
-
|
|
4415
|
-
|
|
4425
|
+
// Reserve room for labels at both outer edges as well as between columns.
|
|
4426
|
+
return Math.max(0, Math.min(180, Math.floor(availableWidth / Math.max(levelCount * 2 + 2, 1))));
|
|
4427
|
+
}
|
|
4428
|
+
resolveSankeyLabelMargins(nodes, label, meta) {
|
|
4429
|
+
const width = this.chartInstance?.getWidth() ?? 0;
|
|
4430
|
+
const height = this.chartInstance?.getHeight() ?? 0;
|
|
4431
|
+
const legend = this.config.styling?.legend;
|
|
4432
|
+
const margin = (value, size, fallback) => {
|
|
4433
|
+
if (typeof value === 'number')
|
|
4434
|
+
return value;
|
|
4435
|
+
if (typeof value === 'string' && value.endsWith('%')) {
|
|
4436
|
+
const percent = Number.parseFloat(value);
|
|
4437
|
+
if (Number.isFinite(percent))
|
|
4438
|
+
return (size * percent) / 100;
|
|
4439
|
+
}
|
|
4440
|
+
return fallback;
|
|
4441
|
+
};
|
|
4442
|
+
const series = this.config.seriesOption;
|
|
4443
|
+
const margins = {
|
|
4444
|
+
left: margin(series?.left, width, 16),
|
|
4445
|
+
right: margin(series?.right, width, 16),
|
|
4446
|
+
top: margin(series?.top, height, this.isLegendEnabled() && legend?.position === 'top' ? 32 : 16),
|
|
4447
|
+
bottom: margin(series?.bottom, height, this.isLegendEnabled() && legend?.position === 'bottom' ? 32 : 16),
|
|
4448
|
+
};
|
|
4449
|
+
const measured = nodes.map((node) => {
|
|
4450
|
+
const override = this.mergeSingleNodeLabel(node, this.resolveSingleNodeStyle(node.name)?.label, meta.get(node.name));
|
|
4451
|
+
const effective = {
|
|
4452
|
+
...label,
|
|
4453
|
+
...override,
|
|
4454
|
+
};
|
|
4455
|
+
const font = `${effective.fontStyle ?? 'normal'} ${effective.fontWeight ?? 'normal'} ${effective.fontSize ?? 12}px ${effective.fontFamily ?? 'sans-serif'}`;
|
|
4456
|
+
const text = typeof effective.formatter === 'function'
|
|
4457
|
+
? effective.formatter({ name: node.name, data: node })
|
|
4458
|
+
: typeof effective.formatter === 'string'
|
|
4459
|
+
? effective.formatter.replace(/\{b\}/g, node.name)
|
|
4460
|
+
: node.name;
|
|
4461
|
+
const bounds = echarts.format.getTextRect(String(text), font, undefined, undefined, effective.padding, effective.rich, false, effective.lineHeight);
|
|
4462
|
+
return { effective, bounds, customWidth: override?.width };
|
|
4463
|
+
});
|
|
4464
|
+
// Size the shared label box to the text, within the existing truncation cap.
|
|
4465
|
+
// This makes outer node positions respond to font and formatted text changes.
|
|
4466
|
+
const textWidths = measured
|
|
4467
|
+
.filter(({ effective }) => effective.show !== false)
|
|
4468
|
+
.map(({ bounds }) => bounds.width);
|
|
4469
|
+
label.width = Math.min(label.width, Math.ceil(Math.max(0, ...textWidths)));
|
|
4470
|
+
const alignment = this.config.styling?.labels?.alignment;
|
|
4471
|
+
const useLabelMargins = alignment === 'left' || alignment === 'right';
|
|
4472
|
+
for (const { effective, bounds, customWidth } of measured) {
|
|
4473
|
+
if (effective.show === false)
|
|
4474
|
+
continue;
|
|
4475
|
+
const labelWidth = typeof customWidth === 'number' ? customWidth : label.width;
|
|
4476
|
+
const distance = Number(effective.distance ?? 5);
|
|
4477
|
+
if (effective.position === 'left' || effective.position === 'right') {
|
|
4478
|
+
// Default labels face into the chart; keep the outer margins at 16px.
|
|
4479
|
+
if (useLabelMargins) {
|
|
4480
|
+
const side = effective.position;
|
|
4481
|
+
margins[side] = Math.max(margins[side], Math.ceil(labelWidth + distance + 8));
|
|
4482
|
+
}
|
|
4483
|
+
// Split labels and large fonts also need room above and below nodes.
|
|
4484
|
+
margins.top = Math.max(margins.top, Math.ceil(bounds.height / 2 + 8));
|
|
4485
|
+
margins.bottom = Math.max(margins.bottom, Math.ceil(bounds.height / 2 + 8));
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
return margins;
|
|
4416
4489
|
}
|
|
4417
4490
|
totalSourceValue(links, meta) {
|
|
4418
4491
|
const sourceNames = new Set([...meta.values()].filter((node) => node.role === 'source').map((node) => node.name));
|
|
@@ -4558,6 +4631,9 @@ class SankeyChart {
|
|
|
4558
4631
|
if (!this.chartInstance)
|
|
4559
4632
|
return;
|
|
4560
4633
|
this.chartInstance.off();
|
|
4634
|
+
const zr = this.chartInstance.getZr();
|
|
4635
|
+
zr.off('mousedown', this.stopNodeDragPropagation);
|
|
4636
|
+
zr.on('mousedown', this.stopNodeDragPropagation);
|
|
4561
4637
|
this.bindSankeyDragBounds();
|
|
4562
4638
|
this.events.forEach((eventName) => {
|
|
4563
4639
|
if (eventName === 'contextmenu') {
|
|
@@ -4647,6 +4723,7 @@ class SankeyChart {
|
|
|
4647
4723
|
ngOnDestroy() {
|
|
4648
4724
|
this.resizeObserver?.disconnect();
|
|
4649
4725
|
if (this.chartInstance) {
|
|
4726
|
+
this.chartInstance.getZr().off('mousedown', this.stopNodeDragPropagation);
|
|
4650
4727
|
this.chartInstance.off();
|
|
4651
4728
|
this.chartInstance.dispose();
|
|
4652
4729
|
}
|