@progress/kendo-charts 2.0.0 → 2.1.0-dev.202401181402

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.
Files changed (39) hide show
  1. package/dist/cdn/js/kendo-charts.js +1 -1
  2. package/dist/cdn/main.js +1 -1
  3. package/dist/es/{map/scroller → common}/observable.js +1 -1
  4. package/dist/es/main.js +1 -0
  5. package/dist/es/map/attribution.js +1 -1
  6. package/dist/es/map/map.js +1 -1
  7. package/dist/es/map/navigator.js +1 -1
  8. package/dist/es/map/scroller/draggable.js +1 -1
  9. package/dist/es/map/scroller/scroller.js +1 -1
  10. package/dist/es/map/scroller/user-events.js +1 -1
  11. package/dist/es/map/zoom.js +1 -1
  12. package/dist/es/sankey/calculation.js +176 -0
  13. package/dist/es/sankey/element.js +49 -0
  14. package/dist/es/sankey/label.js +83 -0
  15. package/dist/es/sankey/link.js +62 -0
  16. package/dist/es/sankey/node.js +50 -0
  17. package/dist/es/sankey/sankey.js +345 -0
  18. package/dist/es/sankey.js +1 -0
  19. package/dist/es2015/{map/scroller → common}/observable.js +1 -1
  20. package/dist/es2015/main.js +1 -0
  21. package/dist/es2015/map/attribution.js +1 -1
  22. package/dist/es2015/map/map.js +1 -1
  23. package/dist/es2015/map/navigator.js +1 -1
  24. package/dist/es2015/map/scroller/draggable.js +1 -1
  25. package/dist/es2015/map/scroller/scroller.js +1 -1
  26. package/dist/es2015/map/scroller/user-events.js +1 -1
  27. package/dist/es2015/map/zoom.js +1 -1
  28. package/dist/es2015/sankey/calculation.js +170 -0
  29. package/dist/es2015/sankey/element.js +41 -0
  30. package/dist/es2015/sankey/label.js +69 -0
  31. package/dist/es2015/sankey/link.js +49 -0
  32. package/dist/es2015/sankey/node.js +40 -0
  33. package/dist/es2015/sankey/sankey.js +315 -0
  34. package/dist/es2015/sankey.js +1 -0
  35. package/dist/npm/main.d.ts +1 -0
  36. package/dist/npm/main.js +743 -1
  37. package/dist/npm/sankey.d.ts +244 -0
  38. package/dist/systemjs/kendo-charts.js +1 -1
  39. package/package.json +1 -1
@@ -0,0 +1,345 @@
1
+ import { drawing } from '@progress/kendo-drawing';
2
+ import { deepExtend, addClass, setDefaultOptions } from '../common';
3
+ import { calculateSankey } from './calculation';
4
+ import { Node, resolveNodeOptions } from './node';
5
+ import { Link, resolveLinkOptions } from './link';
6
+ import { Label, resolveLabelOptions } from './label';
7
+ import { LEFT } from '../common/constants';
8
+ import Box from '../core/box';
9
+ import rectToBox from '../core/utils/rect-to-box';
10
+ import { Observable } from '../common/observable';
11
+
12
+ var LINK = 'link';
13
+ var NODE = 'node';
14
+
15
+ export var Sankey = (function (Observable) {
16
+ function Sankey(element, options, theme) {
17
+ Observable.call(this);
18
+
19
+ this._initElement(element);
20
+ this._initTheme(theme);
21
+ this._setOptions(options);
22
+ this._initSurface();
23
+
24
+ if (options && options.data) {
25
+ this._redraw();
26
+ }
27
+
28
+ this._initResizeObserver();
29
+ }
30
+
31
+ if ( Observable ) Sankey.__proto__ = Observable;
32
+ Sankey.prototype = Object.create( Observable && Observable.prototype );
33
+ Sankey.prototype.constructor = Sankey;
34
+
35
+ Sankey.prototype.destroy = function destroy () {
36
+ this.unbind();
37
+ this._destroySurface();
38
+ this._destroyResizeObserver();
39
+ };
40
+
41
+ Sankey.prototype._initElement = function _initElement (element) {
42
+ this.element = element;
43
+ addClass(this.element, [ "k-chart", "k-sankey" ]);
44
+ };
45
+
46
+ Sankey.prototype._initSurface = function _initSurface () {
47
+ if (!this.surface) {
48
+ this._destroySurface();
49
+ this._initSurfaceElement();
50
+ this.surface = this._createSurface();
51
+ }
52
+ };
53
+
54
+ Sankey.prototype._initResizeObserver = function _initResizeObserver () {
55
+ var this$1 = this;
56
+
57
+ var observer = new ResizeObserver(function (entries) {
58
+ entries.forEach(function (entry) {
59
+ var ref = entry.contentRect;
60
+ var width = ref.width;
61
+ var height = ref.height;
62
+ if (entry.target !== this$1.element ||
63
+ (this$1.size.width === width && this$1.size.height === height)) {
64
+ return;
65
+ }
66
+ this$1.size = { width: width, height: height };
67
+ this$1.surface.setSize(this$1.size);
68
+ this$1._redraw();
69
+ });
70
+ });
71
+ this._resizeObserver = observer;
72
+ observer.observe(this.element);
73
+ };
74
+
75
+ Sankey.prototype._createSurface = function _createSurface () {
76
+ return drawing.Surface.create(this.surfaceElement, {
77
+ mouseenter: this._mouseenter.bind(this),
78
+ mouseleave: this._mouseleave.bind(this)
79
+ });
80
+ };
81
+
82
+ Sankey.prototype._initTheme = function _initTheme (theme) {
83
+ var currentTheme = theme || this.theme || {};
84
+ this.theme = currentTheme;
85
+ this.options = deepExtend({}, currentTheme, this.options);
86
+ };
87
+
88
+ Sankey.prototype.setLinksOpacity = function setLinksOpacity (opacity) {
89
+ var this$1 = this;
90
+
91
+ this.linksVisuals.forEach(function (link) {
92
+ this$1.setOpacity(link, opacity);
93
+ });
94
+ };
95
+
96
+ Sankey.prototype.setOpacity = function setOpacity (link, opacity) {
97
+ link.options.set('stroke', Object.assign({}, link.options.stroke, {opacity: opacity}));
98
+ };
99
+
100
+ Sankey.prototype.trigger = function trigger (name, ev) {
101
+ ev.type = name;
102
+ return Observable.prototype.trigger.call(this, name, ev);
103
+ };
104
+
105
+ Sankey.prototype._mouseenter = function _mouseenter (ev) {
106
+ var this$1 = this;
107
+
108
+ var element = ev.element;
109
+ var isLink = element.type === LINK;
110
+ var isNode = element.type === NODE;
111
+
112
+ if ((isLink && this.trigger('linkEnter', ev)) ||
113
+ (isNode && this.trigger('nodeEnter', ev))) {
114
+ return;
115
+ }
116
+
117
+ var ref = this.options.links;
118
+ var highlight = ref.highlight;
119
+ if (isLink) {
120
+ this.setLinksOpacity(highlight.inactiveOpacity);
121
+ this.setOpacity(element, highlight.opacity);
122
+ } else if (isNode) {
123
+ this.setLinksOpacity(highlight.inactiveOpacity);
124
+ element.links.forEach(function (link) {
125
+ this$1.setOpacity(link, highlight.opacity);
126
+ });
127
+ }
128
+ };
129
+
130
+ Sankey.prototype._mouseleave = function _mouseleave (ev) {
131
+ var element = ev.element;
132
+ var isLink = element.type === LINK;
133
+ var isNode = element.type === NODE;
134
+ var target = ev.originalEvent.relatedTarget;
135
+
136
+ if (isLink && target && target.nodeName === 'text') {
137
+ return;
138
+ }
139
+
140
+ if ((isLink && this.trigger('linkLeave', ev)) ||
141
+ (isNode && this.trigger('nodeLeave', ev))) {
142
+ return;
143
+ }
144
+
145
+ if (isLink || isNode) {
146
+ this.setLinksOpacity(this.options.links.opacity);
147
+ }
148
+ };
149
+
150
+ Sankey.prototype._destroySurface = function _destroySurface () {
151
+ if (this.surface) {
152
+ this.surface.destroy();
153
+ this.surface = null;
154
+ this._destroySurfaceElement();
155
+ }
156
+ };
157
+
158
+ Sankey.prototype._destroyResizeObserver = function _destroyResizeObserver () {
159
+ if (this._resizeObserver) {
160
+ this._resizeObserver.disconnect();
161
+ this._resizeObserver = null;
162
+ }
163
+ };
164
+
165
+ Sankey.prototype._initSurfaceElement = function _initSurfaceElement () {
166
+ if (!this.surfaceElement) {
167
+ this.surfaceElement = document.createElement('div');
168
+ this.element.appendChild(this.surfaceElement);
169
+ }
170
+ };
171
+
172
+ Sankey.prototype._destroySurfaceElement = function _destroySurfaceElement () {
173
+ if (this.surfaceElement && this.surfaceElement.parentNode) {
174
+ this.surfaceElement.parentNode.removeChild(this.surfaceElement);
175
+ this.surfaceElement = null;
176
+ }
177
+ };
178
+
179
+ Sankey.prototype.setOptions = function setOptions (options, theme) {
180
+ this._setOptions(options);
181
+ this._initTheme(theme);
182
+ this._initSurface();
183
+ this._redraw();
184
+ };
185
+
186
+ Sankey.prototype._redraw = function _redraw () {
187
+ this.surface.clear();
188
+
189
+ var ref = this._getSize();
190
+ var width = ref.width;
191
+ var height = ref.height;
192
+ this.size = { width: width, height: height };
193
+ this.surface.setSize(this.size);
194
+
195
+ this.createVisual();
196
+
197
+ this.surface.draw(this.visual);
198
+ };
199
+
200
+ Sankey.prototype._getSize = function _getSize () {
201
+ return this.element.getBoundingClientRect();
202
+ };
203
+
204
+ Sankey.prototype.createVisual = function createVisual () {
205
+ this.visual = this._render();
206
+ };
207
+
208
+ Sankey.prototype.calculateSankey = function calculateSankey$1 (options) {
209
+ var ref = this.options;
210
+ var nodes = ref.nodes;
211
+ var labels = ref.labels;
212
+ var nodesColors = ref.nodesColors;
213
+ var calculatedNodes = calculateSankey(options).nodes;
214
+ var box = new Box();
215
+
216
+ calculatedNodes.forEach(function (nodeEl, i) {
217
+ var nodeOps = resolveNodeOptions(nodeEl, nodes, nodesColors, i);
218
+ var nodeInstance = new Node(nodeOps);
219
+ box.wrap(rectToBox(nodeInstance.exportVisual().rawBBox()));
220
+
221
+ var labelInstance = new Label(deepExtend({ node: nodeEl, totalWidth: options.width }, labels));
222
+ var labelVisual = labelInstance.exportVisual();
223
+ if (labelVisual) {
224
+ box.wrap(rectToBox(labelVisual.rawBBox()));
225
+ }
226
+ });
227
+
228
+ var offsetX = box.x1 < 0 ? -box.x1 : 0;
229
+ var offsetY = box.y1 < 0 ? -box.y1 : 0;
230
+
231
+ var width = box.width() > options.width ? offsetX + options.width - (box.width() - options.width) : options.width;
232
+ var height = box.height() > options.height ? offsetY + options.height - (box.height() - options.height) : options.height;
233
+
234
+ return calculateSankey(Object.assign({}, options, {width: width, height: height, offsetX: offsetX, offsetY: offsetY}));
235
+ };
236
+
237
+ Sankey.prototype._render = function _render () {
238
+ var ref = this.options;
239
+ var data = ref.data;
240
+ var labelOptions = ref.labels;
241
+ var nodesOptions = ref.nodes;
242
+ var linkOptions = ref.links;
243
+ var nodesColors = ref.nodesColors;
244
+ var ref$1 = this.size;
245
+ var width = ref$1.width;
246
+ var height = ref$1.height;
247
+ var calcOptions = Object.assign({}, data, {width: width, height: height, nodesOptions: nodesOptions});
248
+ var ref$2 = this.calculateSankey(calcOptions);
249
+ var nodes = ref$2.nodes;
250
+ var links = ref$2.links;
251
+
252
+ var visual = new drawing.Group();
253
+
254
+ var visualNodes = new Map();
255
+
256
+ nodes.forEach(function (node, i) {
257
+ var nodeOps = resolveNodeOptions(node, nodesOptions, nodesColors, i);
258
+
259
+ var nodeInstance = new Node(nodeOps);
260
+ var nodeVisual = nodeInstance.exportVisual();
261
+ nodeVisual.links = [];
262
+ nodeVisual.type = NODE;
263
+ visualNodes.set(node.id, nodeVisual);
264
+
265
+ visual.append(nodeVisual);
266
+ });
267
+
268
+ links = links.slice().sort(function (a, b) { return b.value - a.value; });
269
+
270
+ var linksVisuals = [];
271
+
272
+ links.forEach(function (link) {
273
+ var source = link.source;
274
+ var target = link.target;
275
+ var sourceNode = visualNodes.get(source.id);
276
+ var targetNode = visualNodes.get(target.id);
277
+ var linkOps = resolveLinkOptions(link, linkOptions, sourceNode, targetNode);
278
+ var linkInstance = new Link(linkOps);
279
+ var linkVisual = linkInstance.exportVisual();
280
+
281
+ linkVisual.type = LINK;
282
+ linksVisuals.push(linkVisual);
283
+
284
+ sourceNode.links.push(linkVisual);
285
+ targetNode.links.push(linkVisual);
286
+
287
+ visual.append(linkVisual);
288
+ });
289
+
290
+ this.linksVisuals = linksVisuals;
291
+
292
+ nodes.forEach(function (node) {
293
+ var textOps = resolveLabelOptions(node, labelOptions, width);
294
+ var labelInstance = new Label(textOps);
295
+ var labelVisual = labelInstance.exportVisual();
296
+
297
+ if (labelVisual) {
298
+ visual.append(labelVisual);
299
+ }
300
+ });
301
+
302
+ return visual;
303
+ };
304
+
305
+ Sankey.prototype.exportVisual = function exportVisual () {
306
+ return this._render();
307
+ };
308
+
309
+ Sankey.prototype._setOptions = function _setOptions (options) {
310
+ this.options = deepExtend({}, this.options, options);
311
+ };
312
+
313
+ return Sankey;
314
+ }(Observable));
315
+
316
+ setDefaultOptions(Sankey, {
317
+ labels: {
318
+ visible: true,
319
+ margin: {
320
+ left: 8,
321
+ right: 8
322
+ },
323
+ padding: 0,
324
+ border: {
325
+ width: 0
326
+ },
327
+ align: LEFT,
328
+ opacity: 1,
329
+ offset: { left: 0, top: 0 }
330
+ },
331
+ nodes: {
332
+ width: 24,
333
+ padding: 16,
334
+ opacity: 1,
335
+ offset: { left: 0, top: 0 }
336
+ },
337
+ links: {
338
+ colorType: 'static', // 'source', 'target', 'static'
339
+ opacity: 0.4,
340
+ highlight: {
341
+ opacity: 0.8,
342
+ inactiveOpacity: 0.2
343
+ }
344
+ }
345
+ });
@@ -0,0 +1 @@
1
+ export { Sankey } from './sankey/sankey';
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Class
3
- } from '../../common';
3
+ } from '../common';
4
4
 
5
5
  const STRING = "string";
6
6
  const FUNCTION = "function";
@@ -7,6 +7,7 @@ export * from './gauges';
7
7
  export * from './barcode';
8
8
  export * from './qrcode';
9
9
  export * from './map';
10
+ export * from './sankey';
10
11
  export * from './common';
11
12
 
12
13
  export { baseTheme as chartBaseTheme } from './chart/base-theme';
@@ -11,7 +11,7 @@ import {
11
11
  removeChildren
12
12
  } from './utils';
13
13
 
14
- import { Observable } from './scroller/observable';
14
+ import { Observable } from '../common/observable';
15
15
 
16
16
  export class Attribution extends Observable {
17
17
  constructor(element, options) {
@@ -76,7 +76,7 @@ import {
76
76
 
77
77
  import {
78
78
  Observable
79
- } from './scroller/observable';
79
+ } from '../common/observable';
80
80
 
81
81
  import MapService from './../services/map-service';
82
82
 
@@ -8,7 +8,7 @@ import {
8
8
 
9
9
  import {
10
10
  Observable
11
- } from './scroller/observable';
11
+ } from '../common/observable';
12
12
 
13
13
  import {
14
14
  proxy,
@@ -5,7 +5,7 @@ import {
5
5
 
6
6
  import {
7
7
  Observable
8
- } from './observable';
8
+ } from '../../common/observable';
9
9
 
10
10
  import {
11
11
  getEventMap,
@@ -39,7 +39,7 @@ import {
39
39
 
40
40
  import {
41
41
  Observable
42
- } from './observable';
42
+ } from '../../common/observable';
43
43
 
44
44
  let
45
45
  extend = Object.assign,
@@ -16,7 +16,7 @@ import {
16
16
 
17
17
  import {
18
18
  Observable
19
- } from './observable';
19
+ } from '../../common/observable';
20
20
 
21
21
  const extend = Object.assign;
22
22
 
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  import {
11
11
  Observable
12
- } from './scroller/observable';
12
+ } from '../common/observable';
13
13
 
14
14
  import {
15
15
  on,
@@ -0,0 +1,170 @@
1
+ import { deepExtend } from '../common';
2
+
3
+ const max = (array, mapFn) => Math.max.apply(null, array.map(mapFn));
4
+ const min = (array, mapFn) => Math.min.apply(null, array.map(mapFn));
5
+ const sum = (array, mapFn) => array.map(mapFn).reduce((acc, curr) => (acc + curr), 0);
6
+ const sortAsc = (a, b) => a.y0 - b.y0;
7
+ const sortSource = (a, b) => sortAsc(a.source, b.source);
8
+ const sortTarget = (a, b) => sortAsc(a.target, b.target);
9
+ const value = (node) => node.value;
10
+
11
+ function sortLinks(nodes) {
12
+ nodes.forEach((node) => {
13
+ const { sourceLinks, targetLinks } = node;
14
+ sourceLinks.sort(sortTarget);
15
+ targetLinks.sort(sortSource);
16
+ });
17
+ }
18
+
19
+ class Sankey {
20
+ constructor(options) {
21
+ const offset = options.nodesOptions.offset || {};
22
+ this.data = {
23
+ nodes: options.nodes.map((node) => deepExtend({}, { offset }, node)),
24
+ links: options.links.map((link) => deepExtend({}, link))
25
+ };
26
+
27
+ this.width = options.width;
28
+ this.height = options.height;
29
+ this.offsetX = options.offsetX || 0;
30
+ this.offsetY = options.offsetY || 0;
31
+ this.nodeWidth = options.nodesOptions.width;
32
+ this.nodePadding = options.nodesOptions.padding;
33
+ }
34
+
35
+ calculate() {
36
+ const { nodes, links } = this.data;
37
+ this.connectLinksToNodes(nodes, links);
38
+ this.calculateNodeValues(nodes);
39
+ this.calculateNodeDepths(nodes);
40
+
41
+ const columns = this.calculateNodeColumns(nodes);
42
+ this.calculateNodeBreadths(columns);
43
+ this.applyNodesOffset(nodes);
44
+ this.calculateLinkBreadths(nodes);
45
+
46
+ return this.data;
47
+ }
48
+
49
+ connectLinksToNodes(nodes, links) {
50
+ const nodesMap = new Map();
51
+
52
+ nodes.forEach((node) => {
53
+ node.sourceLinks = [];
54
+ node.targetLinks = [];
55
+ node.id = node.id !== undefined ? node.id : node.label.text;
56
+ nodesMap.set(node.id, node);
57
+ });
58
+
59
+ links.forEach((link) => {
60
+ link.source = nodesMap.get(link.sourceId);
61
+ link.target = nodesMap.get(link.targetId);
62
+ link.source.sourceLinks.push(link);
63
+ link.target.targetLinks.push(link);
64
+ });
65
+ }
66
+
67
+ calculateNodeValues(nodes) {
68
+ nodes.forEach((node) => {
69
+ node.value = Math.max(
70
+ sum(node.sourceLinks, value),
71
+ sum(node.targetLinks, value)
72
+ );
73
+ });
74
+ }
75
+
76
+ calculateNodeDepths(nodes) {
77
+ let current = new Set(nodes);
78
+ let next = new Set();
79
+ let currDepth = 0;
80
+ while (current.size) {
81
+ const currentNodes = Array.from(current);
82
+ for (let n = 0; n < currentNodes.length; n++) {
83
+ const node = currentNodes[n];
84
+ node.depth = currDepth;
85
+ for (let l = 0; l < node.sourceLinks.length; l++) {
86
+ const link = node.sourceLinks[l];
87
+ next.add(link.target);
88
+ }
89
+ }
90
+ currDepth++;
91
+ current = next;
92
+ next = new Set();
93
+ }
94
+ }
95
+
96
+ calculateNodeColumns(nodes) {
97
+ const maxDepth = max(nodes, (d) => d.depth);
98
+ const columnWidth = (this.width - this.offsetX - this.nodeWidth) / maxDepth;
99
+ const columns = new Array(maxDepth + 1);
100
+ for (let i = 0; i < nodes.length; i++) {
101
+ const node = nodes[i];
102
+ const depth = Math.max(0, Math.min(maxDepth, node.sourceLinks.length ? node.depth : maxDepth));
103
+ node.x0 = this.offsetX + depth * columnWidth;
104
+ node.x1 = node.x0 + this.nodeWidth;
105
+ columns[depth] = columns[depth] || [];
106
+ columns[depth].push(node);
107
+ }
108
+
109
+ return columns;
110
+ }
111
+
112
+ calculateNodeBreadths(columns) {
113
+ const kSize = min(columns, (c) => (this.height - this.offsetY - (c.length - 1) * this.nodePadding) / sum(c, value));
114
+
115
+ for (let c = 0; c < columns.length; c++) {
116
+ const nodes = columns[c];
117
+ let y = this.offsetY;
118
+ for (let i = 0; i < nodes.length; i++) {
119
+ const node = nodes[i];
120
+ node.y0 = y;
121
+ node.y1 = y + node.value * kSize;
122
+ y = node.y1 + this.nodePadding;
123
+ for (let l = 0; l < node.sourceLinks.length; l++) {
124
+ const link = node.sourceLinks[l];
125
+ link.width = link.value * kSize;
126
+ }
127
+ }
128
+
129
+ y = (this.height - y + this.nodePadding) / (nodes.length + 1);
130
+ for (let i = 0; i < nodes.length; ++i) {
131
+ const node = nodes[i];
132
+ node.y0 += y * (i + 1);
133
+ node.y1 += y * (i + 1);
134
+ }
135
+ }
136
+
137
+ for (let c = 0; c < columns.length; c++) {
138
+ sortLinks(columns[c]);
139
+ }
140
+ }
141
+
142
+ applyNodesOffset(nodes) {
143
+ nodes.forEach((node) => {
144
+ const offsetX = (node.offset ? node.offset.left : 0) || 0;
145
+ const offsetY = (node.offset ? node.offset.top : 0) || 0;
146
+ node.x0 += offsetX;
147
+ node.x1 += offsetX;
148
+ node.y0 += offsetY;
149
+ node.y1 += offsetY;
150
+ });
151
+ }
152
+
153
+ calculateLinkBreadths(nodes) {
154
+ nodes.forEach((node) => {
155
+ const { sourceLinks, targetLinks } = node;
156
+ let y = node.y0;
157
+ let y1 = y;
158
+ sourceLinks.forEach((link) => {
159
+ link.y0 = y + link.width / 2;
160
+ y += link.width;
161
+ });
162
+ targetLinks.forEach((link) => {
163
+ link.y1 = y1 + link.width / 2;
164
+ y1 += link.width;
165
+ });
166
+ });
167
+ }
168
+ }
169
+
170
+ export const calculateSankey = (options) => new Sankey(options).calculate();
@@ -0,0 +1,41 @@
1
+ import {
2
+ Class,
3
+ deepExtend
4
+ } from '../common';
5
+
6
+ export class SankeyElement extends Class {
7
+ constructor(options) {
8
+ super();
9
+ this.options = deepExtend({}, this.options, options);
10
+ this.createVisual();
11
+ }
12
+
13
+ createVisual() {
14
+ this.visual = this.createElement();
15
+ }
16
+
17
+ exportVisual() {
18
+ return this.visual;
19
+ }
20
+
21
+ createElement() {
22
+ const customVisual = this.options.visual;
23
+ let visual;
24
+
25
+ if (customVisual) {
26
+ visual = customVisual({
27
+ sender: this.getSender(),
28
+ options: this.visualOptions(),
29
+ createVisual: () => this.getElement()
30
+ });
31
+ } else {
32
+ visual = this.getElement();
33
+ }
34
+
35
+ return visual;
36
+ }
37
+
38
+ getSender() {
39
+ return this;
40
+ }
41
+ }