@rfprodz/client-d3-charts 1.0.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -0
  3. package/esm2020/index.mjs +5 -0
  4. package/esm2020/lib/client-d3-charts.module.mjs +47 -0
  5. package/esm2020/lib/components/bar-chart/bar-chart.component.mjs +87 -0
  6. package/esm2020/lib/components/chart-examples/chart-examples.component.mjs +181 -0
  7. package/esm2020/lib/components/force-directed-chart/force-directed-chart.component.mjs +92 -0
  8. package/esm2020/lib/components/index.mjs +7 -0
  9. package/esm2020/lib/components/line-chart/line-chart.component.mjs +91 -0
  10. package/esm2020/lib/components/pie-chart/pie-chart.component.mjs +90 -0
  11. package/esm2020/lib/components/radar-chart/radar-chart.component.mjs +102 -0
  12. package/esm2020/lib/interfaces/bar-chart.interface.mjs +2 -0
  13. package/esm2020/lib/interfaces/force-directed-chart.interface.mjs +2 -0
  14. package/esm2020/lib/interfaces/index.mjs +6 -0
  15. package/esm2020/lib/interfaces/line-chart.interface.mjs +2 -0
  16. package/esm2020/lib/interfaces/pie-chart.interface.mjs +2 -0
  17. package/esm2020/lib/interfaces/radar-chart.interface.mjs +2 -0
  18. package/esm2020/lib/providers/d3-chart-factory.provider.mjs +18 -0
  19. package/esm2020/lib/util/bar-chart.util.mjs +255 -0
  20. package/esm2020/lib/util/configuration.util.mjs +31 -0
  21. package/esm2020/lib/util/force-directed-chart.util.mjs +255 -0
  22. package/esm2020/lib/util/index.mjs +6 -0
  23. package/esm2020/lib/util/line-chart.util.mjs +266 -0
  24. package/esm2020/lib/util/pie-chart.util.mjs +89 -0
  25. package/esm2020/lib/util/radar-chart.util.mjs +295 -0
  26. package/esm2020/rfprodz-client-d3-charts.mjs +5 -0
  27. package/fesm2015/rfprodz-client-d3-charts.mjs +1868 -0
  28. package/fesm2015/rfprodz-client-d3-charts.mjs.map +1 -0
  29. package/fesm2020/rfprodz-client-d3-charts.mjs +1855 -0
  30. package/fesm2020/rfprodz-client-d3-charts.mjs.map +1 -0
  31. package/index.d.ts +4 -0
  32. package/lib/client-d3-charts.module.d.ts +13 -0
  33. package/lib/components/bar-chart/bar-chart.component.d.ts +49 -0
  34. package/lib/components/chart-examples/chart-examples.component.d.ts +60 -0
  35. package/lib/components/force-directed-chart/force-directed-chart.component.d.ts +49 -0
  36. package/lib/components/index.d.ts +6 -0
  37. package/lib/components/line-chart/line-chart.component.d.ts +49 -0
  38. package/lib/components/pie-chart/pie-chart.component.d.ts +49 -0
  39. package/lib/components/radar-chart/radar-chart.component.d.ts +49 -0
  40. package/lib/interfaces/bar-chart.interface.d.ts +25 -0
  41. package/lib/interfaces/force-directed-chart.interface.d.ts +50 -0
  42. package/lib/interfaces/index.d.ts +5 -0
  43. package/lib/interfaces/line-chart.interface.d.ts +28 -0
  44. package/lib/interfaces/pie-chart.interface.d.ts +19 -0
  45. package/lib/interfaces/radar-chart.interface.d.ts +30 -0
  46. package/lib/providers/d3-chart-factory.provider.d.ts +15 -0
  47. package/lib/util/bar-chart.util.d.ts +14 -0
  48. package/lib/util/configuration.util.d.ts +7 -0
  49. package/lib/util/force-directed-chart.util.d.ts +14 -0
  50. package/lib/util/index.d.ts +5 -0
  51. package/lib/util/line-chart.util.d.ts +14 -0
  52. package/lib/util/pie-chart.util.d.ts +14 -0
  53. package/lib/util/radar-chart.util.d.ts +14 -0
  54. package/package.json +53 -0
@@ -0,0 +1,1855 @@
1
+ import * as i2 from '@angular/common';
2
+ import { DOCUMENT, CommonModule } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { InjectionToken, Component, ChangeDetectionStrategy, Inject, Input, ViewChild, NgModule } from '@angular/core';
5
+ import * as d3 from 'd3';
6
+ import * as i1 from '@angular/cdk/layout';
7
+ import { Breakpoints } from '@angular/cdk/layout';
8
+ import { map, switchMap, timer, first } from 'rxjs';
9
+
10
+ /**
11
+ * Generates a configuration object based on a defaut configuration and an options object.
12
+ * @param config the default object with all properties
13
+ * @param options the input object
14
+ * @param result the output object
15
+ */
16
+ const generateConfiguration = (config, options, result) => {
17
+ const defaultConfiguration = config;
18
+ if (typeof options === 'undefined') {
19
+ return config;
20
+ }
21
+ const keys = Object.keys(defaultConfiguration);
22
+ for (const key of keys) {
23
+ const defaultValue = defaultConfiguration[key];
24
+ const value = options[key];
25
+ const typedKey = key;
26
+ if (typeof defaultValue === 'string' || typeof defaultValue === 'number' || typeof defaultValue === 'boolean') {
27
+ result[typedKey] = typeof value !== 'undefined' ? value : defaultValue;
28
+ }
29
+ else if (defaultValue instanceof Function) {
30
+ result[typedKey] = defaultValue;
31
+ }
32
+ else if (typeof defaultValue === 'object' && defaultValue !== null) {
33
+ const nestedDefaultObject = defaultValue;
34
+ const nestedObject = value;
35
+ result[typedKey] = generateConfiguration(nestedDefaultObject, nestedObject, {});
36
+ }
37
+ }
38
+ return result;
39
+ };
40
+
41
+ /**
42
+ * The bar chart default configuration.
43
+ */
44
+ const defaultBarChartConfig = Object.freeze({
45
+ chartTitle: '',
46
+ width: 350,
47
+ height: 350,
48
+ margin: {
49
+ top: 70,
50
+ right: 50,
51
+ bottom: 50,
52
+ left: 50,
53
+ },
54
+ transitionDuration: 400,
55
+ xAxisPadding: 0.4,
56
+ xAxisTitle: '',
57
+ yAxisTitle: '',
58
+ yAxisTicks: 10,
59
+ displayAxisLabels: true,
60
+ labelTextWrapWidth: 60,
61
+ color: d3.scaleOrdinal(d3.schemeCategory10),
62
+ });
63
+ /**
64
+ * Creates a container for the bar chart.
65
+ * @param container the chart container
66
+ * @param config the chart configuration
67
+ * @returns the object with the svg element and the g element
68
+ */
69
+ const createContainer$4 = (container, config) => {
70
+ const id = container.nativeElement.id ?? 'bar-0';
71
+ d3.select(`#${id}`).select('svg').remove();
72
+ const svg = d3
73
+ .select(`#${id}`)
74
+ .append('svg')
75
+ .attr('width', config.width + config.margin.left + config.margin.right)
76
+ .attr('height', config.height + config.margin.top + config.margin.bottom)
77
+ .attr('class', id);
78
+ const g = svg.append('g').attr('transform', `translate(${config.margin.left},${config.margin.top / 2})`);
79
+ return { svg, g };
80
+ };
81
+ /**
82
+ * Wraps the bar chart axis labels text.
83
+ * @param svgText the svg text elements
84
+ * @param width the chart axis label width
85
+ */
86
+ const wrapSvgText$2 = (svgText, width) => {
87
+ svgText.each(function () {
88
+ const text = d3.select(this);
89
+ const words = text.text().split(/\s+/).reverse();
90
+ let line = [];
91
+ let lineNumber = 0;
92
+ const lineHeight = 1.4;
93
+ const y = text.attr('y');
94
+ const x = text.attr('x');
95
+ const dy = parseFloat(text.attr('dy') ?? 0);
96
+ let tspan = text.text(null).append('tspan').attr('x', x).attr('y', y).attr('dy', `${dy}em`); // axis label
97
+ let word = words.pop();
98
+ while (typeof word !== 'undefined') {
99
+ line.push(word ?? '');
100
+ tspan.text(line.join(' '));
101
+ if ((tspan.node()?.getComputedTextLength() ?? 0) > width) {
102
+ line.pop();
103
+ tspan.text(line.join(' '));
104
+ line = [word ?? ''];
105
+ lineNumber += 1;
106
+ tspan = text
107
+ .append('tspan')
108
+ .attr('x', 0)
109
+ .attr('y', y)
110
+ .attr('dy', `${lineNumber * lineHeight + dy}em`)
111
+ .text(word ?? '');
112
+ }
113
+ word = words.pop();
114
+ }
115
+ });
116
+ };
117
+ /**
118
+ * Creates the legend.
119
+ * @param g the svg g element
120
+ * @param config the chart configuration
121
+ */
122
+ const createLegend$2 = (g, config) => {
123
+ if (config.displayAxisLabels && config.xAxisTitle !== '') {
124
+ g.append('g')
125
+ .attr('transform', `translate(0, ${config.height + config.margin.bottom})`)
126
+ .append('text')
127
+ .style('font-size', '12px')
128
+ .attr('class', 'legend')
129
+ .attr('dx', '1.5em')
130
+ .attr('dy', '1em')
131
+ .text(`x - ${config.xAxisTitle}`);
132
+ }
133
+ if (config.displayAxisLabels && config.yAxisTitle !== '') {
134
+ g.append('g')
135
+ .attr('transform', `translate(0, ${config.height + config.margin.bottom})`)
136
+ .append('text')
137
+ .style('font-size', '12px')
138
+ .attr('class', 'legend')
139
+ .attr('dx', '1.5em')
140
+ .attr('dy', '2.5em')
141
+ .text(`y - ${config.yAxisTitle}`);
142
+ }
143
+ if (config.chartTitle !== '') {
144
+ g.append('g')
145
+ .attr('transform', `translate(0, 0)`)
146
+ .append('text')
147
+ .style('font-size', '12px')
148
+ .attr('class', 'legend')
149
+ .attr('dx', '1.5em')
150
+ .attr('dy', '-2em')
151
+ .text(config.chartTitle);
152
+ }
153
+ };
154
+ /**
155
+ * Creates the x axis.
156
+ * @param g the svg g element
157
+ * @param x the x axis scale
158
+ * @param config the chart configuration
159
+ */
160
+ const createAxisX$1 = (g, x, config) => {
161
+ const xLabels = g.append('g').attr('transform', `translate(0, ${config.height})`).call(d3.axisBottom(x)).append('text');
162
+ g.selectAll('text').call(wrapSvgText$2, config.labelTextWrapWidth);
163
+ if (config.displayAxisLabels) {
164
+ xLabels.attr('transform', `translate(${config.width}, 0)`).attr('class', 'legend').attr('dx', '1.5em').attr('dy', '0.7em').text('x');
165
+ }
166
+ };
167
+ /**
168
+ * Creates the y axis.
169
+ * @param g the svg g element
170
+ * @param y the y axis scale
171
+ * @param config the chart configuration
172
+ */
173
+ const createAxisY$1 = (g, y, config) => {
174
+ const yLabels = g
175
+ .append('g')
176
+ .call(d3
177
+ .axisLeft(y)
178
+ .tickFormat(function (d) {
179
+ return `${d}`;
180
+ })
181
+ .ticks(config.yAxisTicks))
182
+ .append('text');
183
+ if (config.displayAxisLabels) {
184
+ yLabels.attr('dy', '-1.5em').attr('class', 'legend').text('y');
185
+ }
186
+ };
187
+ /**
188
+ * The mouse over event handler.
189
+ * @param self an svg rect element
190
+ * @param d the chart data node
191
+ * @param g the svg g element
192
+ * @param x the x axis scale
193
+ * @param y the y axis scale
194
+ * @param config the chart configuration
195
+ */
196
+ const onMouseOver$1 = (self, d, g, x, y, config) => {
197
+ const widthModifier = 5;
198
+ d3.select(self)
199
+ .transition()
200
+ .duration(config.transitionDuration)
201
+ .attr('width', x.bandwidth() + widthModifier)
202
+ .attr('y', function () {
203
+ const modifier = 10;
204
+ return y(d.value) - modifier;
205
+ })
206
+ .attr('height', function () {
207
+ const modifier = 10;
208
+ return config.height - y(d.value) + modifier;
209
+ });
210
+ g.append('text')
211
+ .attr('class', 'val')
212
+ .style('font-size', '11px')
213
+ .attr('x', () => x(d.title) ?? '')
214
+ .attr('y', function () {
215
+ const modifier = 15;
216
+ return y(d.value) - modifier;
217
+ })
218
+ .text(() => d.value);
219
+ };
220
+ /**
221
+ * The mouse out event handler.
222
+ * @param self an svg rect element
223
+ * @param d the chart data node
224
+ * @param x the x axis scale
225
+ * @param y the y axis scale
226
+ * @param config the chart configuration
227
+ */
228
+ const onMouseOut$1 = (self, d, x, y, config) => {
229
+ d3.select(self).attr('class', 'bar');
230
+ d3.select(self)
231
+ .transition()
232
+ .duration(config.transitionDuration)
233
+ .attr('width', x.bandwidth())
234
+ .attr('y', () => y(d.value) ?? 0)
235
+ .attr('height', () => config.height - (y(d.value) ?? 0));
236
+ d3.selectAll('.val').remove();
237
+ };
238
+ /**
239
+ * Draws the chart bars, and sets the mouse pointer events.
240
+ * @param g the svg g element
241
+ * @param x the x axis scale
242
+ * @param y the y axis scale
243
+ * @param config the chart configuration
244
+ * @param data the chart data
245
+ */
246
+ const drawBarsAndSetPointerEvents = (g, x, y, config, data) => {
247
+ const duration = 400;
248
+ g.selectAll('.bar')
249
+ .data(data)
250
+ .enter()
251
+ .append('rect')
252
+ .attr('class', 'bar')
253
+ .style('fill', (d, i) => config.color(i.toString()))
254
+ .on('mouseover', function (event, d) {
255
+ return onMouseOver$1(this, d, g, x, y, config);
256
+ })
257
+ .on('mouseout', function (event, d) {
258
+ return onMouseOut$1(this, d, x, y, config);
259
+ })
260
+ .attr('x', d => x(d.title) ?? '')
261
+ .attr('y', d => y(d.value))
262
+ .attr('width', x.bandwidth())
263
+ .transition()
264
+ .ease(d3.easeLinear)
265
+ .duration(duration)
266
+ .delay(function (d, i) {
267
+ const multiplier = 50;
268
+ return i * multiplier;
269
+ })
270
+ .attr('height', d => config.height - y(d.value));
271
+ };
272
+ /**
273
+ * Draws the bar chart.
274
+ * @param container the chart container
275
+ * @param data the chart data
276
+ * @param options the chart options
277
+ * @returns the chart configuration
278
+ */
279
+ const drawBarChart = (container, data, options) => {
280
+ const config = generateConfiguration(defaultBarChartConfig, options, {});
281
+ const { g } = createContainer$4(container, config);
282
+ const x = d3
283
+ .scaleBand([0, config.width])
284
+ .padding(config.xAxisPadding)
285
+ .domain(data.map(d => d.title));
286
+ const y = d3.scaleLinear([config.height, 0]).domain([0, d3.max(data, d => d.value) ?? 1]);
287
+ createAxisX$1(g, x, config);
288
+ createAxisY$1(g, y, config);
289
+ createLegend$2(g, config);
290
+ drawBarsAndSetPointerEvents(g, x, y, config, data);
291
+ return config;
292
+ };
293
+
294
+ /**
295
+ * The force directed chart default configuration.
296
+ */
297
+ const defaultForceDirectedChartConfig = Object.freeze({
298
+ chartTitle: '',
299
+ width: 600,
300
+ height: 600,
301
+ centerCalcMod: 1.6,
302
+ charge: {
303
+ strength: -10,
304
+ theta: 0.6,
305
+ distanceMax: 2000,
306
+ },
307
+ distance: 75,
308
+ fontSize: 10,
309
+ collisionRadius: 30,
310
+ margin: {
311
+ top: 20,
312
+ right: 20,
313
+ bottom: 20,
314
+ left: 20,
315
+ },
316
+ strokeWidth: 1.5,
317
+ labelTextWrapWidth: 60,
318
+ color: d3.scaleOrdinal(d3.schemeCategory10),
319
+ });
320
+ /**
321
+ * The force durected chart tick handler.
322
+ * @param link chart links
323
+ * @param node chart nodes
324
+ * @param text chart text
325
+ * @returns rotation angle
326
+ */
327
+ const ticked = (link, node, text) => {
328
+ if (typeof link !== 'undefined') {
329
+ link
330
+ .attr('x1', d => d.source.x ?? 0)
331
+ .attr('y1', d => d.source.y ?? 0)
332
+ .attr('x2', d => d.target.x ?? 0)
333
+ .attr('y2', d => d.target.y ?? 0);
334
+ }
335
+ if (typeof node !== 'undefined') {
336
+ node.attr('cx', d => d.x ?? 0).attr('cy', d => d.y ?? 0);
337
+ }
338
+ if (typeof text !== 'undefined') {
339
+ const dx = 10;
340
+ const dy = 5;
341
+ text.attr('x', d => (d.x ?? 0) + dx).attr('y', d => (d.y ?? 0) - dy);
342
+ }
343
+ return 'rotate(0)';
344
+ };
345
+ /**
346
+ * Creates a container for the force directed chart.
347
+ * @param container the chart container
348
+ * @param config the chart configuration
349
+ * @returns the object with the svg element and the g element
350
+ */
351
+ const createContainer$3 = (container, config) => {
352
+ const id = container.nativeElement.id ?? 'force-directed-0';
353
+ d3.select(`#${id}`).select('svg').remove();
354
+ const svg = d3
355
+ .select(`#${id}`)
356
+ .append('svg')
357
+ .attr('width', config.width + config.margin.left + config.margin.right)
358
+ .attr('height', config.height + config.margin.top + config.margin.bottom)
359
+ .attr('class', id);
360
+ const g = svg
361
+ .append('g')
362
+ .attr('transform', `translate(${config.width / 2 + config.margin.left},${config.height / 2 + config.margin.top})`);
363
+ return { svg, g };
364
+ };
365
+ /**
366
+ * Applies the force directed chart data.
367
+ * @param g the svg g element
368
+ * @param data the chart data
369
+ */
370
+ const applyChartData = (g, data) => {
371
+ const imageXY = 10;
372
+ g.append('defs')
373
+ .selectAll('pattern')
374
+ .data(data.entities)
375
+ .enter()
376
+ .append('pattern')
377
+ .attr('id', (val, i) => `img-${val.index}`)
378
+ .attr('x', 0)
379
+ .attr('y', 0)
380
+ .attr('height', val => {
381
+ const baseValue = 30;
382
+ return baseValue + val.linksCount * 2;
383
+ })
384
+ .attr('width', val => {
385
+ const baseValue = 30;
386
+ return baseValue + val.linksCount * 2;
387
+ })
388
+ .append('image')
389
+ .attr('x', imageXY)
390
+ .attr('y', imageXY)
391
+ .attr('height', val => {
392
+ const baseValue = 30;
393
+ return baseValue + val.linksCount * 2;
394
+ })
395
+ .attr('width', val => {
396
+ const baseValue = 30;
397
+ return baseValue + val.linksCount * 2;
398
+ })
399
+ .attr('xlink:href', val => val.img);
400
+ };
401
+ /**
402
+ * Creates the force directed chart links.
403
+ * @param svg the svg element
404
+ * @param config the chart configuration
405
+ * @param data the chart data
406
+ * @returns the chart links
407
+ */
408
+ const createLinks = (svg, config, data) => {
409
+ return svg
410
+ .selectAll('.link')
411
+ .data(data.links)
412
+ .enter()
413
+ .append('line')
414
+ .attr('class', 'link')
415
+ .style('stroke', '#000000')
416
+ .style('stroke-width', config.strokeWidth);
417
+ };
418
+ /**
419
+ * Creates the force directed chart forces.
420
+ * @param config the chart configuration
421
+ * @param data the chart data
422
+ * @returns the chart forces
423
+ */
424
+ const createForces = (config, data) => {
425
+ return d3
426
+ .forceSimulation(data.nodes)
427
+ .force('link', d3.forceLink().id(d => d.index ?? 0))
428
+ .force('charge', d3.forceManyBody().strength(config.charge.strength).theta(config.charge.theta).distanceMax(config.charge.distanceMax))
429
+ .force('center', d3.forceCenter(config.width / config.centerCalcMod, config.height / config.centerCalcMod))
430
+ .force('collision', d3.forceCollide().radius(d => config.collisionRadius))
431
+ .force('link', d3
432
+ .forceLink(data.links)
433
+ .id(d => d.index ?? 0)
434
+ .distance(config.distance)
435
+ .links(data.links));
436
+ };
437
+ /**
438
+ * The force directed chart node drag start handler.
439
+ * @param event a drag event
440
+ * @param datum the chart data
441
+ * @param force the chart forces
442
+ */
443
+ const nodeDragStartHandler = (event, datum, force) => {
444
+ if (!event.active && typeof force !== 'undefined') {
445
+ const alphaTarget = 0.3;
446
+ force.alphaTarget(alphaTarget).restart();
447
+ }
448
+ datum.fx = event.x;
449
+ datum.fy = event.y;
450
+ };
451
+ /**
452
+ * The force directed chart node drag handler.
453
+ * @param event a drag event
454
+ * @param datum the chart data
455
+ * @param config the chart configuration
456
+ */
457
+ const nodeDragHandler = (event, datum, config) => {
458
+ datum.fx = event.x > config.margin.left && event.x < config.width + config.margin.right ? event.x : datum.fx;
459
+ datum.fy = event.y > config.margin.top && event.y < config.width + config.margin.bottom ? event.y : datum.fy;
460
+ };
461
+ /**
462
+ * The force directed chart node drag end handler.
463
+ * @param event a drag event
464
+ * @param datum the chart data
465
+ * @param force the chart forces
466
+ */
467
+ const nodeDragEndHandler = (event, datum, force) => {
468
+ if (!event.active && typeof force !== 'undefined') {
469
+ force.alphaTarget(0);
470
+ }
471
+ datum.fx = null;
472
+ datum.fy = null;
473
+ };
474
+ /**
475
+ * Creates the force directed chart nodes.
476
+ * @param svg the svg element
477
+ * @param data the chart data
478
+ * @param force the chart forces
479
+ * @param config the chart configuration
480
+ * @returns the chart nodes
481
+ */
482
+ const createNodes = (svg, data, force, config) => {
483
+ return svg
484
+ .selectAll('.node')
485
+ .data(data.nodes)
486
+ .enter()
487
+ .append('circle')
488
+ .attr('class', 'node')
489
+ .attr('r', val => {
490
+ const base = 5;
491
+ return base + (val.value ?? 0) + (val.linksCount ?? 0) * 2;
492
+ })
493
+ .style('stroke-width', val => {
494
+ const base = 5;
495
+ return base + (val.value ?? 0) + (val.linksCount ?? 0) * 2;
496
+ })
497
+ .style('fill', val => (typeof val.img === 'undefined' || val.img === '' ? '#f00000' : `url(${val.img})`))
498
+ .call(d3
499
+ .drag()
500
+ .on('start', function (event, datum) {
501
+ nodeDragStartHandler(event, datum, force);
502
+ })
503
+ .on('drag', function (event, datum) {
504
+ nodeDragHandler(event, datum, config);
505
+ })
506
+ .on('end', function (event, datum) {
507
+ nodeDragEndHandler(event, datum, force);
508
+ }));
509
+ };
510
+ /**
511
+ * Creates the force directed chart text labels.
512
+ * @param svg the svg element
513
+ * @param data the chart data
514
+ * @returns the chart text labels
515
+ */
516
+ const createText = (svg, data) => {
517
+ return svg
518
+ .append('g')
519
+ .selectAll('text')
520
+ .data(data.nodes)
521
+ .enter()
522
+ .append('text')
523
+ .attr('class', 'legend')
524
+ .text(val => val.name ?? `N/A (id. ${val.index})`);
525
+ };
526
+ /**
527
+ * Draws the force directed chart.
528
+ * @param container the chart container
529
+ * @param data the chart data
530
+ * @param options the chart options
531
+ * @returns the chart configuration
532
+ */
533
+ const drawForceDirectedChart = (container, data, options) => {
534
+ const config = generateConfiguration(defaultForceDirectedChartConfig, options, {});
535
+ const { svg, g } = createContainer$3(container, config);
536
+ applyChartData(g, data);
537
+ const link = createLinks(svg, config, data);
538
+ const force = createForces(config, data);
539
+ const node = createNodes(svg, data, force, config);
540
+ const text = createText(svg, data);
541
+ force.on('tick', () => {
542
+ ticked(link, node, text);
543
+ });
544
+ return config;
545
+ };
546
+
547
+ /**
548
+ * The line chart default configuration.
549
+ */
550
+ const defaultLineChartConfig = Object.freeze({
551
+ chartTitle: '',
552
+ width: 350,
553
+ height: 350,
554
+ margin: {
555
+ top: 70,
556
+ right: 50,
557
+ bottom: 50,
558
+ left: 50,
559
+ },
560
+ transitionDuration: 400,
561
+ dotRadius: 3.5,
562
+ xAxisTitle: '',
563
+ yAxisTitle: '',
564
+ ticks: {
565
+ x: 5,
566
+ y: 10,
567
+ },
568
+ displayAxisLabels: true,
569
+ labelTextWrapWidth: 20,
570
+ color: d3.scaleOrdinal(d3.schemeCategory10),
571
+ });
572
+ /**
573
+ * Creates a container for the line chart.
574
+ * @param container the chart container
575
+ * @param config the chart configuration
576
+ * @returns the object with the svg element and the g element
577
+ */
578
+ const createContainer$2 = (container, config) => {
579
+ const id = container.nativeElement.id ?? 'line-0';
580
+ d3.select(`#${id}`).select('svg').remove();
581
+ const svg = d3
582
+ .select(`#${id}`)
583
+ .append('svg')
584
+ .attr('width', config.width + config.margin.left + config.margin.right)
585
+ .attr('height', config.height + config.margin.top + config.margin.bottom)
586
+ .attr('class', id);
587
+ const g = svg.append('g').attr('transform', `translate(${config.margin.left},${config.margin.top / 2})`);
588
+ return { svg, g };
589
+ };
590
+ /**
591
+ * Wraps the line chart axis labels text.
592
+ * @param svgText the svg text elements
593
+ * @param width the chart axis label width
594
+ */
595
+ const wrapSvgText$1 = (svgText, width) => {
596
+ svgText.each(function () {
597
+ const text = d3.select(this);
598
+ const words = text.text().split(/\s+/).reverse();
599
+ let line = [];
600
+ let lineNumber = 0;
601
+ const lineHeight = 1.4;
602
+ const y = text.attr('y');
603
+ const x = text.attr('x');
604
+ const dy = parseFloat(text.attr('dy') ?? 0);
605
+ let tspan = text.text(null).append('tspan').attr('x', x).attr('y', y).attr('dy', `${dy}em`); // axis label
606
+ let word = words.pop();
607
+ while (typeof word !== 'undefined') {
608
+ line.push(word ?? '');
609
+ tspan.text(line.join(' '));
610
+ if ((tspan.node()?.getComputedTextLength() ?? 0) > width) {
611
+ line.pop();
612
+ tspan.text(line.join(' '));
613
+ line = [word ?? ''];
614
+ lineNumber += 1;
615
+ tspan = text
616
+ .append('tspan')
617
+ .attr('x', 0)
618
+ .attr('y', y)
619
+ .attr('dy', `${lineNumber * lineHeight + dy}em`)
620
+ .text(word ?? '');
621
+ }
622
+ word = words.pop();
623
+ }
624
+ });
625
+ };
626
+ /**
627
+ * Creates the legend.
628
+ * @param g the svg g element
629
+ * @param config the chart configuration
630
+ */
631
+ const createLegend$1 = (g, config) => {
632
+ if (config.displayAxisLabels && config.xAxisTitle !== '') {
633
+ g.append('g')
634
+ .attr('transform', `translate(0, ${config.height + config.margin.bottom})`)
635
+ .append('text')
636
+ .style('font-size', '12px')
637
+ .attr('class', 'legend')
638
+ .attr('dx', '1.5em')
639
+ .attr('dy', '1em')
640
+ .text(`x - ${config.xAxisTitle}`);
641
+ }
642
+ if (config.displayAxisLabels && config.yAxisTitle !== '') {
643
+ g.append('g')
644
+ .attr('transform', `translate(0, ${config.height + config.margin.bottom})`)
645
+ .append('text')
646
+ .style('font-size', '12px')
647
+ .attr('class', 'legend')
648
+ .attr('dx', '1.5em')
649
+ .attr('dy', '2.5em')
650
+ .text(`y - ${config.yAxisTitle}`);
651
+ }
652
+ if (config.chartTitle !== '') {
653
+ g.append('g')
654
+ .attr('transform', `translate(0, 0)`)
655
+ .append('text')
656
+ .style('font-size', '12px')
657
+ .attr('class', 'legend')
658
+ .attr('dx', '1.5em')
659
+ .attr('dy', '-2em')
660
+ .text(config.chartTitle);
661
+ }
662
+ };
663
+ /**
664
+ * Creates the x axis.
665
+ * @param g the svg g element
666
+ * @param x the x axis scale
667
+ * @param config the chart configuration
668
+ */
669
+ const createAxisX = (g, x, config) => {
670
+ const xLabels = g
671
+ .append('g')
672
+ .attr('transform', `translate(0, ${config.height})`)
673
+ .call(d3
674
+ .axisBottom(x)
675
+ .ticks(config.ticks.x)
676
+ .tickFormat(d => {
677
+ const date = new Date(d.valueOf());
678
+ const formattingOffset = 10;
679
+ const day = date.getDate();
680
+ const dd = day < formattingOffset ? `0${day}` : day;
681
+ const month = date.getMonth() + 1;
682
+ const mm = month < formattingOffset ? `0${month}` : month;
683
+ const year = date.getFullYear().toString();
684
+ const yy = year.slice(2);
685
+ const hours = date.getHours();
686
+ const hour = hours < formattingOffset ? `0${hours}` : hours;
687
+ const minutes = date.getMinutes();
688
+ const minute = minutes < formattingOffset ? `0${minutes}` : minutes;
689
+ return `${dd}/${mm}/${yy} ${hour}:${minute}`;
690
+ }))
691
+ .append('text');
692
+ g.selectAll('text').call(wrapSvgText$1, config.labelTextWrapWidth);
693
+ if (config.displayAxisLabels) {
694
+ xLabels.attr('transform', `translate(${config.width}, 0)`).attr('class', 'legend').attr('dx', '1.5em').attr('dy', '0.7em').text('x');
695
+ }
696
+ };
697
+ /**
698
+ * Creates the y axis.
699
+ * @param g the svg g element
700
+ * @param y the y axis scale
701
+ * @param config the chart configuration
702
+ */
703
+ const createAxisY = (g, y, config) => {
704
+ const yLabels = g
705
+ .append('g')
706
+ .call(d3
707
+ .axisLeft(y)
708
+ .ticks(config.ticks.y)
709
+ .tickFormat(d => `${d}`))
710
+ .append('text');
711
+ if (config.displayAxisLabels) {
712
+ yLabels.attr('class', 'legend').attr('dy', '-1.5em').attr('class', 'legend').text('y');
713
+ }
714
+ };
715
+ /**
716
+ * The mouse over event handler.
717
+ * @param self an svg circle element
718
+ * @param d the chart data node
719
+ * @param g the svg g element
720
+ * @param config the chart configuration
721
+ */
722
+ const onMouseOver = (self, d, g, config) => {
723
+ const duration = 400;
724
+ d3.select(self)
725
+ .transition()
726
+ .duration(duration)
727
+ .attr('r', config.dotRadius * 2);
728
+ const tooltipShift = 4;
729
+ const tooltipDy = -10;
730
+ g.append('text')
731
+ .attr('class', 'val')
732
+ .style('font-size', '11px')
733
+ .attr('dx', () => (config.width - config.margin.left - config.margin.right) / tooltipShift)
734
+ .attr('dy', () => tooltipDy)
735
+ .text(() => `${d.value} (${new Date(d.timestamp).toUTCString()})`);
736
+ };
737
+ /**
738
+ * The mouse out event handler.
739
+ * @param self an svg circle element
740
+ * @param config the chart configuration
741
+ */
742
+ const onMouseOut = (self, config) => {
743
+ const duration = 400;
744
+ d3.select(self).attr('class', 'dot');
745
+ d3.select(self).transition().duration(duration).attr('r', config.dotRadius);
746
+ d3.selectAll('.val').remove();
747
+ };
748
+ /**
749
+ * Draws the chart lines, dots, and sets the mouse pointer events.
750
+ * @param g the svg g element
751
+ * @param x the x axis scale
752
+ * @param y the y axis scale
753
+ * @param config the chart configuration
754
+ * @param data the chart data
755
+ */
756
+ const drawLinesDotsAndSetPointerEvents = (g, x, y, config, data) => {
757
+ const line = d3
758
+ .line()
759
+ .x(d => x(d.timestamp))
760
+ .y(d => y(d.value))
761
+ .curve(d3.curveMonotoneX);
762
+ g.append('path').attr('id', 'line').style('fill', 'none').style('stroke', 'red').style('stroke-width', '2px').attr('d', line(data));
763
+ g.selectAll('.dot')
764
+ .data(data)
765
+ .enter()
766
+ .append('circle')
767
+ .attr('class', 'dot')
768
+ .style('pointer-events', 'all')
769
+ .style('fill', (d, i) => config.color(i.toString()))
770
+ .on('mouseover', function (event, d) {
771
+ return onMouseOver(this, d, g, config);
772
+ })
773
+ .on('mouseout', function () {
774
+ return onMouseOut(this, config);
775
+ })
776
+ .attr('cx', function (d) {
777
+ return x(d.timestamp);
778
+ })
779
+ .attr('cy', function (d) {
780
+ return y(d.value);
781
+ })
782
+ .attr('r', 0)
783
+ .transition()
784
+ .ease(d3.easeLinear)
785
+ .duration(config.transitionDuration)
786
+ .delay((d, i) => {
787
+ const multiplier = 50;
788
+ return i * multiplier;
789
+ })
790
+ .attr('r', config.dotRadius);
791
+ };
792
+ /**
793
+ * Draws the line chart.
794
+ * @param container the chart container
795
+ * @param data the chart data
796
+ * @param options the chart options
797
+ * @returns the chart configuration
798
+ */
799
+ const drawLineChart = (container, data, options) => {
800
+ const config = generateConfiguration(defaultLineChartConfig, options, {});
801
+ const { g } = createContainer$2(container, config);
802
+ const x = d3.scaleTime([0, config.width]).domain([Math.min(...data.map(d => d.timestamp)), Math.max(...data.map(d => d.timestamp))]);
803
+ const y = d3.scaleLinear([config.height, 0]).domain([0, d3.max(data, d => d.value) ?? 1]);
804
+ createAxisX(g, x, config);
805
+ createAxisY(g, y, config);
806
+ createLegend$1(g, config);
807
+ drawLinesDotsAndSetPointerEvents(g, x, y, config, data);
808
+ return config;
809
+ };
810
+
811
+ /**
812
+ * The pie chart default configuration.
813
+ */
814
+ const defaultPieChartConfig = Object.freeze({
815
+ chartTitle: '',
816
+ width: 600,
817
+ height: 600,
818
+ margin: {
819
+ top: 20,
820
+ right: 20,
821
+ bottom: 20,
822
+ left: 20,
823
+ },
824
+ innerRadius: 0,
825
+ labelRadiusModifier: 50,
826
+ labelTextWrapWidth: 60,
827
+ color: d3.scaleOrdinal(d3.schemeCategory10),
828
+ });
829
+ /**
830
+ * Creates a container for the pie chart.
831
+ * @param container the chart container
832
+ * @param config the chart configuration
833
+ * @returns the object with the svg element and the g element
834
+ */
835
+ const createContainer$1 = (container, config) => {
836
+ const id = container.nativeElement.id ?? 'pie-0';
837
+ d3.select(`#${id}`).select('svg').remove();
838
+ const svg = d3
839
+ .select(`#${id}`)
840
+ .append('svg')
841
+ .attr('width', config.width + config.margin.left + config.margin.right)
842
+ .attr('height', config.height + config.margin.top + config.margin.bottom)
843
+ .attr('class', id);
844
+ const g = svg
845
+ .append('g')
846
+ .attr('transform', `translate(${config.width / 2 + config.margin.left},${config.height / 2 + config.margin.top})`);
847
+ return { svg, g };
848
+ };
849
+ /**
850
+ * Draws the pie chart.
851
+ * @param container the chart container
852
+ * @param data the chart data
853
+ * @param options the chart options
854
+ * @returns the chart configuration
855
+ */
856
+ const drawPieChart = (container, data, options) => {
857
+ const config = generateConfiguration(defaultPieChartConfig, options, {});
858
+ const { g } = createContainer$1(container, config);
859
+ const pie = d3.pie().value(datum => datum.y);
860
+ const radius = Math.min(config.width, config.height) / 2;
861
+ const arc = d3.arc().innerRadius(config.innerRadius).outerRadius(radius);
862
+ const arcs = g
863
+ .selectAll('arc')
864
+ .data(pie(data))
865
+ .enter()
866
+ .append('g')
867
+ .attr('class', 'arc')
868
+ .on('mouseover', function (event, d) {
869
+ d3.select('#tooltip')
870
+ .style('left', `${event.pageX}px`)
871
+ .style('top', `${event.pageY}px`)
872
+ .style('opacity', 1)
873
+ .select('#value')
874
+ .text(d.value);
875
+ })
876
+ .on('mouseout', function (event, d) {
877
+ d3.select('#tooltip').style('opacity', 0);
878
+ });
879
+ const label = d3
880
+ .arc()
881
+ .innerRadius(radius)
882
+ .outerRadius(radius + config.labelRadiusModifier);
883
+ arcs
884
+ .append('path')
885
+ .attr('fill', (d, i) => config.color(i.toString()))
886
+ .attr('d', arc);
887
+ const textDy = 5;
888
+ arcs
889
+ .append('text')
890
+ .attr('class', 'legend')
891
+ .attr('text-anchor', 'middle')
892
+ .attr('dy', textDy)
893
+ .attr('transform', d => `translate(${label.centroid(d)})`)
894
+ .text(d => d.data.y);
895
+ return config;
896
+ };
897
+
898
+ /**
899
+ * The radar chart default configuration.
900
+ */
901
+ const defaultRadarChartConfig = Object.freeze({
902
+ chartTitle: '',
903
+ width: 350,
904
+ height: 350,
905
+ margin: {
906
+ top: 50,
907
+ right: 50,
908
+ bottom: 50,
909
+ left: 50,
910
+ },
911
+ levels: 3,
912
+ maxValue: 0,
913
+ lineFactor: 1.1,
914
+ labelFactor: 1.15,
915
+ labelTextWrapWidth: 60,
916
+ opacityArea: 0.35,
917
+ dotRadius: 4,
918
+ opacityCircles: 0.1,
919
+ strokeWidth: 2,
920
+ roundStrokes: false,
921
+ transitionDuration: 200,
922
+ color: d3.scaleOrdinal(d3.schemeCategory10),
923
+ });
924
+ /**
925
+ * Creates a container for the radar chart.
926
+ * @param container the chart container
927
+ * @param config the chart configuration
928
+ * @returns the object with the svg element and the g element
929
+ */
930
+ const createContainer = (container, config) => {
931
+ const id = container.nativeElement.id ?? 'radar-0';
932
+ d3.select(`#${id}`).select('svg').remove();
933
+ const svg = d3
934
+ .select(`#${id}`)
935
+ .append('svg')
936
+ .attr('width', config.width + config.margin.left + config.margin.right)
937
+ .attr('height', config.height + config.margin.top + config.margin.bottom)
938
+ .attr('class', id);
939
+ const g = svg
940
+ .append('g')
941
+ .attr('transform', `translate(${config.width / 2 + config.margin.left},${config.height / 2 + config.margin.top})`);
942
+ return { svg, g };
943
+ };
944
+ /**
945
+ * Draws the radar chart circular grid.
946
+ * @param axisGrid the chart axis grid
947
+ * @param radius the chart radius value
948
+ * @param maxValue the maximum value of the chart axis
949
+ * @param config the chart configuration
950
+ */
951
+ const drawCircularGrid = (axisGrid, radius, maxValue, config) => {
952
+ // background circles
953
+ axisGrid
954
+ .selectAll('.levels')
955
+ .data(d3.range(1, config.levels + 1).reverse())
956
+ .enter()
957
+ .append('circle')
958
+ .attr('class', 'grid-circle')
959
+ .attr('r', (d, i) => (radius / config.levels) * d)
960
+ .style('fill', '#CDCDCD')
961
+ .style('stroke', '#CDCDCD')
962
+ .style('fill-opacity', config.opacityCircles)
963
+ .style('filter', 'url(#glow)');
964
+ // text indicating at what % each level is
965
+ const axisGridX = 4;
966
+ axisGrid
967
+ .selectAll('.axis-label')
968
+ .data(d3.range(1, config.levels + 1).reverse())
969
+ .enter()
970
+ .append('text')
971
+ .attr('class', 'axis-label')
972
+ .attr('x', axisGridX)
973
+ .attr('y', d => (-d * radius) / config.levels)
974
+ .attr('dy', '0.4em')
975
+ .style('font-size', '10px')
976
+ .attr('fill', '#737373')
977
+ .text((d, i) => (maxValue * d) / config.levels);
978
+ };
979
+ /**
980
+ * Wraps the chart axis labels text.
981
+ * @param svgText the svg text elements
982
+ * @param width the chart axis label width
983
+ */
984
+ const wrapSvgText = (svgText, width) => {
985
+ svgText.each(function () {
986
+ const text = d3.select(this);
987
+ const words = text.text().split(/\s+/).reverse();
988
+ let line = [];
989
+ let lineNumber = 0;
990
+ const lineHeight = 1.4;
991
+ const y = text.attr('y');
992
+ const x = text.attr('x');
993
+ const dy = parseFloat(text.attr('dy') ?? 0);
994
+ let tspan = text.text(null).append('tspan').attr('x', x).attr('y', y).attr('dy', `${dy}em`);
995
+ let word = words.pop();
996
+ while (typeof word !== 'undefined') {
997
+ line.push(word ?? '');
998
+ tspan.text(line.join(' '));
999
+ if ((tspan.node()?.getComputedTextLength() ?? 0) > width) {
1000
+ line.pop();
1001
+ tspan.text(line.join(' '));
1002
+ line = [word ?? ''];
1003
+ lineNumber += 1;
1004
+ tspan = text
1005
+ .append('tspan')
1006
+ .attr('x', x)
1007
+ .attr('y', y)
1008
+ .attr('dy', `${lineNumber * lineHeight + dy}em`)
1009
+ .text(word ?? '');
1010
+ }
1011
+ word = words.pop();
1012
+ }
1013
+ });
1014
+ };
1015
+ /**
1016
+ * Creates the legend.
1017
+ * @param g the svg g element
1018
+ * @param config the chart configuration
1019
+ */
1020
+ const createLegend = (g, config) => {
1021
+ if (config.chartTitle !== '') {
1022
+ g.append('g')
1023
+ .attr('transform', `translate(-${config.width / 2 + config.margin.left / 2}, -${config.height / 2 + config.margin.top / 2})`)
1024
+ .append('text')
1025
+ .style('font-size', '12px')
1026
+ .attr('class', 'legend')
1027
+ .text(config.chartTitle);
1028
+ }
1029
+ };
1030
+ /**
1031
+ * Draws the radar chart axis.
1032
+ * @param axisGrid the chart axis grid
1033
+ * @param axisNames the chart axis names
1034
+ * @param radiusScale the chart radius scale
1035
+ * @param maxValue the maximum value of the chart axis
1036
+ * @param angleSlice the chart angle slice value
1037
+ * @param config the chart configuration
1038
+ */
1039
+ const drawAxis = (axisGrid, axisNames, radiusScale, maxValue, angleSlice, config) => {
1040
+ // create the straight lines radiating outward from the center
1041
+ const axis = axisGrid.selectAll('.axis').data(axisNames).enter().append('g').attr('class', 'axis');
1042
+ // append the lines
1043
+ axis
1044
+ .append('line')
1045
+ .attr('x1', 0)
1046
+ .attr('y1', 0)
1047
+ .attr('x2', (d, i) => radiusScale(maxValue * config.lineFactor) * Math.cos(angleSlice * i - Math.PI / 2))
1048
+ .attr('y2', (d, i) => radiusScale(maxValue * config.lineFactor) * Math.sin(angleSlice * i - Math.PI / 2))
1049
+ .attr('class', 'line')
1050
+ .style('stroke', 'white')
1051
+ .style('stroke-width', '2px');
1052
+ // append the labels at each axis
1053
+ axis
1054
+ .append('text')
1055
+ .attr('class', 'legend')
1056
+ .style('font-size', '11px')
1057
+ .attr('text-anchor', 'middle')
1058
+ .attr('dy', '0.35em')
1059
+ .attr('x', (d, i) => radiusScale(maxValue * config.labelFactor) * Math.cos(angleSlice * i - Math.PI / 2))
1060
+ .attr('y', (d, i) => radiusScale(maxValue * config.labelFactor) * Math.sin(angleSlice * i - Math.PI / 2))
1061
+ .text(d => d)
1062
+ .call(wrapSvgText, config.labelTextWrapWidth);
1063
+ };
1064
+ /**
1065
+ * Draws the radar chart blobs.
1066
+ * @param radiusScale the chart radius scale
1067
+ * @param angleSlice the chart angle slice value
1068
+ * @param g the svg g element
1069
+ * @param data the chart data
1070
+ * @param config the chart configuration
1071
+ */
1072
+ const drawRadarChartBlobs = (radiusScale, angleSlice, g, data, config) => {
1073
+ // the radial line function
1074
+ const radarLine = d3
1075
+ .lineRadial()
1076
+ .radius(d => radiusScale(d.value))
1077
+ .angle((d, i) => i * angleSlice);
1078
+ // create a wrapper for the blobs
1079
+ const blobWrapper = g.selectAll('.radar-wrapper').data(data).enter().append('g').attr('class', 'radar-wrapper');
1080
+ // append the backgrounds
1081
+ blobWrapper
1082
+ .append('path')
1083
+ .attr('class', 'radar-area')
1084
+ .attr('d', (d, i) => radarLine(d))
1085
+ .style('fill', (d, i) => config.color(i.toString()))
1086
+ .style('fill-opacity', config.opacityArea)
1087
+ .on('mouseover', function (d, i) {
1088
+ // dim all blobs
1089
+ const radarAreaFillOpacity = 0.1;
1090
+ d3.selectAll('.radar-area').transition().duration(config.transitionDuration).style('fill-opacity', radarAreaFillOpacity);
1091
+ // bring back the hovered over blob
1092
+ const fillOpacity = 0.7;
1093
+ d3.select(this).transition().duration(config.transitionDuration).style('fill-opacity', fillOpacity);
1094
+ })
1095
+ .on('mouseout', () => {
1096
+ // bring back all blobs
1097
+ d3.selectAll('.radar-area').transition().duration(config.transitionDuration).style('fill-opacity', config.opacityArea);
1098
+ });
1099
+ // create the outlines
1100
+ blobWrapper
1101
+ .append('path')
1102
+ .attr('class', 'radar-stroke')
1103
+ .attr('d', (d, i) => radarLine(d))
1104
+ .style('stroke-width', `${config.strokeWidth}px`)
1105
+ .style('stroke', (d, i) => config.color(i.toString()))
1106
+ .style('fill', 'none')
1107
+ .style('filter', 'url(#glow)');
1108
+ // append the circles
1109
+ const blobWrapperFillOpacity = 0.8;
1110
+ blobWrapper
1111
+ .selectAll('.radar-circle')
1112
+ .data((d, i) => d)
1113
+ .enter()
1114
+ .append('circle')
1115
+ .attr('class', 'radar-circle')
1116
+ .attr('r', config.dotRadius)
1117
+ .attr('cx', (d, i) => radiusScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2))
1118
+ .attr('cy', (d, i) => radiusScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2))
1119
+ .style('fill', (d, i, j) => config.color(j.toString()))
1120
+ .style('fill-opacity', blobWrapperFillOpacity);
1121
+ };
1122
+ /**
1123
+ * Appends the invisible tooltip circles.
1124
+ * @param g the svg g element
1125
+ * @param data the chart data
1126
+ * @param radiusScale the chart radius scale
1127
+ * @param angleSlice the chart angle slice value
1128
+ * @param config the chart configuration
1129
+ */
1130
+ const appendInvisibleTooltipCircles = (g, data, radiusScale, angleSlice, config) => {
1131
+ // wrapper for the invisible circles on top
1132
+ const blobCircleWrapper = g.selectAll('.radar-circle-wrapper').data(data).enter().append('g').attr('class', 'radar-circle-wrapper');
1133
+ // set up the small tooltip for when you hover over a circle
1134
+ const tooltip = g.append('text').attr('class', 'tooltip').style('opacity', 0);
1135
+ // append a set of invisible circles on top for the mouseover pop-up
1136
+ const blobCircleWrapperRadiusMultiplier = 1.5;
1137
+ blobCircleWrapper
1138
+ .selectAll('.radar-invisible-circle')
1139
+ .data((d, i) => d)
1140
+ .enter()
1141
+ .append('circle')
1142
+ .attr('class', 'radar-invisible-circle')
1143
+ .attr('r', config.dotRadius * blobCircleWrapperRadiusMultiplier)
1144
+ .attr('cx', (d, i) => radiusScale(d.value) * Math.cos(angleSlice * i - Math.PI / 2))
1145
+ .attr('cy', (d, i) => radiusScale(d.value) * Math.sin(angleSlice * i - Math.PI / 2))
1146
+ .style('fill', 'none')
1147
+ .style('pointer-events', 'all')
1148
+ .on('mouseover', function (event, i) {
1149
+ const modifier = 10;
1150
+ const newX = parseFloat(d3.select(this).attr('cx')) - modifier;
1151
+ const newY = parseFloat(d3.select(this).attr('cy')) - modifier;
1152
+ const nodeData = event.target['__data__'];
1153
+ const tooltipText = `${nodeData.value} ${nodeData.unit}`;
1154
+ tooltip.attr('x', newX).attr('y', newY).text(tooltipText).transition().duration(config.transitionDuration).style('opacity', 1);
1155
+ })
1156
+ .on('mouseout', () => {
1157
+ tooltip.transition().duration(config.transitionDuration).style('opacity', 0);
1158
+ });
1159
+ };
1160
+ /**
1161
+ * Draws the radar chart.
1162
+ * @param container the chart container
1163
+ * @param data the chart data
1164
+ * @param options the chart options
1165
+ * @returns the hart configuration
1166
+ */
1167
+ const drawRadarChart = (container, data, options) => {
1168
+ const config = generateConfiguration(defaultRadarChartConfig, options, {});
1169
+ const maxValue = Math.max(config.maxValue, d3.max(data, i => d3.max(i.map(o => o.value))) ?? 0);
1170
+ const axisNames = data[0].map((i, j) => i.axis);
1171
+ const totalAxis = axisNames.length;
1172
+ const radius = Math.min(config.width / 2 - config.margin.left / 2, config.height / 2 - config.margin.top / 2);
1173
+ const angleSlice = (Math.PI * 2) / totalAxis;
1174
+ const radiusScale = d3.scaleLinear([0, radius]).domain([0, maxValue]);
1175
+ const { g } = createContainer(container, config);
1176
+ // filter for the outside glow
1177
+ const filter = g.append('defs').append('filter').attr('id', 'glow');
1178
+ filter.append('feGaussianBlur').attr('stdDeviation', '2.5').attr('result', 'coloredBlur');
1179
+ const feMerge = filter.append('feMerge');
1180
+ feMerge.append('feMergeNode').attr('in', 'coloredBlur');
1181
+ feMerge.append('feMergeNode').attr('in', 'SourceGraphic');
1182
+ const axisGrid = g.append('g').attr('class', 'axis-wrapper');
1183
+ drawCircularGrid(axisGrid, radius, maxValue, config);
1184
+ drawAxis(axisGrid, axisNames, radiusScale, maxValue, angleSlice, config);
1185
+ createLegend(g, config);
1186
+ drawRadarChartBlobs(radiusScale, angleSlice, g, data, config);
1187
+ appendInvisibleTooltipCircles(g, data, radiusScale, angleSlice, config);
1188
+ return config;
1189
+ };
1190
+
1191
+ const d3ChartFactory = () => ({
1192
+ drawPieChart,
1193
+ drawRadarChart,
1194
+ drawBarChart,
1195
+ drawLineChart,
1196
+ drawForceDirectedChart,
1197
+ });
1198
+ const D3_CHART_FACTORY = new InjectionToken('D3_CHART_FACTORY', {
1199
+ providedIn: 'root',
1200
+ factory: d3ChartFactory,
1201
+ });
1202
+
1203
+ class AppBarChartComponent {
1204
+ constructor(doc, d3Factory) {
1205
+ this.doc = doc;
1206
+ this.d3Factory = d3Factory;
1207
+ /**
1208
+ * The chart id.
1209
+ */
1210
+ this.chartId = 'bar-0';
1211
+ /**
1212
+ * The chart data.
1213
+ */
1214
+ this.data = [];
1215
+ /**
1216
+ * The chart options.
1217
+ */
1218
+ this.options = {};
1219
+ }
1220
+ /**
1221
+ * The chart options constructor.
1222
+ * @returns chart options
1223
+ */
1224
+ chartOptions() {
1225
+ const bodyWidthAdjustment = 10;
1226
+ const width = Math.min(this.options.width ?? defaultBarChartConfig.width, this.doc.body.clientWidth - defaultBarChartConfig.margin.left - defaultBarChartConfig.margin.right - bodyWidthAdjustment);
1227
+ const height = Math.min(this.options.height ?? width, this.doc.body.clientWidth - defaultBarChartConfig.margin.top - defaultBarChartConfig.margin.bottom - bodyWidthAdjustment);
1228
+ const yAxisTicks = Math.max(...this.data.map(item => item.value));
1229
+ const options = {
1230
+ width,
1231
+ height,
1232
+ yAxisTicks,
1233
+ ...this.options,
1234
+ };
1235
+ return options;
1236
+ }
1237
+ /**
1238
+ * Draws the chart.
1239
+ */
1240
+ drawChart() {
1241
+ if (typeof this.container !== 'undefined') {
1242
+ const options = this.chartOptions();
1243
+ this.d3Factory.drawBarChart(this.container, this.data, options);
1244
+ }
1245
+ }
1246
+ /**
1247
+ * Actually draws the chart after the component view is initialized.
1248
+ */
1249
+ ngAfterViewInit() {
1250
+ this.drawChart();
1251
+ }
1252
+ /**
1253
+ * Redraws the chart on changes.
1254
+ */
1255
+ ngOnChanges(changes) {
1256
+ const data = changes.data?.currentValue;
1257
+ const options = changes.options?.currentValue;
1258
+ if ((typeof data !== 'undefined' && data !== null) || (typeof options !== 'undefined' && options !== null)) {
1259
+ this.drawChart();
1260
+ }
1261
+ }
1262
+ }
1263
+ /** @nocollapse */ AppBarChartComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppBarChartComponent, deps: [{ token: DOCUMENT }, { token: D3_CHART_FACTORY }], target: i0.ɵɵFactoryTarget.Component });
1264
+ /** @nocollapse */ AppBarChartComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: AppBarChartComponent, selector: "app-bar-chart", inputs: { chartId: "chartId", data: "data", options: "options" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"container\" id=\"{{ chartId }}\" #container></div>\n", styles: [":host{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center}:host .container{flex:0 1 auto}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1265
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppBarChartComponent, decorators: [{
1266
+ type: Component,
1267
+ args: [{ selector: 'app-bar-chart', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"container\" id=\"{{ chartId }}\" #container></div>\n", styles: [":host{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center}:host .container{flex:0 1 auto}\n"] }]
1268
+ }], ctorParameters: function () { return [{ type: Document, decorators: [{
1269
+ type: Inject,
1270
+ args: [DOCUMENT]
1271
+ }] }, { type: undefined, decorators: [{
1272
+ type: Inject,
1273
+ args: [D3_CHART_FACTORY]
1274
+ }] }]; }, propDecorators: { chartId: [{
1275
+ type: Input
1276
+ }], data: [{
1277
+ type: Input
1278
+ }], options: [{
1279
+ type: Input
1280
+ }], container: [{
1281
+ type: ViewChild,
1282
+ args: ['container']
1283
+ }] } });
1284
+
1285
+ class AppPieChartComponent {
1286
+ constructor(doc, d3Factory) {
1287
+ this.doc = doc;
1288
+ this.d3Factory = d3Factory;
1289
+ /**
1290
+ * The chart id.
1291
+ */
1292
+ this.chartId = 'pie-0';
1293
+ /**
1294
+ * The chart data.
1295
+ */
1296
+ this.data = [];
1297
+ /**
1298
+ * The chart options.
1299
+ */
1300
+ this.options = {};
1301
+ }
1302
+ /**
1303
+ * The chart options constructor.
1304
+ * @returns chart options
1305
+ */
1306
+ chartOptions() {
1307
+ const margin = { top: 50, right: 50, bottom: 50, left: 50 };
1308
+ const minWidth = 350;
1309
+ const modifiers = {
1310
+ width: 10,
1311
+ height: 20,
1312
+ };
1313
+ const width = Math.min(minWidth, this.doc.body.clientWidth - modifiers.width) - margin.left - margin.right;
1314
+ const height = Math.min(width, this.doc.body.clientHeight - margin.top - margin.bottom - modifiers.height);
1315
+ const options = {
1316
+ width,
1317
+ height,
1318
+ margin,
1319
+ ...this.options,
1320
+ };
1321
+ return options;
1322
+ }
1323
+ /**
1324
+ * Draws the chart.
1325
+ */
1326
+ drawChart() {
1327
+ if (typeof this.container !== 'undefined') {
1328
+ const options = this.chartOptions();
1329
+ this.d3Factory.drawPieChart(this.container, this.data, options);
1330
+ }
1331
+ }
1332
+ /**
1333
+ * Actually draws the chart after the component view is initialized.
1334
+ */
1335
+ ngAfterViewInit() {
1336
+ this.drawChart();
1337
+ }
1338
+ /**
1339
+ * Redraws the chart on changes.
1340
+ */
1341
+ ngOnChanges(changes) {
1342
+ const data = changes.data?.currentValue;
1343
+ const options = changes.options?.currentValue;
1344
+ if ((typeof data !== 'undefined' && data !== null) || (typeof options !== 'undefined' && options !== null)) {
1345
+ this.drawChart();
1346
+ }
1347
+ }
1348
+ }
1349
+ /** @nocollapse */ AppPieChartComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppPieChartComponent, deps: [{ token: DOCUMENT }, { token: D3_CHART_FACTORY }], target: i0.ɵɵFactoryTarget.Component });
1350
+ /** @nocollapse */ AppPieChartComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: AppPieChartComponent, selector: "app-pie-chart", inputs: { chartId: "chartId", data: "data", options: "options" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"container\">\n <div id=\"{{ chartId }}\" #container></div>\n\n <small class=\"container--chart-title\">{{ options.chartTitle }}</small>\n</div>\n", styles: [":host{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center}:host canvas{width:auto!important;height:150px}:host .container{flex:0 1 auto}:host .container--chart-title{display:block;text-align:center}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1351
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppPieChartComponent, decorators: [{
1352
+ type: Component,
1353
+ args: [{ selector: 'app-pie-chart', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"container\">\n <div id=\"{{ chartId }}\" #container></div>\n\n <small class=\"container--chart-title\">{{ options.chartTitle }}</small>\n</div>\n", styles: [":host{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center}:host canvas{width:auto!important;height:150px}:host .container{flex:0 1 auto}:host .container--chart-title{display:block;text-align:center}\n"] }]
1354
+ }], ctorParameters: function () { return [{ type: Document, decorators: [{
1355
+ type: Inject,
1356
+ args: [DOCUMENT]
1357
+ }] }, { type: undefined, decorators: [{
1358
+ type: Inject,
1359
+ args: [D3_CHART_FACTORY]
1360
+ }] }]; }, propDecorators: { chartId: [{
1361
+ type: Input
1362
+ }], data: [{
1363
+ type: Input
1364
+ }], options: [{
1365
+ type: Input
1366
+ }], container: [{
1367
+ type: ViewChild,
1368
+ args: ['container']
1369
+ }] } });
1370
+
1371
+ class AppRadarChartComponent {
1372
+ constructor(doc, d3Factory) {
1373
+ this.doc = doc;
1374
+ this.d3Factory = d3Factory;
1375
+ /**
1376
+ * The chart id.
1377
+ */
1378
+ this.chartId = 'radar-0';
1379
+ /**
1380
+ * The chart data.
1381
+ */
1382
+ this.data = [[]];
1383
+ /**
1384
+ * The chart options.
1385
+ */
1386
+ this.options = {};
1387
+ }
1388
+ /**
1389
+ * The chart options constructor.
1390
+ * @returns chart options
1391
+ */
1392
+ chartOptions() {
1393
+ const xsOffset = 500;
1394
+ const smOffset = 800;
1395
+ const mdOffset = 1024;
1396
+ const labelFactorDefault = 1.15;
1397
+ const labelFactorMd = 1.15;
1398
+ const labelFactorSm = 1.15;
1399
+ const labelFactorXs = 1.4;
1400
+ const wrapWidthDefault = 85;
1401
+ const wrapWidthMd = 80;
1402
+ const wrapWidthXs = 70;
1403
+ const bodyWidthAdjustment = 10;
1404
+ const width = Math.min(this.options.width ?? defaultRadarChartConfig.width, this.doc.body.clientWidth - defaultRadarChartConfig.margin.left - defaultRadarChartConfig.margin.right - bodyWidthAdjustment);
1405
+ const height = Math.min(width, this.doc.body.clientHeight - defaultRadarChartConfig.margin.top - defaultRadarChartConfig.margin.bottom - bodyWidthAdjustment);
1406
+ const labelFactor = width <= xsOffset ? labelFactorXs : width <= smOffset ? labelFactorSm : width <= mdOffset ? labelFactorMd : labelFactorDefault;
1407
+ const labelTextWrapWidth = width <= xsOffset ? wrapWidthXs : width <= mdOffset ? wrapWidthMd : wrapWidthDefault;
1408
+ const options = {
1409
+ width,
1410
+ height,
1411
+ maxValue: this.data[0].reduce((accumulator, item) => (item.value > accumulator ? item.value : accumulator), 0) + 1,
1412
+ levels: 5,
1413
+ roundStrokes: true,
1414
+ labelFactor,
1415
+ labelTextWrapWidth,
1416
+ ...this.options,
1417
+ };
1418
+ return options;
1419
+ }
1420
+ /**
1421
+ * Draws the chart.
1422
+ */
1423
+ drawChart() {
1424
+ if (typeof this.container !== 'undefined') {
1425
+ const options = this.chartOptions();
1426
+ this.d3Factory.drawRadarChart(this.container, this.data, options);
1427
+ }
1428
+ }
1429
+ /**
1430
+ * Actually draws the chart after the component view is initialized.
1431
+ */
1432
+ ngAfterViewInit() {
1433
+ this.drawChart();
1434
+ }
1435
+ /**
1436
+ * Redraws the chart on changes.
1437
+ */
1438
+ ngOnChanges(changes) {
1439
+ const currentValue = changes.data?.currentValue;
1440
+ const options = changes.options?.currentValue;
1441
+ if ((typeof currentValue !== 'undefined' && currentValue !== null) || (typeof options !== 'undefined' && options !== null)) {
1442
+ this.drawChart();
1443
+ }
1444
+ }
1445
+ }
1446
+ /** @nocollapse */ AppRadarChartComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppRadarChartComponent, deps: [{ token: DOCUMENT }, { token: D3_CHART_FACTORY }], target: i0.ɵɵFactoryTarget.Component });
1447
+ /** @nocollapse */ AppRadarChartComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: AppRadarChartComponent, selector: "app-radar-chart", inputs: { chartId: "chartId", data: "data", options: "options" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"container\" id=\"{{ chartId }}\" #container></div>\n", styles: [":host{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center}:host .container{flex:0 1 auto}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1448
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppRadarChartComponent, decorators: [{
1449
+ type: Component,
1450
+ args: [{ selector: 'app-radar-chart', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"container\" id=\"{{ chartId }}\" #container></div>\n", styles: [":host{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center}:host .container{flex:0 1 auto}\n"] }]
1451
+ }], ctorParameters: function () { return [{ type: Document, decorators: [{
1452
+ type: Inject,
1453
+ args: [DOCUMENT]
1454
+ }] }, { type: undefined, decorators: [{
1455
+ type: Inject,
1456
+ args: [D3_CHART_FACTORY]
1457
+ }] }]; }, propDecorators: { chartId: [{
1458
+ type: Input
1459
+ }], data: [{
1460
+ type: Input
1461
+ }], options: [{
1462
+ type: Input
1463
+ }], container: [{
1464
+ type: ViewChild,
1465
+ args: ['container']
1466
+ }] } });
1467
+
1468
+ class AppForceDirectedChartComponent {
1469
+ constructor(doc, d3Factory) {
1470
+ this.doc = doc;
1471
+ this.d3Factory = d3Factory;
1472
+ /**
1473
+ * The chart identifier.
1474
+ */
1475
+ this.chartId = 'force-0';
1476
+ /**
1477
+ * The chart data.
1478
+ */
1479
+ this.data = {
1480
+ domains: [],
1481
+ entities: [],
1482
+ links: [],
1483
+ nodes: [],
1484
+ };
1485
+ /**
1486
+ * The chart options.
1487
+ */
1488
+ this.options = {};
1489
+ }
1490
+ /**
1491
+ * The chart options constructor.
1492
+ * @returns chart options
1493
+ */
1494
+ chartOptions() {
1495
+ const margin = { top: 50, right: 50, bottom: 50, left: 50 };
1496
+ const minWidth = 350;
1497
+ const modifiers = {
1498
+ width: 10,
1499
+ height: 20,
1500
+ };
1501
+ const width = Math.min(minWidth, this.doc.body.clientWidth - modifiers.width) - margin.left - margin.right;
1502
+ const height = Math.min(width, this.doc.body.clientHeight - margin.top - margin.bottom - modifiers.height);
1503
+ const options = { width, height, margin, ...this.options };
1504
+ return options;
1505
+ }
1506
+ /**
1507
+ * Draws the chart.
1508
+ */
1509
+ drawChart() {
1510
+ if (typeof this.container !== 'undefined') {
1511
+ const options = this.chartOptions();
1512
+ this.d3Factory.drawForceDirectedChart(this.container, this.data, options);
1513
+ }
1514
+ }
1515
+ /**
1516
+ * Actually draws the chart after the component view is initialized.
1517
+ */
1518
+ ngAfterViewInit() {
1519
+ this.drawChart();
1520
+ }
1521
+ /**
1522
+ * Redraws the chart on changes.
1523
+ */
1524
+ ngOnChanges(changes) {
1525
+ const prevData = changes.data?.previousValue;
1526
+ const nextData = changes.data?.currentValue;
1527
+ const options = changes.options?.currentValue;
1528
+ if ((Boolean(changes.data?.currentValue) && (prevData?.nodes ?? []).length !== (nextData?.nodes ?? []).length) ||
1529
+ (typeof options !== 'undefined' && options !== null)) {
1530
+ this.drawChart();
1531
+ }
1532
+ }
1533
+ }
1534
+ /** @nocollapse */ AppForceDirectedChartComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppForceDirectedChartComponent, deps: [{ token: DOCUMENT }, { token: D3_CHART_FACTORY }], target: i0.ɵɵFactoryTarget.Component });
1535
+ /** @nocollapse */ AppForceDirectedChartComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: AppForceDirectedChartComponent, selector: "app-force-directed-chart", inputs: { chartId: "chartId", data: "data", options: "options" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"container\">\n <div id=\"{{ chartId }}\" #container></div>\n\n <small class=\"container--chart-title\">{{ options.chartTitle }}</small>\n</div>\n", styles: [":host{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center}:host .container{flex:0 1 auto}:host .container--chart-title{display:block;text-align:center}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1536
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppForceDirectedChartComponent, decorators: [{
1537
+ type: Component,
1538
+ args: [{ selector: 'app-force-directed-chart', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"container\">\n <div id=\"{{ chartId }}\" #container></div>\n\n <small class=\"container--chart-title\">{{ options.chartTitle }}</small>\n</div>\n", styles: [":host{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center}:host .container{flex:0 1 auto}:host .container--chart-title{display:block;text-align:center}\n"] }]
1539
+ }], ctorParameters: function () { return [{ type: Document, decorators: [{
1540
+ type: Inject,
1541
+ args: [DOCUMENT]
1542
+ }] }, { type: undefined, decorators: [{
1543
+ type: Inject,
1544
+ args: [D3_CHART_FACTORY]
1545
+ }] }]; }, propDecorators: { chartId: [{
1546
+ type: Input
1547
+ }], data: [{
1548
+ type: Input
1549
+ }], options: [{
1550
+ type: Input
1551
+ }], container: [{
1552
+ type: ViewChild,
1553
+ args: ['container']
1554
+ }] } });
1555
+
1556
+ class AppLineChartComponent {
1557
+ constructor(doc, d3Factory) {
1558
+ this.doc = doc;
1559
+ this.d3Factory = d3Factory;
1560
+ /**
1561
+ * The chart id.
1562
+ */
1563
+ this.chartId = 'line-0';
1564
+ /**
1565
+ * The chart data.
1566
+ */
1567
+ this.data = [];
1568
+ /**
1569
+ * The chart options.
1570
+ */
1571
+ this.options = {};
1572
+ }
1573
+ /**
1574
+ * The chart options constructor.
1575
+ * @returns chart options
1576
+ */
1577
+ chartOptions() {
1578
+ const bodyWidthAdjustment = 10;
1579
+ const width = Math.min(this.options.width ?? defaultLineChartConfig.width, this.doc.body.clientWidth - defaultLineChartConfig.margin.left - defaultLineChartConfig.margin.right - bodyWidthAdjustment);
1580
+ const xTicksScale = 50;
1581
+ const ticks = {
1582
+ x: width / xTicksScale,
1583
+ y: Math.max(...this.data.map(item => item.value)),
1584
+ };
1585
+ const height = Math.min(this.options.height ?? width, this.doc.body.clientWidth - defaultLineChartConfig.margin.top - defaultLineChartConfig.margin.bottom - bodyWidthAdjustment);
1586
+ const options = {
1587
+ width,
1588
+ height,
1589
+ ticks,
1590
+ ...this.options,
1591
+ };
1592
+ return options;
1593
+ }
1594
+ /**
1595
+ * Draws the chart.
1596
+ */
1597
+ drawChart() {
1598
+ if (typeof this.container !== 'undefined') {
1599
+ const options = this.chartOptions();
1600
+ this.d3Factory.drawLineChart(this.container, this.data, options);
1601
+ }
1602
+ }
1603
+ /**
1604
+ * Actually draws the chart after the component view is initialized.
1605
+ */
1606
+ ngAfterViewInit() {
1607
+ this.drawChart();
1608
+ }
1609
+ /**
1610
+ * Redraws the chart on changes.
1611
+ */
1612
+ ngOnChanges(changes) {
1613
+ const data = changes.data?.currentValue;
1614
+ const options = changes.options?.currentValue;
1615
+ if ((typeof data !== 'undefined' && data !== null) || (typeof options !== 'undefined' && options !== null)) {
1616
+ this.drawChart();
1617
+ }
1618
+ }
1619
+ }
1620
+ /** @nocollapse */ AppLineChartComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppLineChartComponent, deps: [{ token: DOCUMENT }, { token: D3_CHART_FACTORY }], target: i0.ɵɵFactoryTarget.Component });
1621
+ /** @nocollapse */ AppLineChartComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: AppLineChartComponent, selector: "app-line-chart", inputs: { chartId: "chartId", data: "data", options: "options" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"container\" id=\"{{ chartId }}\" #container></div>\n", styles: [":host{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center}:host .container{flex:0 1 auto}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1622
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppLineChartComponent, decorators: [{
1623
+ type: Component,
1624
+ args: [{ selector: 'app-line-chart', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"container\" id=\"{{ chartId }}\" #container></div>\n", styles: [":host{display:flex;flex-direction:row;flex-wrap:nowrap;justify-content:center}:host .container{flex:0 1 auto}\n"] }]
1625
+ }], ctorParameters: function () { return [{ type: Document, decorators: [{
1626
+ type: Inject,
1627
+ args: [DOCUMENT]
1628
+ }] }, { type: undefined, decorators: [{
1629
+ type: Inject,
1630
+ args: [D3_CHART_FACTORY]
1631
+ }] }]; }, propDecorators: { chartId: [{
1632
+ type: Input
1633
+ }], data: [{
1634
+ type: Input
1635
+ }], options: [{
1636
+ type: Input
1637
+ }], container: [{
1638
+ type: ViewChild,
1639
+ args: ['container']
1640
+ }] } });
1641
+
1642
+ class AppChartExamplesComponent {
1643
+ constructor(breakpointObserver) {
1644
+ this.breakpointObserver = breakpointObserver;
1645
+ this.breakpoint$ = this.breakpointObserver
1646
+ .observe([Breakpoints.XSmall, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge])
1647
+ .pipe(map(result => Object.keys(result.breakpoints).find(item => result.breakpoints[item]) ?? 'unknown'));
1648
+ this.barChartConfig$ = this.breakpoint$.pipe(switchMap(() => timer(this.timeout).pipe(first(), map(() => ({ data: this.barChartData, options: this.barChartOptions() })))));
1649
+ this.lineChartConfig$ = this.breakpoint$.pipe(switchMap(() => timer(this.timeout).pipe(first(), map(() => ({ data: this.lineChartData, options: this.lineChartOptions() })))));
1650
+ this.radarChartConfig$ = this.breakpoint$.pipe(switchMap(() => timer(this.timeout).pipe(first(), map(() => ({ data: this.radarChartData, options: this.radarChartOptions() })))));
1651
+ this.pieChartConfig$ = this.breakpoint$.pipe(switchMap(() => timer(this.timeout).pipe(first(), map(() => ({ data: this.pieChartData, options: this.pieChartOptions() })))));
1652
+ this.forceDirectedChartConfig$ = this.breakpoint$.pipe(switchMap(() => timer(this.timeout).pipe(first(), map(() => ({ data: this.forceDirectedChartData, options: this.forceDirectedChartOptions() })))));
1653
+ this.timeout = 100;
1654
+ }
1655
+ /**
1656
+ * Sample bar chart data.
1657
+ */
1658
+ get barChartData() {
1659
+ return [
1660
+ { title: 'one', value: 1 },
1661
+ { title: 'two', value: 2 },
1662
+ { title: 'three', value: 3 },
1663
+ { title: 'four', value: 4 },
1664
+ { title: 'five', value: 5 },
1665
+ ];
1666
+ }
1667
+ /**
1668
+ * Sample line chart data.
1669
+ */
1670
+ get lineChartData() {
1671
+ const increment = 10000;
1672
+ const multiplier = 2;
1673
+ return [
1674
+ { timestamp: new Date().getTime(), value: 1 },
1675
+ { timestamp: new Date().getTime() + increment, value: 10 },
1676
+ { timestamp: new Date().getTime() + increment * multiplier, value: 3 },
1677
+ { timestamp: new Date().getTime() + increment * Math.pow(multiplier, multiplier), value: 5 },
1678
+ { timestamp: new Date().getTime() + increment * Math.pow(multiplier, multiplier) * multiplier, value: 4 },
1679
+ { timestamp: new Date().getTime() + increment * Math.pow(multiplier, multiplier) * Math.pow(multiplier, multiplier), value: 7 },
1680
+ {
1681
+ timestamp: new Date().getTime() + increment * Math.pow(multiplier, multiplier) * Math.pow(multiplier, multiplier) * multiplier,
1682
+ value: 8,
1683
+ },
1684
+ ];
1685
+ }
1686
+ /**
1687
+ * Sample radar chart data.
1688
+ */
1689
+ get radarChartData() {
1690
+ return [
1691
+ [
1692
+ { axis: 'one', value: 1, unit: 'x' },
1693
+ { axis: 'two', value: 2, unit: 'x' },
1694
+ { axis: 'three', value: 3, unit: 'x' },
1695
+ { axis: 'four', value: 4, unit: 'x' },
1696
+ { axis: 'five', value: 5, unit: 'x' },
1697
+ { axis: 'six', value: 6, unit: 'x' },
1698
+ { axis: 'seven', value: 7, unit: 'x' },
1699
+ { axis: 'eight', value: 8, unit: 'x' },
1700
+ { axis: 'nine (long labels are wrapped)', value: 9, unit: 'x' },
1701
+ ],
1702
+ [
1703
+ { axis: 'one', value: 9, unit: 'y' },
1704
+ { axis: 'two', value: 8, unit: 'y' },
1705
+ { axis: 'three', value: 7, unit: 'y' },
1706
+ { axis: 'four', value: 6, unit: 'y' },
1707
+ { axis: 'five', value: 5, unit: 'y' },
1708
+ { axis: 'six', value: 4, unit: 'y' },
1709
+ { axis: 'seven', value: 3, unit: 'y' },
1710
+ { axis: 'eight', value: 2, unit: 'y' },
1711
+ { axis: 'nine (long labels are wrapped)', value: 1, unit: 'y' },
1712
+ ],
1713
+ ];
1714
+ }
1715
+ /**
1716
+ * Sample pie chart data.
1717
+ */
1718
+ get pieChartData() {
1719
+ return [
1720
+ { key: 'one', y: 1 },
1721
+ { key: 'two', y: 2 },
1722
+ { key: 'three', y: 3 },
1723
+ { key: 'four', y: 4 },
1724
+ { key: 'five', y: 5 },
1725
+ { key: 'six', y: 6 },
1726
+ ];
1727
+ }
1728
+ /**
1729
+ * Sample force directed chart data.
1730
+ */
1731
+ get forceDirectedChartData() {
1732
+ const input = {
1733
+ domains: ['first', 'second', 'third'],
1734
+ entities: [
1735
+ { name: 'one', domains: ['first'], img: '' },
1736
+ { name: 'two', domains: ['second'], img: '' },
1737
+ { name: 'three', domains: ['third'], img: '' },
1738
+ { name: 'four', domains: ['first', 'second'], img: '' },
1739
+ { name: 'five', domains: ['second'], img: '' },
1740
+ { name: 'six', domains: ['third', 'second'], img: '' },
1741
+ { name: 'seven', domains: ['second'], img: '' },
1742
+ { name: 'eight', domains: ['third'], img: '' },
1743
+ ],
1744
+ };
1745
+ const domains = input.domains.map((name, index) => ({ index, name, value: 1 }));
1746
+ const entities = input.entities.map((app, index) => ({
1747
+ index: index,
1748
+ name: app.name,
1749
+ domains: [...app.domains],
1750
+ img: app.img,
1751
+ linksCount: 0,
1752
+ }));
1753
+ const nodes = [...entities];
1754
+ const links = entities
1755
+ .map(entity => {
1756
+ return entity.domains.map(domain => {
1757
+ const source = domains.find(value => domain === value.name)?.index ?? -1;
1758
+ const target = entity.index;
1759
+ return { source, target };
1760
+ });
1761
+ })
1762
+ .reduce((accumulator, item) => (Array.isArray(item) ? [...accumulator, ...item] : [...accumulator, item]), [])
1763
+ .filter(link => link.source !== -1 && link.target !== -1 && typeof link.target !== 'undefined');
1764
+ const chartData = {
1765
+ domains,
1766
+ entities: entities.map(item => ({
1767
+ ...item,
1768
+ linksCount: links.reduce((acc, link) => (link.target === item.index ? acc + 1 : acc), 0),
1769
+ })),
1770
+ links,
1771
+ nodes,
1772
+ };
1773
+ return chartData;
1774
+ }
1775
+ barChartOptions() {
1776
+ return {
1777
+ chartTitle: 'Example bar chart',
1778
+ xAxisTitle: 'long x axis title',
1779
+ yAxisTitle: 'long y axis title',
1780
+ };
1781
+ }
1782
+ lineChartOptions() {
1783
+ return {
1784
+ chartTitle: 'Example line chart',
1785
+ xAxisTitle: 'Date range',
1786
+ yAxisTitle: 'Value range',
1787
+ };
1788
+ }
1789
+ radarChartOptions() {
1790
+ return {
1791
+ chartTitle: 'Example radar chart',
1792
+ };
1793
+ }
1794
+ pieChartOptions() {
1795
+ return {
1796
+ chartTitle: 'Example pie chart',
1797
+ };
1798
+ }
1799
+ forceDirectedChartOptions() {
1800
+ return {
1801
+ chartTitle: 'Example force directed chart',
1802
+ };
1803
+ }
1804
+ }
1805
+ /** @nocollapse */ AppChartExamplesComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppChartExamplesComponent, deps: [{ token: i1.BreakpointObserver }], target: i0.ɵɵFactoryTarget.Component });
1806
+ /** @nocollapse */ AppChartExamplesComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.0.6", type: AppChartExamplesComponent, selector: "app-chart-examples", ngImport: i0, template: "<div class=\"container\" *ngIf=\"barChartConfig$ | async as config\">\n <app-bar-chart [chartId]=\"'bar-example-1'\" [data]=\"config.data\" [options]=\"config.options\"></app-bar-chart>\n</div>\n\n<hr [ngStyle]=\"{ width: '100%' }\" />\n\n<div class=\"container\" *ngIf=\"lineChartConfig$ | async as config\">\n <app-line-chart [chartId]=\"'line-example-1'\" [data]=\"config.data\" [options]=\"config.options\"></app-line-chart>\n</div>\n\n<hr [ngStyle]=\"{ width: '100%' }\" />\n\n<div class=\"container\" *ngIf=\"radarChartConfig$ | async as config\">\n <app-radar-chart [chartId]=\"'radar-example-1'\" [data]=\"config.data\" [options]=\"config.options\"></app-radar-chart>\n</div>\n\n<hr [ngStyle]=\"{ width: '100%' }\" />\n\n<div class=\"container\" *ngIf=\"pieChartConfig$ | async as config\">\n <app-pie-chart [chartId]=\"'pie-example-1'\" [data]=\"config.data\" [options]=\"config.options\"></app-pie-chart>\n</div>\n\n<hr [ngStyle]=\"{ width: '100%' }\" />\n\n<div class=\"container\" *ngIf=\"forceDirectedChartConfig$ | async as config\">\n <app-force-directed-chart\n [chartId]=\"'force-directed-example-1'\"\n [data]=\"config.data\"\n [options]=\"config.options\"\n ></app-force-directed-chart>\n</div>\n", styles: [":host{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center}:host .container{flex:1 1 auto;width:100%}\n"], dependencies: [{ kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: AppPieChartComponent, selector: "app-pie-chart", inputs: ["chartId", "data", "options"] }, { kind: "component", type: AppRadarChartComponent, selector: "app-radar-chart", inputs: ["chartId", "data", "options"] }, { kind: "component", type: AppForceDirectedChartComponent, selector: "app-force-directed-chart", inputs: ["chartId", "data", "options"] }, { kind: "component", type: AppBarChartComponent, selector: "app-bar-chart", inputs: ["chartId", "data", "options"] }, { kind: "component", type: AppLineChartComponent, selector: "app-line-chart", inputs: ["chartId", "data", "options"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1807
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppChartExamplesComponent, decorators: [{
1808
+ type: Component,
1809
+ args: [{ selector: 'app-chart-examples', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"container\" *ngIf=\"barChartConfig$ | async as config\">\n <app-bar-chart [chartId]=\"'bar-example-1'\" [data]=\"config.data\" [options]=\"config.options\"></app-bar-chart>\n</div>\n\n<hr [ngStyle]=\"{ width: '100%' }\" />\n\n<div class=\"container\" *ngIf=\"lineChartConfig$ | async as config\">\n <app-line-chart [chartId]=\"'line-example-1'\" [data]=\"config.data\" [options]=\"config.options\"></app-line-chart>\n</div>\n\n<hr [ngStyle]=\"{ width: '100%' }\" />\n\n<div class=\"container\" *ngIf=\"radarChartConfig$ | async as config\">\n <app-radar-chart [chartId]=\"'radar-example-1'\" [data]=\"config.data\" [options]=\"config.options\"></app-radar-chart>\n</div>\n\n<hr [ngStyle]=\"{ width: '100%' }\" />\n\n<div class=\"container\" *ngIf=\"pieChartConfig$ | async as config\">\n <app-pie-chart [chartId]=\"'pie-example-1'\" [data]=\"config.data\" [options]=\"config.options\"></app-pie-chart>\n</div>\n\n<hr [ngStyle]=\"{ width: '100%' }\" />\n\n<div class=\"container\" *ngIf=\"forceDirectedChartConfig$ | async as config\">\n <app-force-directed-chart\n [chartId]=\"'force-directed-example-1'\"\n [data]=\"config.data\"\n [options]=\"config.options\"\n ></app-force-directed-chart>\n</div>\n", styles: [":host{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center}:host .container{flex:1 1 auto;width:100%}\n"] }]
1810
+ }], ctorParameters: function () { return [{ type: i1.BreakpointObserver }]; } });
1811
+
1812
+ class AppClientD3ChartsModule {
1813
+ }
1814
+ /** @nocollapse */ AppClientD3ChartsModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppClientD3ChartsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1815
+ /** @nocollapse */ AppClientD3ChartsModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.0.6", ngImport: i0, type: AppClientD3ChartsModule, declarations: [AppPieChartComponent,
1816
+ AppRadarChartComponent,
1817
+ AppForceDirectedChartComponent,
1818
+ AppBarChartComponent,
1819
+ AppLineChartComponent,
1820
+ AppChartExamplesComponent], imports: [CommonModule], exports: [AppPieChartComponent,
1821
+ AppRadarChartComponent,
1822
+ AppForceDirectedChartComponent,
1823
+ AppBarChartComponent,
1824
+ AppLineChartComponent,
1825
+ AppChartExamplesComponent] });
1826
+ /** @nocollapse */ AppClientD3ChartsModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppClientD3ChartsModule, imports: [CommonModule] });
1827
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.6", ngImport: i0, type: AppClientD3ChartsModule, decorators: [{
1828
+ type: NgModule,
1829
+ args: [{
1830
+ imports: [CommonModule],
1831
+ declarations: [
1832
+ AppPieChartComponent,
1833
+ AppRadarChartComponent,
1834
+ AppForceDirectedChartComponent,
1835
+ AppBarChartComponent,
1836
+ AppLineChartComponent,
1837
+ AppChartExamplesComponent,
1838
+ ],
1839
+ exports: [
1840
+ AppPieChartComponent,
1841
+ AppRadarChartComponent,
1842
+ AppForceDirectedChartComponent,
1843
+ AppBarChartComponent,
1844
+ AppLineChartComponent,
1845
+ AppChartExamplesComponent,
1846
+ ],
1847
+ }]
1848
+ }] });
1849
+
1850
+ /**
1851
+ * Generated bundle index. Do not edit.
1852
+ */
1853
+
1854
+ export { AppBarChartComponent, AppChartExamplesComponent, AppClientD3ChartsModule, AppForceDirectedChartComponent, AppLineChartComponent, AppPieChartComponent, AppRadarChartComponent, defaultBarChartConfig, defaultForceDirectedChartConfig, defaultLineChartConfig, defaultPieChartConfig, defaultRadarChartConfig, drawBarChart, drawForceDirectedChart, drawLineChart, drawPieChart, drawRadarChart };
1855
+ //# sourceMappingURL=rfprodz-client-d3-charts.mjs.map