@progress/kendo-charts 2.2.0-dev.202401301155 → 2.2.0-dev.202402121742

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.
@@ -1,14 +1,15 @@
1
1
  import { drawing } from '@progress/kendo-drawing';
2
- import { deepExtend, addClass, setDefaultOptions, getSpacing } from '../common';
2
+ import { deepExtend, addClass, setDefaultOptions } from '../common';
3
3
  import { calculateSankey } from './calculation';
4
4
  import { Node, resolveNodeOptions } from './node';
5
5
  import { Link, resolveLinkOptions } from './link';
6
6
  import { Label, resolveLabelOptions } from './label';
7
7
  import { Title } from './title';
8
- import { CENTER, LEFT, TOP } from '../common/constants';
8
+ import { BOTTOM, LEFT, RIGHT, TOP } from '../common/constants';
9
9
  import Box from '../core/box';
10
10
  import rectToBox from '../core/utils/rect-to-box';
11
11
  import { Observable } from '../common/observable';
12
+ import { Legend } from './legend';
12
13
 
13
14
  const LINK = 'link';
14
15
  const NODE = 'node';
@@ -97,6 +98,7 @@ export class Sankey extends Observable {
97
98
  const element = ev.element;
98
99
  const isLink = element.type === LINK;
99
100
  const isNode = element.type === NODE;
101
+ const isLegendItem = Boolean(element.chartElement && element.chartElement.options.node);
100
102
 
101
103
  if ((isLink && this.trigger('linkEnter', ev)) ||
102
104
  (isNode && this.trigger('nodeEnter', ev))) {
@@ -108,10 +110,10 @@ export class Sankey extends Observable {
108
110
  this.setLinksOpacity(highlight.inactiveOpacity);
109
111
  this.setOpacity(element, highlight.opacity);
110
112
  } else if (isNode) {
111
- this.setLinksOpacity(highlight.inactiveOpacity);
112
- element.links.forEach(link => {
113
- this.setOpacity(link, highlight.opacity);
114
- });
113
+ this.highlightLinks(element, highlight);
114
+ } else if (isLegendItem) {
115
+ const nodeVisual = this.nodesVisuals.get(element.chartElement.options.node.id);
116
+ this.highlightLinks(nodeVisual, highlight);
115
117
  }
116
118
  }
117
119
 
@@ -119,6 +121,7 @@ export class Sankey extends Observable {
119
121
  const element = ev.element;
120
122
  const isLink = element.type === LINK;
121
123
  const isNode = element.type === NODE;
124
+ const isLegendItem = Boolean(element.chartElement && element.chartElement.options.node);
122
125
  const target = ev.originalEvent.relatedTarget;
123
126
 
124
127
  if (isLink && target && target.nodeName === 'text') {
@@ -130,11 +133,20 @@ export class Sankey extends Observable {
130
133
  return;
131
134
  }
132
135
 
133
- if (isLink || isNode) {
136
+ if (isLink || isNode || isLegendItem) {
134
137
  this.setLinksOpacity(this.options.links.opacity);
135
138
  }
136
139
  }
137
140
 
141
+ highlightLinks(node, highlight) {
142
+ if (node) {
143
+ this.setLinksOpacity(highlight.inactiveOpacity);
144
+ node.links.forEach(link => {
145
+ this.setOpacity(link, highlight.opacity);
146
+ });
147
+ }
148
+ }
149
+
138
150
  _destroySurface() {
139
151
  if (this.surface) {
140
152
  this.surface.destroy();
@@ -191,22 +203,68 @@ export class Sankey extends Observable {
191
203
  this.visual = this._render();
192
204
  }
193
205
 
194
- titleHeight(title, totalWidth) {
195
- if (!title.visible || !title.text) {
196
- return 0;
206
+ titleBox(title, drawingRect) {
207
+ if (!title || title.visible === false || !title.text) {
208
+ return null;
197
209
  }
198
210
 
199
- const titleElement = new Title(deepExtend({}, { totalWidth }, title));
211
+ const titleElement = new Title(Object.assign({}, {drawingRect}, title));
200
212
  const titleVisual = titleElement.exportVisual();
201
- return titleVisual.chartElement.box.height();
213
+ return titleVisual.chartElement.box;
202
214
  }
203
215
 
204
- calculateSankey(options) {
205
- const title = this.options.title;
206
- const titleHeight = this.titleHeight(title, options.width);
216
+ legendBox(options, nodes, drawingRect) {
217
+ if (!options || options.visible === false) {
218
+ return null;
219
+ }
207
220
 
221
+ const legend = new Legend(Object.assign({}, {nodes}, options, {drawingRect}));
222
+ const legendVisual = legend.exportVisual();
223
+
224
+ return legendVisual.chartElement.box;
225
+ }
226
+
227
+ calculateSankey(options) {
228
+ const { title, legend, data } = this.options;
208
229
  const { nodes, labels, nodesColors } = this.options;
209
- const calculatedNodes = calculateSankey(Object.assign({}, options, {offsetX: 0, offsetY: titleHeight})).nodes;
230
+
231
+ const sankeyBox = new Box(0, 0, options.width, options.height);
232
+ const titleBox = this.titleBox(title, sankeyBox);
233
+
234
+ let legendArea = sankeyBox.clone();
235
+
236
+ if (titleBox) {
237
+ const titleHeight = titleBox.height();
238
+ if (title.position === TOP) {
239
+ sankeyBox.unpad({ top: titleHeight });
240
+ legendArea = new Box(0, titleHeight, options.width, options.height);
241
+ } else {
242
+ sankeyBox.shrink(0, titleHeight);
243
+ legendArea = new Box(0, 0, options.width, options.height - titleHeight);
244
+ }
245
+ }
246
+
247
+ const legendBox = this.legendBox(legend, data.nodes, legendArea);
248
+
249
+ if (legendBox) {
250
+ if (legend.position === LEFT) {
251
+ sankeyBox.unpad({ left: legendBox.width() });
252
+ }
253
+
254
+ if (legend.position === RIGHT) {
255
+ sankeyBox.shrink(legendBox.width(), 0);
256
+ }
257
+
258
+ if (legend.position === TOP) {
259
+ sankeyBox.unpad({ top: legendBox.height() });
260
+ }
261
+
262
+ if (legend.position === BOTTOM) {
263
+ sankeyBox.shrink(0, legendBox.height());
264
+ }
265
+ }
266
+
267
+ const calculatedNodes = calculateSankey(Object.assign({}, options, {offsetX: sankeyBox.x1, offsetY: sankeyBox.y1, width: sankeyBox.x2, height: sankeyBox.y2})).nodes;
210
268
  const box = new Box();
211
269
 
212
270
  calculatedNodes.forEach((nodeEl, i) => {
@@ -221,32 +279,40 @@ export class Sankey extends Observable {
221
279
  }
222
280
  });
223
281
 
224
- const offsetX = box.x1 < 0 ? -box.x1 : 0;
225
- let offsetY = box.y1 < 0 ? -box.y1 : 0;
226
-
227
- const width = box.width() > options.width ? offsetX + options.width - (box.width() - options.width) : options.width;
228
- let height = box.height() > options.height ? offsetY + options.height - (box.height() - options.height) : options.height;
282
+ let offsetX = (box.x1 < 0 ? -box.x1 : 0) + sankeyBox.x1;
283
+ let offsetY = (box.y1 < 0 ? -box.y1 : 0) + sankeyBox.y1;
229
284
 
230
- height -= titleHeight;
231
- offsetY += title.position === TOP ? titleHeight : 0;
285
+ let width = box.width() > sankeyBox.x2 ? offsetX + sankeyBox.x2 - (box.width() - sankeyBox.x2) : sankeyBox.x2;
286
+ let height = box.height() > sankeyBox.y2 ? offsetY + sankeyBox.y2 - (box.height() - sankeyBox.y2) : sankeyBox.y2;
232
287
 
233
- return calculateSankey(Object.assign({}, options, {width, height, offsetX, offsetY}));
288
+ return {
289
+ sankey: calculateSankey(Object.assign({}, options, {offsetX, offsetY, width, height})),
290
+ legendBox,
291
+ titleBox
292
+ };
234
293
  }
235
294
 
236
295
  _render() {
237
- const { data, labels: labelOptions, nodes: nodesOptions, links: linkOptions, nodesColors, title } = this.options;
296
+ const { data, labels: labelOptions, nodes: nodesOptions, links: linkOptions, nodesColors, title, legend } = this.options;
238
297
  const { width, height } = this.size;
239
- const calcOptions = Object.assign({}, data, {width, height, nodesOptions, title});
240
- let { nodes, links } = this.calculateSankey(calcOptions);
298
+ const calcOptions = Object.assign({}, data, {width, height, nodesOptions, title, legend});
299
+ const { sankey, titleBox, legendBox } = this.calculateSankey(calcOptions);
300
+ const { nodes, links } = sankey;
241
301
 
242
302
  const visual = new drawing.Group();
243
303
 
244
- if (title.visible && title.text) {
245
- const titleElement = new Title(deepExtend({}, { wrapWidth: width, wrapHeight: height }, title));
304
+ if (titleBox) {
305
+ const titleElement = new Title(Object.assign({}, title, {drawingRect: titleBox}));
246
306
  const titleVisual = titleElement.exportVisual();
247
307
  visual.append(titleVisual);
248
308
  }
249
309
 
310
+ if (legendBox) {
311
+ const legendElement = new Legend(Object.assign({}, legend, {drawingRect: legendBox, nodes, colors: nodesColors}));
312
+ const legendVisual = legendElement.exportVisual();
313
+ visual.append(legendVisual);
314
+ }
315
+
250
316
  const visualNodes = new Map();
251
317
 
252
318
  nodes.forEach((node, i) => {
@@ -261,11 +327,11 @@ export class Sankey extends Observable {
261
327
  visual.append(nodeVisual);
262
328
  });
263
329
 
264
- links = links.slice().sort((a, b) => b.value - a.value);
330
+ const sortedLinks = links.slice().sort((a, b) => b.value - a.value);
265
331
 
266
332
  const linksVisuals = [];
267
333
 
268
- links.forEach(link => {
334
+ sortedLinks.forEach(link => {
269
335
  const { source, target } = link;
270
336
  const sourceNode = visualNodes.get(source.id);
271
337
  const targetNode = visualNodes.get(target.id);
@@ -283,6 +349,7 @@ export class Sankey extends Observable {
283
349
  });
284
350
 
285
351
  this.linksVisuals = linksVisuals;
352
+ this.nodesVisuals = visualNodes;
286
353
 
287
354
  nodes.forEach((node) => {
288
355
  const textOps = resolveLabelOptions(node, labelOptions, width);
@@ -334,16 +401,5 @@ setDefaultOptions(Sankey, {
334
401
  opacity: 0.8,
335
402
  inactiveOpacity: 0.2
336
403
  }
337
- },
338
- title: {
339
- visible: true,
340
- position: TOP,
341
- align: CENTER,
342
- opacity: 1,
343
- border: {
344
- width: 0
345
- },
346
- margin: getSpacing(5),
347
- padding: getSpacing(5)
348
- },
404
+ }
349
405
  });
@@ -1,28 +1,20 @@
1
+ import { Title as ChartTitle } from "../core";
1
2
  import { SankeyElement } from "./element";
2
- import { Title as CoreTitle } from "../core";
3
- import Box from "../core/box";
4
- import { deepExtend } from "../common";
5
- import { BOTTOM } from "../common/constants";
6
- import { getSpacing } from "../common";
3
+ import { setDefaultOptions, getSpacing } from '../common';
4
+ import { CENTER, TOP } from "../common/constants";
7
5
 
8
6
  export class Title extends SankeyElement {
9
7
  getElement() {
10
- const options = deepExtend({}, this.options, this.options.title);
11
- const { wrapWidth, wrapHeight, text, border } = options;
12
- const padding = getSpacing(options.padding);
13
- const margin = getSpacing(options.margin);
8
+ const options = this.options;
9
+ const { drawingRect, text } = options;
14
10
 
15
- if (!options.visible || !text) {
11
+ if (options.visible === false || !text) {
16
12
  return null;
17
13
  }
18
14
 
19
- const title = CoreTitle.buildTitle(text, options);
20
- const titleSizeBox = title.children[0].container.box;
21
- const totalTitleHeight = margin.top + border.width + padding.top + titleSizeBox.height() + padding.bottom + border.width + margin.bottom;
22
- const offsetTop = options.position === BOTTOM ? wrapHeight - totalTitleHeight : 0;
23
- const titleRect = new Box(0, offsetTop, wrapWidth, offsetTop + totalTitleHeight);
15
+ const title = ChartTitle.buildTitle(text, options);
24
16
 
25
- title.reflow(titleRect);
17
+ title.reflow(drawingRect);
26
18
 
27
19
  title.renderVisual();
28
20
  return title.visual;
@@ -32,3 +24,14 @@ export class Title extends SankeyElement {
32
24
  return this.getElement();
33
25
  }
34
26
  }
27
+
28
+ setDefaultOptions(Title, {
29
+ position: TOP, // 'top', 'bottom'
30
+ align: CENTER, // 'left', 'right', 'center'
31
+ opacity: 1,
32
+ border: {
33
+ width: 0
34
+ },
35
+ margin: getSpacing(5),
36
+ padding: getSpacing(5)
37
+ });