@axdspub/axiom-charts 0.0.20 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/library/Libraries/axiom/index.js +509 -770
- package/library/Libraries/axiom/index.js.map +1 -1
- package/library/axiom-charts.d.ts +34 -27
- package/library/browser.js +33 -10
- package/library/browser.js.map +1 -1
- package/library/cjs.js +33 -10
- package/library/cjs.js.map +1 -1
- package/library/index.js +33 -10
- package/library/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { RequestItem } from '@axdspub/axiom-ui-data-services';
|
|
2
|
-
import { extent,
|
|
2
|
+
import { extent, sum } from 'd3-array';
|
|
3
3
|
import { groupBy, debounce } from 'lodash';
|
|
4
4
|
import { create } from 'd3-selection';
|
|
5
|
+
import * as d3 from 'd3';
|
|
6
|
+
import { bisector } from 'd3';
|
|
5
7
|
import { scaleUtc, scaleLinear, scaleRadial } from 'd3-scale';
|
|
6
8
|
import { axisLeft, axisBottom } from 'd3-axis';
|
|
7
9
|
import { line, area, lineRadial, curveLinearClosed, pointRadial, areaRadial } from 'd3-shape';
|
|
8
|
-
import * as d3 from 'd3';
|
|
9
10
|
|
|
10
11
|
/******************************************************************************
|
|
11
12
|
Copyright (c) Microsoft Corporation.
|
|
@@ -66,6 +67,10 @@ var EPlotTypes;
|
|
|
66
67
|
class PlotRoot extends RequestItem {
|
|
67
68
|
constructor(props) {
|
|
68
69
|
super(props);
|
|
70
|
+
this.scrubSettingsDefaults = {
|
|
71
|
+
enabled: true,
|
|
72
|
+
axis: EChartAxes.x
|
|
73
|
+
};
|
|
69
74
|
this.id = props.id;
|
|
70
75
|
this.type = props.type;
|
|
71
76
|
this.chart = props.chart;
|
|
@@ -74,6 +79,7 @@ class PlotRoot extends RequestItem {
|
|
|
74
79
|
this.scrubbing = props.scrubbing;
|
|
75
80
|
this.onDataLoad = props.onDataLoad;
|
|
76
81
|
this.axisSettings = props.axisSettings;
|
|
82
|
+
this.scrubSettings = Object.assign(Object.assign({}, this.scrubSettingsDefaults), props.scrubSettings);
|
|
77
83
|
this.runtime = {};
|
|
78
84
|
}
|
|
79
85
|
getAxisSetting(axis) {
|
|
@@ -167,12 +173,17 @@ class PlotRoot extends RequestItem {
|
|
|
167
173
|
this.data = yield this.getData();
|
|
168
174
|
});
|
|
169
175
|
}
|
|
170
|
-
|
|
171
|
-
|
|
176
|
+
onLegendReady(legendItems) {
|
|
177
|
+
const chart = this.chart;
|
|
178
|
+
chart.emitPlotLegendReady(this.id, legendItems);
|
|
172
179
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
onScrub(scrubData) {
|
|
181
|
+
const chart = this.chart;
|
|
182
|
+
chart.emitPlotScrub(this.id, scrubData);
|
|
183
|
+
}
|
|
184
|
+
onScrubEnd() {
|
|
185
|
+
const chart = this.chart;
|
|
186
|
+
chart.emitPlotScrubEnd(this.id);
|
|
176
187
|
}
|
|
177
188
|
destroy() {
|
|
178
189
|
}
|
|
@@ -184,8 +195,9 @@ class ChartRoot {
|
|
|
184
195
|
this.settings = props.settings;
|
|
185
196
|
this.element = props.element;
|
|
186
197
|
this.onPlotDataLoad = props.onPlotDataLoad;
|
|
187
|
-
this.
|
|
188
|
-
this.
|
|
198
|
+
this.onPlotLegendReady = props.onPlotLegendReady;
|
|
199
|
+
this.onPlotScrub = props.onPlotScrub;
|
|
200
|
+
this.onPlotScrubEnd = props.onPlotScrubEnd;
|
|
189
201
|
this.runtime = {
|
|
190
202
|
prevWidth: 0,
|
|
191
203
|
prevHeight: 0
|
|
@@ -201,6 +213,21 @@ class ChartRoot {
|
|
|
201
213
|
})
|
|
202
214
|
} */
|
|
203
215
|
}
|
|
216
|
+
emitPlotLegendReady(plotId, legendItems) {
|
|
217
|
+
if (this.onPlotLegendReady === undefined)
|
|
218
|
+
return;
|
|
219
|
+
this.onPlotLegendReady(plotId, legendItems);
|
|
220
|
+
}
|
|
221
|
+
emitPlotScrub(plotId, scrubData) {
|
|
222
|
+
if (this.onPlotScrub === undefined)
|
|
223
|
+
return;
|
|
224
|
+
this.onPlotScrub(plotId, scrubData);
|
|
225
|
+
}
|
|
226
|
+
emitPlotScrubEnd(plotId) {
|
|
227
|
+
if (this.onPlotScrubEnd === undefined)
|
|
228
|
+
return;
|
|
229
|
+
this.onPlotScrubEnd(plotId);
|
|
230
|
+
}
|
|
204
231
|
onError(e) {
|
|
205
232
|
}
|
|
206
233
|
onResize(prevWidth, prevHeight) {
|
|
@@ -412,10 +439,6 @@ class AxiomPlotRoot extends PlotRoot {
|
|
|
412
439
|
}
|
|
413
440
|
|
|
414
441
|
class AxiomLine extends AxiomPlotRoot {
|
|
415
|
-
constructor() {
|
|
416
|
-
super(...arguments);
|
|
417
|
-
this.addedScrubbingListener = false;
|
|
418
|
-
}
|
|
419
442
|
getDomainAccessor(axis) {
|
|
420
443
|
var _a, _b, _c;
|
|
421
444
|
const accessors = ((_c = (_b = (_a = this.runtime) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.parsed) === null || _c === void 0 ? void 0 : _c.accessors) !== undefined ? this.runtime.data.parsed.accessors : {};
|
|
@@ -427,7 +450,10 @@ class AxiomLine extends AxiomPlotRoot {
|
|
|
427
450
|
};
|
|
428
451
|
}
|
|
429
452
|
draw() {
|
|
430
|
-
|
|
453
|
+
const _super = Object.create(null, {
|
|
454
|
+
onLegendReady: { get: () => super.onLegendReady }
|
|
455
|
+
});
|
|
456
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
431
457
|
return __awaiter(this, void 0, void 0, function* () {
|
|
432
458
|
const style = this.style !== undefined ? this.style : {};
|
|
433
459
|
if (this.parsedData === undefined) {
|
|
@@ -486,75 +512,49 @@ class AxiomLine extends AxiomPlotRoot {
|
|
|
486
512
|
}
|
|
487
513
|
line$1(this.parsedData);
|
|
488
514
|
ctx.stroke();
|
|
515
|
+
if (((_m = this.axes) === null || _m === void 0 ? void 0 : _m.y) !== undefined) {
|
|
516
|
+
_super.onLegendReady.call(this, [{
|
|
517
|
+
id: this.axes.y.key,
|
|
518
|
+
label: (_o = chart.getAxisLabel(this.axes.y, 'y')) !== null && _o !== void 0 ? _o : this.axes.y.key,
|
|
519
|
+
style: { backgroundColor: ctx.strokeStyle }
|
|
520
|
+
}]);
|
|
521
|
+
}
|
|
522
|
+
this.setupScrubbing();
|
|
489
523
|
});
|
|
490
524
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
return undefined;
|
|
499
|
-
}
|
|
500
|
-
const scrubValue = this.xScale.invert(mouseX - this.offset.x);
|
|
501
|
-
const bisect = bisector((row) => xAccessor(row)).center;
|
|
502
|
-
const bisectIndex = bisect(this.parsedData, scrubValue);
|
|
503
|
-
const nearestRow = this.getNearestRow(bisectIndex, scrubValue, this.parsedData, xAccessor, yAccessor);
|
|
504
|
-
const bisected = nearestRow;
|
|
505
|
-
const xValue = xAccessor(bisected);
|
|
506
|
-
const xPosition = this.xScale(xValue) + this.offset.x;
|
|
507
|
-
const yValue = yAccessor(nearestRow);
|
|
508
|
-
const yPosition = this.yScale(yValue) + this.offset.y;
|
|
509
|
-
return {
|
|
510
|
-
xPosition,
|
|
511
|
-
xValue,
|
|
512
|
-
yPosition,
|
|
513
|
-
yValue
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
getNearestRow(bisectIndex, scrubValue, parsedData, xAccessor, yAccessor) {
|
|
517
|
-
const nonEmptyRows = parsedData.filter(row => yAccessor(row) !== null && yAccessor(row) !== undefined);
|
|
518
|
-
let minDistance = Number.MAX_VALUE;
|
|
519
|
-
let nearestRow = parsedData[bisectIndex];
|
|
520
|
-
for (let i = 0; i < nonEmptyRows.length; i += 1) {
|
|
521
|
-
const current = xAccessor(nonEmptyRows[i]);
|
|
522
|
-
const distance = Math.abs(+current - +scrubValue);
|
|
523
|
-
if (distance < minDistance) {
|
|
524
|
-
minDistance = distance;
|
|
525
|
-
nearestRow = nonEmptyRows[i];
|
|
526
|
-
}
|
|
525
|
+
setupScrubbing() {
|
|
526
|
+
var _a, _b, _c;
|
|
527
|
+
const chartElements = this.chart.getChartElements();
|
|
528
|
+
if (chartElements !== undefined) {
|
|
529
|
+
(_a = chartElements.svg.node()) === null || _a === void 0 ? void 0 : _a.addEventListener('mouseover', event => { this.onMouseScrub(event); });
|
|
530
|
+
(_b = chartElements.svg.node()) === null || _b === void 0 ? void 0 : _b.addEventListener('mousemove', event => { this.onMouseScrub(event); });
|
|
531
|
+
(_c = chartElements.svg.node()) === null || _c === void 0 ? void 0 : _c.addEventListener('mouseleave', () => { this.onScrubEnd(); });
|
|
527
532
|
}
|
|
528
|
-
return nearestRow;
|
|
529
533
|
}
|
|
530
|
-
|
|
534
|
+
onMouseScrub(event) {
|
|
531
535
|
const chart = this.chart;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
536
|
+
if (chart.onPlotScrub === undefined)
|
|
537
|
+
return;
|
|
538
|
+
const xAccessor = this.getAccessorAtAxis('x');
|
|
539
|
+
const yAccessor = this.getAccessorAtAxis('y');
|
|
540
|
+
if (this.scrubSettings.axis === undefined ||
|
|
541
|
+
this.xScale === undefined ||
|
|
542
|
+
this.yScale === undefined ||
|
|
543
|
+
xAccessor === undefined ||
|
|
544
|
+
yAccessor === undefined ||
|
|
545
|
+
this.parsedData === undefined ||
|
|
546
|
+
this.offset === undefined) {
|
|
547
|
+
return;
|
|
535
548
|
}
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
549
|
+
const scrub = chart.getScrubEvent(event, this.scrubSettings.axis, this.xScale, this.yScale, xAccessor, yAccessor, this.parsedData, this.offset);
|
|
550
|
+
if (scrub === undefined || scrub.x === undefined)
|
|
538
551
|
return;
|
|
539
|
-
|
|
540
|
-
this.
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
ctx.setLineDash([]);
|
|
546
|
-
ctx.arc(x, y, 6, 0, 2 * Math.PI, true);
|
|
547
|
-
ctx.strokeStyle = this.getStrokeStyle();
|
|
548
|
-
ctx.lineWidth = this.getLineWidth();
|
|
549
|
-
ctx.stroke();
|
|
550
|
-
}
|
|
551
|
-
drawScrubLine(x, ctx) {
|
|
552
|
-
const lineHeight = this.chart.settings.height - this.chart.settings.margin.bottom;
|
|
553
|
-
ctx.setLineDash([1, 1]);
|
|
554
|
-
ctx.beginPath();
|
|
555
|
-
ctx.moveTo(x, 0);
|
|
556
|
-
ctx.lineTo(x, lineHeight);
|
|
557
|
-
ctx.stroke();
|
|
552
|
+
this.onScrub(scrub);
|
|
553
|
+
chart.drawPlotScrubbing(this.id, scrub, this.style, this.scrubSettings);
|
|
554
|
+
}
|
|
555
|
+
onScrubEnd() {
|
|
556
|
+
this.chart.erasePlotScrub(this.id);
|
|
557
|
+
super.onScrubEnd();
|
|
558
558
|
}
|
|
559
559
|
getStrokeStyle() {
|
|
560
560
|
const style = this.style !== undefined ? this.style : {};
|
|
@@ -605,7 +605,10 @@ class AxiomArea extends AxiomPlotRoot {
|
|
|
605
605
|
};
|
|
606
606
|
}
|
|
607
607
|
draw() {
|
|
608
|
-
|
|
608
|
+
const _super = Object.create(null, {
|
|
609
|
+
onLegendReady: { get: () => super.onLegendReady }
|
|
610
|
+
});
|
|
611
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
609
612
|
return __awaiter(this, void 0, void 0, function* () {
|
|
610
613
|
const style = this.style !== undefined ? this.style : {};
|
|
611
614
|
const data = yield this.getData();
|
|
@@ -673,18 +676,22 @@ class AxiomArea extends AxiomPlotRoot {
|
|
|
673
676
|
ctx.beginPath();
|
|
674
677
|
area$1(parsedData);
|
|
675
678
|
ctx.fill();
|
|
679
|
+
if (((_m = this.axes) === null || _m === void 0 ? void 0 : _m.y) !== undefined) {
|
|
680
|
+
_super.onLegendReady.call(this, [{
|
|
681
|
+
id: this.axes.y.key,
|
|
682
|
+
label: (_o = chart.getAxisLabel(this.axes.y, 'y')) !== null && _o !== void 0 ? _o : this.axes.y.key,
|
|
683
|
+
style: { backgroundColor: ctx.fillStyle }
|
|
684
|
+
}]);
|
|
685
|
+
}
|
|
676
686
|
});
|
|
677
687
|
}
|
|
678
|
-
drawScrubbing(mouseX) {
|
|
679
|
-
console.error('AxiomArea.drawScrubbing not implemented');
|
|
680
|
-
}
|
|
681
|
-
getScrubPosition(mouseX) {
|
|
682
|
-
console.error('AxiomArea.getScrubPosition not implemented');
|
|
683
|
-
return undefined;
|
|
684
|
-
}
|
|
685
688
|
}
|
|
686
689
|
|
|
687
690
|
class AxiomScatter extends AxiomPlotRoot {
|
|
691
|
+
constructor() {
|
|
692
|
+
super(...arguments);
|
|
693
|
+
this.circleClass = `${this.id}-scrub-circle`;
|
|
694
|
+
}
|
|
688
695
|
getDomainAccessor(axis) {
|
|
689
696
|
var _a, _b, _c;
|
|
690
697
|
const accessors = ((_c = (_b = (_a = this.runtime) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.parsed) === null || _c === void 0 ? void 0 : _c.accessors) !== undefined ? this.runtime.data.parsed.accessors : {};
|
|
@@ -696,11 +703,14 @@ class AxiomScatter extends AxiomPlotRoot {
|
|
|
696
703
|
};
|
|
697
704
|
}
|
|
698
705
|
draw() {
|
|
699
|
-
|
|
706
|
+
const _super = Object.create(null, {
|
|
707
|
+
onLegendReady: { get: () => super.onLegendReady }
|
|
708
|
+
});
|
|
709
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
700
710
|
return __awaiter(this, void 0, void 0, function* () {
|
|
701
711
|
const style = this.style !== undefined ? this.style : {};
|
|
702
712
|
const data = yield this.getData();
|
|
703
|
-
|
|
713
|
+
this.parsedData = (_a = data === null || data === void 0 ? void 0 : data.parsed) === null || _a === void 0 ? void 0 : _a.data;
|
|
704
714
|
const xAccessor = this.getAccessorAtAxis('x');
|
|
705
715
|
const yAccessor = this.getAccessorAtAxis('y');
|
|
706
716
|
const chart = this.chart;
|
|
@@ -710,24 +720,28 @@ class AxiomScatter extends AxiomPlotRoot {
|
|
|
710
720
|
}
|
|
711
721
|
const xAxisScale = (_c = (_b = this === null || this === void 0 ? void 0 : this.axes) === null || _b === void 0 ? void 0 : _b.x) === null || _c === void 0 ? void 0 : _c.scale;
|
|
712
722
|
const yAxisScale = (_e = (_d = this === null || this === void 0 ? void 0 : this.axes) === null || _d === void 0 ? void 0 : _d.y) === null || _e === void 0 ? void 0 : _e.scale;
|
|
713
|
-
if (xAxisScale === undefined || yAxisScale === undefined || parsedData === undefined || xAccessor === undefined || yAccessor === undefined) {
|
|
723
|
+
if (xAxisScale === undefined || yAxisScale === undefined || this.parsedData === undefined || xAccessor === undefined || yAccessor === undefined) {
|
|
714
724
|
return undefined;
|
|
715
725
|
}
|
|
716
|
-
|
|
726
|
+
this.offset = {
|
|
717
727
|
x: ((_h = (_g = (_f = this === null || this === void 0 ? void 0 : this.axes) === null || _f === void 0 ? void 0 : _f.x) === null || _g === void 0 ? void 0 : _g.offset) === null || _h === void 0 ? void 0 : _h.x) !== undefined ? this.axes.x.offset.x : 0,
|
|
718
728
|
y: ((_l = (_k = (_j = this === null || this === void 0 ? void 0 : this.axes) === null || _j === void 0 ? void 0 : _j.y) === null || _k === void 0 ? void 0 : _k.offset) === null || _l === void 0 ? void 0 : _l.y) !== undefined ? this.axes.y.offset.y : 0
|
|
719
729
|
};
|
|
720
|
-
|
|
730
|
+
this.xScale = chart.getD3XScale(xAxisScale);
|
|
721
731
|
const xPosition = (d) => {
|
|
732
|
+
if (this.xScale === undefined || this.offset === undefined)
|
|
733
|
+
return 0;
|
|
722
734
|
const v = xAccessor(d);
|
|
723
|
-
const p = xScale(v);
|
|
724
|
-
return p === undefined ? 0 : (p + offset.x);
|
|
735
|
+
const p = this.xScale(v);
|
|
736
|
+
return p === undefined ? 0 : (p + this.offset.x);
|
|
725
737
|
};
|
|
726
|
-
|
|
738
|
+
this.yScale = chart.getD3YScale(yAxisScale);
|
|
727
739
|
const yPosition = (d) => {
|
|
740
|
+
if (this.yScale === undefined || this.offset === undefined)
|
|
741
|
+
return 0;
|
|
728
742
|
const v = yAccessor(d);
|
|
729
|
-
const p = yScale(v);
|
|
730
|
-
return p === undefined ? 0 : (p + offset.y);
|
|
743
|
+
const p = this.yScale(v);
|
|
744
|
+
return p === undefined ? 0 : (p + this.offset.y);
|
|
731
745
|
};
|
|
732
746
|
const isDefined = (d) => {
|
|
733
747
|
const v = yAccessor(d);
|
|
@@ -739,7 +753,7 @@ class AxiomScatter extends AxiomPlotRoot {
|
|
|
739
753
|
const radius = style.radius !== undefined ? style.radius : 2;
|
|
740
754
|
ctx.lineWidth = (style.strokeWidth !== undefined || style.strokeColor !== undefined) ? ((style.strokeWidth !== undefined ? style.strokeWidth : 1) + radius) : 0;
|
|
741
755
|
ctx.fillStyle = style.fill !== undefined ? style.fill : '#666';
|
|
742
|
-
parsedData.forEach(d => {
|
|
756
|
+
this.parsedData.forEach(d => {
|
|
743
757
|
if (isDefined(d)) {
|
|
744
758
|
ctx.beginPath();
|
|
745
759
|
ctx.arc(xPosition(d), yPosition(d), radius, 0, 2 * Math.PI);
|
|
@@ -747,13 +761,52 @@ class AxiomScatter extends AxiomPlotRoot {
|
|
|
747
761
|
ctx.fill();
|
|
748
762
|
}
|
|
749
763
|
});
|
|
764
|
+
if (((_m = this.axes) === null || _m === void 0 ? void 0 : _m.y) !== undefined) {
|
|
765
|
+
_super.onLegendReady.call(this, [{
|
|
766
|
+
id: this.axes.y.key,
|
|
767
|
+
label: (_o = chart.getAxisLabel(this.axes.y, 'y')) !== null && _o !== void 0 ? _o : this.axes.y.key,
|
|
768
|
+
style: {
|
|
769
|
+
backgroundColor: ctx.fillStyle,
|
|
770
|
+
border: `1px solid ${ctx.strokeStyle}`
|
|
771
|
+
}
|
|
772
|
+
}]);
|
|
773
|
+
}
|
|
774
|
+
this.setupScrubbing();
|
|
750
775
|
});
|
|
751
776
|
}
|
|
752
|
-
|
|
753
|
-
|
|
777
|
+
setupScrubbing() {
|
|
778
|
+
var _a, _b, _c;
|
|
779
|
+
const chartElements = this.chart.getChartElements();
|
|
780
|
+
if (chartElements !== undefined) {
|
|
781
|
+
(_a = chartElements.svg.node()) === null || _a === void 0 ? void 0 : _a.addEventListener('mouseover', event => { this.onMouseScrub(event); });
|
|
782
|
+
(_b = chartElements.svg.node()) === null || _b === void 0 ? void 0 : _b.addEventListener('mousemove', event => { this.onMouseScrub(event); });
|
|
783
|
+
(_c = chartElements.svg.node()) === null || _c === void 0 ? void 0 : _c.addEventListener('mouseleave', () => { this.onScrubEnd(); });
|
|
784
|
+
}
|
|
754
785
|
}
|
|
755
|
-
|
|
756
|
-
|
|
786
|
+
onMouseScrub(event) {
|
|
787
|
+
const chart = this.chart;
|
|
788
|
+
if (chart.onPlotScrub === undefined)
|
|
789
|
+
return;
|
|
790
|
+
const xAccessor = this.getAccessorAtAxis('x');
|
|
791
|
+
const yAccessor = this.getAccessorAtAxis('y');
|
|
792
|
+
if (this.scrubSettings.axis === undefined ||
|
|
793
|
+
this.xScale === undefined ||
|
|
794
|
+
this.yScale === undefined ||
|
|
795
|
+
xAccessor === undefined ||
|
|
796
|
+
yAccessor === undefined ||
|
|
797
|
+
this.parsedData === undefined ||
|
|
798
|
+
this.offset === undefined) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const scrub = chart.getScrubEvent(event, this.scrubSettings.axis, this.xScale, this.yScale, xAccessor, yAccessor, this.parsedData, this.offset);
|
|
802
|
+
if (scrub === undefined || scrub.x === undefined)
|
|
803
|
+
return;
|
|
804
|
+
this.onScrub(scrub);
|
|
805
|
+
chart.drawPlotScrubbing(this.id, scrub, this.style, this.scrubSettings);
|
|
806
|
+
}
|
|
807
|
+
onScrubEnd() {
|
|
808
|
+
this.chart.erasePlotScrub(this.id);
|
|
809
|
+
super.onScrubEnd();
|
|
757
810
|
}
|
|
758
811
|
}
|
|
759
812
|
|
|
@@ -770,6 +823,9 @@ class AxiomBar extends AxiomPlotRoot {
|
|
|
770
823
|
}
|
|
771
824
|
}
|
|
772
825
|
draw() {
|
|
826
|
+
const _super = Object.create(null, {
|
|
827
|
+
onLegendReady: { get: () => super.onLegendReady }
|
|
828
|
+
});
|
|
773
829
|
var _a, _b, _c, _d, _e;
|
|
774
830
|
return __awaiter(this, void 0, void 0, function* () {
|
|
775
831
|
const dataResponse = yield this.getData();
|
|
@@ -815,9 +871,6 @@ class AxiomBar extends AxiomPlotRoot {
|
|
|
815
871
|
// Colors
|
|
816
872
|
const fillColors = (_e = (_d = this.style) === null || _d === void 0 ? void 0 : _d.colors) !== null && _e !== void 0 ? _e : d3.schemeTableau10;
|
|
817
873
|
const color = d3.scaleOrdinal(fillColors).domain(subgroups);
|
|
818
|
-
// TODO use real data
|
|
819
|
-
// @ts-expect-error just for testing
|
|
820
|
-
this.emitDataLoad(parsedData, color);
|
|
821
874
|
const svg = chartElements.svg;
|
|
822
875
|
const g = svg.insert('g');
|
|
823
876
|
const x = this.getXScale(plotXStart, plotXEnd, groups);
|
|
@@ -828,24 +881,6 @@ class AxiomBar extends AxiomPlotRoot {
|
|
|
828
881
|
.domain(subgroups)
|
|
829
882
|
.range([0, x.bandwidth()])
|
|
830
883
|
.padding(0.05);
|
|
831
|
-
const onMouseEnterOrMove = (event, data) => {
|
|
832
|
-
if (this.chart.onScrub === undefined)
|
|
833
|
-
return;
|
|
834
|
-
const scrubPositions = {
|
|
835
|
-
[this.id]: {
|
|
836
|
-
xPosition: event.offsetX,
|
|
837
|
-
xValue: null,
|
|
838
|
-
yPosition: event.offsetY,
|
|
839
|
-
yValue: data.value
|
|
840
|
-
}
|
|
841
|
-
};
|
|
842
|
-
this.chart.onScrub(scrubPositions);
|
|
843
|
-
};
|
|
844
|
-
const onMouseLeave = () => {
|
|
845
|
-
if (this.chart.onScrubEnd === undefined)
|
|
846
|
-
return;
|
|
847
|
-
this.chart.onScrubEnd();
|
|
848
|
-
};
|
|
849
884
|
// Make Bar Groups
|
|
850
885
|
g.append('g')
|
|
851
886
|
.attr('width', plotWidth)
|
|
@@ -857,11 +892,12 @@ class AxiomBar extends AxiomPlotRoot {
|
|
|
857
892
|
.attr('transform', d => `translate(${Number(x(xAccessor(d)))}, 0)`)
|
|
858
893
|
.selectAll('rect')
|
|
859
894
|
.data(d => Object.keys(this.dimensions).filter(key => key !== 'x').map((key) => {
|
|
860
|
-
var _a, _b, _c;
|
|
895
|
+
var _a, _b, _c, _d, _e;
|
|
861
896
|
return {
|
|
862
897
|
label: (_a = this.dimensions[key].label) !== null && _a !== void 0 ? _a : key,
|
|
863
898
|
key: (_b = this.dimensions[key].property) !== null && _b !== void 0 ? _b : '',
|
|
864
|
-
|
|
899
|
+
x: d[(_d = (_c = this.dimensions.x) === null || _c === void 0 ? void 0 : _c.property) !== null && _d !== void 0 ? _d : 'x'],
|
|
900
|
+
value: d[(_e = this.dimensions[key].property) !== null && _e !== void 0 ? _e : '']
|
|
865
901
|
};
|
|
866
902
|
}))
|
|
867
903
|
.enter()
|
|
@@ -872,9 +908,9 @@ class AxiomBar extends AxiomPlotRoot {
|
|
|
872
908
|
.attr('height', d => plotHeight - Number(subgroupScales[d.key](d.value)))
|
|
873
909
|
.attr('fill', d => color(d.key))
|
|
874
910
|
.classed('bar', true)
|
|
875
|
-
.on('mouseover',
|
|
876
|
-
.on('mousemove',
|
|
877
|
-
.on('mouseleave',
|
|
911
|
+
.on('mouseover', (event, data) => { this.onMouseScrub(event, data); })
|
|
912
|
+
.on('mousemove', (event, data) => { this.onMouseScrub(event, data); })
|
|
913
|
+
.on('mouseleave', () => { this.onScrubEnd(); });
|
|
878
914
|
// X Axis
|
|
879
915
|
const xAxis = g.append('g')
|
|
880
916
|
.attr('transform', `translate(0, ${plotHeight})`)
|
|
@@ -911,6 +947,28 @@ class AxiomBar extends AxiomPlotRoot {
|
|
|
911
947
|
.attr('font-weight', 800)
|
|
912
948
|
.text(y2Label);
|
|
913
949
|
}
|
|
950
|
+
_super.onLegendReady.call(this, Object.keys(this.dimensions).filter(key => key !== 'x').map(key => {
|
|
951
|
+
var _a, _b;
|
|
952
|
+
return ({
|
|
953
|
+
id: key,
|
|
954
|
+
label: (_a = this.dimensions[key].label) !== null && _a !== void 0 ? _a : key,
|
|
955
|
+
style: { backgroundColor: this.dimensions[key].property === undefined ? '' : color((_b = this.dimensions[key].property) !== null && _b !== void 0 ? _b : '') }
|
|
956
|
+
});
|
|
957
|
+
}));
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
onMouseScrub(event, data) {
|
|
961
|
+
if (this.chart.onPlotScrub === undefined)
|
|
962
|
+
return;
|
|
963
|
+
this.onScrub({
|
|
964
|
+
x: {
|
|
965
|
+
position: event.offsetX,
|
|
966
|
+
value: data.x
|
|
967
|
+
},
|
|
968
|
+
y: {
|
|
969
|
+
position: event.offsetY,
|
|
970
|
+
value: data.value
|
|
971
|
+
}
|
|
914
972
|
});
|
|
915
973
|
}
|
|
916
974
|
getXScale(plotXStart, plotXEnd, groups) {
|
|
@@ -949,26 +1007,6 @@ class AxiomBar extends AxiomPlotRoot {
|
|
|
949
1007
|
}
|
|
950
1008
|
return subgroupScales;
|
|
951
1009
|
}
|
|
952
|
-
getScrubPosition(mouseX) { return undefined; }
|
|
953
|
-
drawScrubbing(mouseX) { }
|
|
954
|
-
emitDataLoad(data, color) {
|
|
955
|
-
if (this.onDataLoad !== undefined) {
|
|
956
|
-
const legend = Object.keys(this.dimensions).filter(key => key !== 'x').map(key => {
|
|
957
|
-
var _a, _b;
|
|
958
|
-
return ({
|
|
959
|
-
key,
|
|
960
|
-
label: (_a = this.dimensions[key].label) !== null && _a !== void 0 ? _a : key,
|
|
961
|
-
color: color((_b = this.dimensions[key].property) !== null && _b !== void 0 ? _b : '')
|
|
962
|
-
});
|
|
963
|
-
});
|
|
964
|
-
const barPlotLoadEvent = {
|
|
965
|
-
plotId: this.id,
|
|
966
|
-
data,
|
|
967
|
-
properties: { legend }
|
|
968
|
-
};
|
|
969
|
-
this.onDataLoad(barPlotLoadEvent);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
1010
|
}
|
|
973
1011
|
|
|
974
1012
|
const chartTypes = {
|
|
@@ -1101,13 +1139,15 @@ class AxiomChart extends ChartRoot {
|
|
|
1101
1139
|
axisSelection.append('g').call(axisG);
|
|
1102
1140
|
}
|
|
1103
1141
|
});
|
|
1104
|
-
this.
|
|
1142
|
+
if (this.settings.layout === 'timeseries') {
|
|
1143
|
+
this.showYLabelsForTimeSeries(axes, this.plots);
|
|
1144
|
+
}
|
|
1105
1145
|
}
|
|
1106
1146
|
getAxisLabel(axis, dimensionKey) {
|
|
1107
1147
|
var _a;
|
|
1108
1148
|
return (_a = axis.plots[0]) === null || _a === void 0 ? void 0 : _a.dimensions[dimensionKey].label;
|
|
1109
1149
|
}
|
|
1110
|
-
|
|
1150
|
+
showYLabelsForTimeSeries(axes, plots) {
|
|
1111
1151
|
const yLabelsAreSupportedForPlotType = [EPlotTypes.line, EPlotTypes.scatter, EPlotTypes.seasonal, EPlotTypes.area]
|
|
1112
1152
|
.find(type => type === plots[0].type) !== undefined;
|
|
1113
1153
|
if (plots.length === 0 || !yLabelsAreSupportedForPlotType) {
|
|
@@ -1202,7 +1242,8 @@ class AxiomChart extends ChartRoot {
|
|
|
1202
1242
|
.attr('viewBox', [0, 0, width, height])
|
|
1203
1243
|
.attr('width', width)
|
|
1204
1244
|
.attr('height', height)
|
|
1205
|
-
.style('position', 'absolute')
|
|
1245
|
+
.style('position', 'absolute')
|
|
1246
|
+
.style('z-index', 20);
|
|
1206
1247
|
const canvas = create('canvas')
|
|
1207
1248
|
.attr('width', width)
|
|
1208
1249
|
.attr('height', height)
|
|
@@ -1219,6 +1260,128 @@ class AxiomChart extends ChartRoot {
|
|
|
1219
1260
|
}
|
|
1220
1261
|
return this.chartElements;
|
|
1221
1262
|
}
|
|
1263
|
+
drawPlotScrubbing(plotId, scrubPosition, style, scrubSettings) {
|
|
1264
|
+
const chartElements = this.getChartElements();
|
|
1265
|
+
if (chartElements === undefined)
|
|
1266
|
+
return;
|
|
1267
|
+
if (scrubPosition === undefined || scrubPosition.x === undefined || scrubPosition.y === undefined)
|
|
1268
|
+
return;
|
|
1269
|
+
const { x, y } = scrubPosition;
|
|
1270
|
+
this.drawScrubLine(plotId, scrubPosition, chartElements.svg, style, scrubSettings);
|
|
1271
|
+
this.drawPlotScrubCircle(plotId, x === null || x === void 0 ? void 0 : x.position, y === null || y === void 0 ? void 0 : y.position, chartElements.svg, style);
|
|
1272
|
+
}
|
|
1273
|
+
drawScrubLine(plotId, scrubPosition, svg, style, scrubSettings) {
|
|
1274
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1275
|
+
const scrubLineClass = this.getPlotScrubLineClass(plotId);
|
|
1276
|
+
svg.select(`.${scrubLineClass}`).remove();
|
|
1277
|
+
const line = svg.insert('line')
|
|
1278
|
+
.classed(scrubLineClass, true);
|
|
1279
|
+
if ((scrubSettings === null || scrubSettings === void 0 ? void 0 : scrubSettings.axis) === EChartAxes.x) {
|
|
1280
|
+
const lineLength = this.settings.height - this.settings.margin.bottom;
|
|
1281
|
+
line.attr('x1', (_b = (_a = scrubPosition.x) === null || _a === void 0 ? void 0 : _a.position) !== null && _b !== void 0 ? _b : 0)
|
|
1282
|
+
.attr('y1', 0)
|
|
1283
|
+
.attr('x2', (_d = (_c = scrubPosition.x) === null || _c === void 0 ? void 0 : _c.position) !== null && _d !== void 0 ? _d : 0)
|
|
1284
|
+
.attr('y2', lineLength)
|
|
1285
|
+
.attr('stroke', this.getPlotStrokeColor(style))
|
|
1286
|
+
.style('stroke-width', this.getPlotStrokeWidth(style));
|
|
1287
|
+
}
|
|
1288
|
+
else {
|
|
1289
|
+
const width = this.getChartWidth();
|
|
1290
|
+
const left = this.settings.margin.left;
|
|
1291
|
+
const lineLength = width - left;
|
|
1292
|
+
line.attr('x1', this.settings.margin.left)
|
|
1293
|
+
.attr('y1', (_f = (_e = scrubPosition.y) === null || _e === void 0 ? void 0 : _e.position) !== null && _f !== void 0 ? _f : 0)
|
|
1294
|
+
.attr('x2', width + lineLength)
|
|
1295
|
+
.attr('y2', (_h = (_g = scrubPosition.y) === null || _g === void 0 ? void 0 : _g.position) !== null && _h !== void 0 ? _h : 0)
|
|
1296
|
+
.attr('stroke', this.getPlotStrokeColor(style))
|
|
1297
|
+
.style('stroke-width', this.getPlotStrokeWidth(style));
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
drawPlotScrubCircle(plotId, x, y, svg, style) {
|
|
1301
|
+
svg.select(`.${this.getPlotScrubCircleClass(plotId)}`).remove();
|
|
1302
|
+
svg.insert('circle')
|
|
1303
|
+
.classed(this.getPlotScrubCircleClass(plotId), true)
|
|
1304
|
+
.attr('r', 6)
|
|
1305
|
+
.attr('cx', x)
|
|
1306
|
+
.attr('cy', y)
|
|
1307
|
+
.style('stroke', this.getPlotStrokeColor(style))
|
|
1308
|
+
.style('stroke-width', this.getPlotStrokeWidth(style))
|
|
1309
|
+
.style('fill-opacity', '0');
|
|
1310
|
+
}
|
|
1311
|
+
erasePlotScrub(plotId) {
|
|
1312
|
+
if (this.chartElements === undefined)
|
|
1313
|
+
return;
|
|
1314
|
+
this.chartElements.svg.select(`.${this.getPlotScrubCircleClass(plotId)}`).remove();
|
|
1315
|
+
this.chartElements.svg.select(`.${this.getPlotScrubLineClass(plotId)}`).remove();
|
|
1316
|
+
}
|
|
1317
|
+
getScrubEvent(event, scrubAxis, xScale, yScale, xAccessor, yAccessor, rows, offsetSettings) {
|
|
1318
|
+
let mousePosition;
|
|
1319
|
+
let offset;
|
|
1320
|
+
let scrubbingScale;
|
|
1321
|
+
let scrubbingAccessor;
|
|
1322
|
+
let valueAccessor;
|
|
1323
|
+
if (scrubAxis === EChartAxes.x) {
|
|
1324
|
+
mousePosition = event.offsetX;
|
|
1325
|
+
offset = offsetSettings.x;
|
|
1326
|
+
scrubbingScale = xScale;
|
|
1327
|
+
scrubbingAccessor = xAccessor;
|
|
1328
|
+
valueAccessor = yAccessor;
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
mousePosition = event.offsetY;
|
|
1332
|
+
offset = offsetSettings.y;
|
|
1333
|
+
scrubbingScale = yScale;
|
|
1334
|
+
scrubbingAccessor = yAccessor;
|
|
1335
|
+
valueAccessor = xAccessor;
|
|
1336
|
+
}
|
|
1337
|
+
const scrubValue = scrubbingScale.invert(mousePosition - offset);
|
|
1338
|
+
const bisect = bisector((row) => scrubbingAccessor(row)).center;
|
|
1339
|
+
const bisectIndex = bisect(rows, scrubValue);
|
|
1340
|
+
const nearestRow = this.getDataRowNearestToScrub(bisectIndex, scrubValue, rows, valueAccessor, scrubbingAccessor);
|
|
1341
|
+
const bisected = nearestRow;
|
|
1342
|
+
const xValue = xAccessor(bisected);
|
|
1343
|
+
const xPosition = xScale(xValue) + offsetSettings.x;
|
|
1344
|
+
const yValue = yAccessor(nearestRow);
|
|
1345
|
+
const yPosition = yScale(yValue) + offsetSettings.y;
|
|
1346
|
+
return {
|
|
1347
|
+
x: {
|
|
1348
|
+
position: xPosition,
|
|
1349
|
+
value: xValue
|
|
1350
|
+
},
|
|
1351
|
+
y: {
|
|
1352
|
+
position: yPosition,
|
|
1353
|
+
value: yValue
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
getDataRowNearestToScrub(bisectIndex, scrubValue, parsedData, valueAccessor, scrubbingAccessor) {
|
|
1358
|
+
const nonEmptyRows = parsedData.filter(row => valueAccessor(row) !== null && valueAccessor(row) !== undefined);
|
|
1359
|
+
let minDistance = Number.MAX_VALUE;
|
|
1360
|
+
let nearestRow = parsedData[bisectIndex];
|
|
1361
|
+
for (let i = 0; i < nonEmptyRows.length; i += 1) {
|
|
1362
|
+
const current = scrubbingAccessor(nonEmptyRows[i]);
|
|
1363
|
+
const distance = Math.abs(+current - +scrubValue);
|
|
1364
|
+
if (distance < minDistance) {
|
|
1365
|
+
minDistance = distance;
|
|
1366
|
+
nearestRow = nonEmptyRows[i];
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return nearestRow;
|
|
1370
|
+
}
|
|
1371
|
+
getPlotStrokeColor(style) {
|
|
1372
|
+
var _a;
|
|
1373
|
+
return (_a = style === null || style === void 0 ? void 0 : style.strokeColor) !== null && _a !== void 0 ? _a : '#333';
|
|
1374
|
+
}
|
|
1375
|
+
getPlotStrokeWidth(style) {
|
|
1376
|
+
var _a;
|
|
1377
|
+
return (_a = style === null || style === void 0 ? void 0 : style.strokeWidth) !== null && _a !== void 0 ? _a : 1;
|
|
1378
|
+
}
|
|
1379
|
+
getPlotScrubCircleClass(plotId) {
|
|
1380
|
+
return `${plotId}-scrub-circle`;
|
|
1381
|
+
}
|
|
1382
|
+
getPlotScrubLineClass(plotId) {
|
|
1383
|
+
return `${plotId}-scrub-line`;
|
|
1384
|
+
}
|
|
1222
1385
|
attachChartElements() {
|
|
1223
1386
|
const chartElements = this.getChartElements();
|
|
1224
1387
|
if (this.element !== undefined && chartElements !== undefined) {
|
|
@@ -1231,7 +1394,6 @@ class AxiomChart extends ChartRoot {
|
|
|
1231
1394
|
this.element.append(canvasNode);
|
|
1232
1395
|
}
|
|
1233
1396
|
}
|
|
1234
|
-
this.addScrubbing();
|
|
1235
1397
|
}
|
|
1236
1398
|
createPlot(plot) {
|
|
1237
1399
|
const props = this.getPlotImplementationProps(plot);
|
|
@@ -1255,67 +1417,6 @@ class AxiomChart extends ChartRoot {
|
|
|
1255
1417
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
1256
1418
|
}
|
|
1257
1419
|
}
|
|
1258
|
-
addScrubbing() {
|
|
1259
|
-
const ctx = this.getCanvasContext();
|
|
1260
|
-
if (ctx === null) {
|
|
1261
|
-
return;
|
|
1262
|
-
}
|
|
1263
|
-
ctx.canvas.addEventListener('touchmove', event => {
|
|
1264
|
-
const touch = event.touches[0];
|
|
1265
|
-
const touchOffset = 20; // TODO: figure out why touch.clientX is always off to the right
|
|
1266
|
-
this.onScrubTriggered(touch.clientX - touchOffset);
|
|
1267
|
-
});
|
|
1268
|
-
ctx.canvas.addEventListener('touchend', event => { this.onScrubEndTriggered(); });
|
|
1269
|
-
ctx.canvas.addEventListener('mousemove', event => { this.onScrubTriggered(event.offsetX); });
|
|
1270
|
-
ctx.canvas.addEventListener('mouseleave', event => { this.onScrubEndTriggered(); });
|
|
1271
|
-
}
|
|
1272
|
-
onScrubTriggered(cursorX) {
|
|
1273
|
-
if (this.plotImplementations === undefined)
|
|
1274
|
-
return;
|
|
1275
|
-
const plotsWithScrubbingEnabled = this.plotImplementations.filter(plot => {
|
|
1276
|
-
return plot.scrubbing !== undefined && plot.scrubbing && plot.type === EPlotTypes.line;
|
|
1277
|
-
});
|
|
1278
|
-
const plotScrubPositions = this.getScrubPositions(cursorX, plotsWithScrubbingEnabled);
|
|
1279
|
-
if (this.onScrub !== undefined) {
|
|
1280
|
-
this.onScrub(plotScrubPositions);
|
|
1281
|
-
}
|
|
1282
|
-
this.runtime.busy = true;
|
|
1283
|
-
this.clearCanvas();
|
|
1284
|
-
const drawPromises = this.plotImplementations.map((plot) => __awaiter(this, void 0, void 0, function* () { yield plot.draw(); }));
|
|
1285
|
-
Promise.all(drawPromises).then(() => {
|
|
1286
|
-
plotsWithScrubbingEnabled.forEach(plot => {
|
|
1287
|
-
const position = plotScrubPositions[plot.id];
|
|
1288
|
-
plot.drawScrubbing(position.xPosition);
|
|
1289
|
-
});
|
|
1290
|
-
this.runtime.busy = false;
|
|
1291
|
-
}).catch(error => {
|
|
1292
|
-
console.error(error);
|
|
1293
|
-
});
|
|
1294
|
-
}
|
|
1295
|
-
onScrubEndTriggered() {
|
|
1296
|
-
this.removeScrubbers();
|
|
1297
|
-
if (this.onScrubEnd !== undefined) {
|
|
1298
|
-
this.onScrubEnd();
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
removeScrubbers() {
|
|
1302
|
-
if (this.plotImplementations === undefined)
|
|
1303
|
-
return;
|
|
1304
|
-
this.runtime.busy = true;
|
|
1305
|
-
this.clearCanvas();
|
|
1306
|
-
const drawPromises = this.plotImplementations.map((plot) => __awaiter(this, void 0, void 0, function* () { yield plot.draw(); }));
|
|
1307
|
-
void Promise.all(drawPromises);
|
|
1308
|
-
}
|
|
1309
|
-
getScrubPositions(mouseX, plots) {
|
|
1310
|
-
const plotScrubPositions = {};
|
|
1311
|
-
plots.forEach(plot => {
|
|
1312
|
-
const scrubPosition = plot.getScrubPosition(mouseX);
|
|
1313
|
-
if (scrubPosition !== undefined) {
|
|
1314
|
-
plotScrubPositions[plot.id] = scrubPosition;
|
|
1315
|
-
}
|
|
1316
|
-
});
|
|
1317
|
-
return plotScrubPositions;
|
|
1318
|
-
}
|
|
1319
1420
|
init(drawImmediately = false) {
|
|
1320
1421
|
const _super = Object.create(null, {
|
|
1321
1422
|
init: { get: () => super.init }
|
|
@@ -1334,7 +1435,10 @@ class AxiomChart extends ChartRoot {
|
|
|
1334
1435
|
|
|
1335
1436
|
class AxiomRadialLine extends AxiomLine {
|
|
1336
1437
|
draw() {
|
|
1337
|
-
|
|
1438
|
+
const _super = Object.create(null, {
|
|
1439
|
+
onLegendReady: { get: () => super.onLegendReady }
|
|
1440
|
+
});
|
|
1441
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1338
1442
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1339
1443
|
const style = this.style !== undefined ? this.style : {};
|
|
1340
1444
|
const data = yield this.getData();
|
|
@@ -1378,21 +1482,32 @@ class AxiomRadialLine extends AxiomLine {
|
|
|
1378
1482
|
.curve(curveLinearClosed);
|
|
1379
1483
|
const radialData = parsedData.slice();
|
|
1380
1484
|
const chartOffset = chart.getChartOffset();
|
|
1485
|
+
const stroke = style.strokeColor !== undefined ? style.strokeColor : '#333';
|
|
1381
1486
|
svg.append('g')
|
|
1382
1487
|
.attr('transform', `translate(${chartOffset.x},${chartOffset.y})`)
|
|
1383
1488
|
.attr('class', 'radial-line')
|
|
1384
1489
|
.append('path')
|
|
1385
1490
|
.attr('fill', 'none')
|
|
1386
|
-
.attr('stroke',
|
|
1491
|
+
.attr('stroke', stroke)
|
|
1387
1492
|
.attr('stroke-width', style.strokeWidth !== undefined ? style.strokeWidth : 1)
|
|
1388
1493
|
.attr('d', line(radialData));
|
|
1494
|
+
if (((_f = this.axes) === null || _f === void 0 ? void 0 : _f.y) !== undefined) {
|
|
1495
|
+
_super.onLegendReady.call(this, [{
|
|
1496
|
+
id: this.axes.y.key,
|
|
1497
|
+
label: (_g = chart.getAxisLabel(this.axes.y, 'y')) !== null && _g !== void 0 ? _g : this.axes.y.key,
|
|
1498
|
+
style: { backgroundColor: stroke }
|
|
1499
|
+
}]);
|
|
1500
|
+
}
|
|
1389
1501
|
});
|
|
1390
1502
|
}
|
|
1391
1503
|
}
|
|
1392
1504
|
|
|
1393
1505
|
class AxiomRadialScatter extends AxiomLine {
|
|
1394
1506
|
draw() {
|
|
1395
|
-
|
|
1507
|
+
const _super = Object.create(null, {
|
|
1508
|
+
onLegendReady: { get: () => super.onLegendReady }
|
|
1509
|
+
});
|
|
1510
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1396
1511
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1397
1512
|
// const style = this.style !== undefined ? this.style : {}
|
|
1398
1513
|
const data = yield this.getData();
|
|
@@ -1431,23 +1546,38 @@ class AxiomRadialScatter extends AxiomLine {
|
|
|
1431
1546
|
.attr('class', 'radial-scatter');
|
|
1432
1547
|
const style = this.style !== undefined ? this.style : {};
|
|
1433
1548
|
const radius = style.radius !== undefined ? style.radius : 2;
|
|
1549
|
+
const fill = style.fill !== undefined ? style.fill : '#333';
|
|
1550
|
+
const stroke = style.strokeColor !== undefined ? style.strokeColor : '#CCC';
|
|
1434
1551
|
parsedData.forEach(d => {
|
|
1435
1552
|
const pt = pointRadial(xPosition(d), yPosition(d));
|
|
1436
1553
|
g.append('circle')
|
|
1437
1554
|
.attr('r', radius * 2)
|
|
1438
|
-
.attr('fill',
|
|
1439
|
-
.attr('stroke',
|
|
1555
|
+
.attr('fill', fill)
|
|
1556
|
+
.attr('stroke', stroke)
|
|
1440
1557
|
.attr('stroke-width', (style.strokeWidth !== undefined || style.strokeColor !== undefined) ? ((style.strokeWidth !== undefined ? style.strokeWidth : 1)) : 0)
|
|
1441
1558
|
.attr('cx', pt[0] + chart.settings.margin.left)
|
|
1442
1559
|
.attr('cy', pt[1] + chart.settings.margin.top);
|
|
1443
1560
|
});
|
|
1561
|
+
if (((_f = this.axes) === null || _f === void 0 ? void 0 : _f.y) !== undefined) {
|
|
1562
|
+
_super.onLegendReady.call(this, [{
|
|
1563
|
+
id: this.axes.y.key,
|
|
1564
|
+
label: (_g = chart.getAxisLabel(this.axes.y, 'y')) !== null && _g !== void 0 ? _g : this.axes.y.key,
|
|
1565
|
+
style: {
|
|
1566
|
+
backgroundColor: fill,
|
|
1567
|
+
border: `1px solid ${stroke}`
|
|
1568
|
+
}
|
|
1569
|
+
}]);
|
|
1570
|
+
}
|
|
1444
1571
|
});
|
|
1445
1572
|
}
|
|
1446
1573
|
}
|
|
1447
1574
|
|
|
1448
1575
|
class AxiomRadialArea extends AxiomArea {
|
|
1449
1576
|
draw() {
|
|
1450
|
-
|
|
1577
|
+
const _super = Object.create(null, {
|
|
1578
|
+
onLegendReady: { get: () => super.onLegendReady }
|
|
1579
|
+
});
|
|
1580
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1451
1581
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1452
1582
|
const style = this.style !== undefined ? this.style : {};
|
|
1453
1583
|
const data = yield this.getData();
|
|
@@ -1502,21 +1632,172 @@ class AxiomRadialArea extends AxiomArea {
|
|
|
1502
1632
|
.curve(curveLinearClosed);
|
|
1503
1633
|
const radialData = parsedData.slice();
|
|
1504
1634
|
const chartOffset = chart.getChartOffset();
|
|
1635
|
+
const fill = style.fill !== undefined ? style.fill : '#EDEDED99';
|
|
1505
1636
|
svg.insert('g', 'g.axis')
|
|
1506
1637
|
.attr('transform', `translate(${chartOffset.x},${chartOffset.y})`)
|
|
1507
1638
|
.attr('class', 'radial-area')
|
|
1508
1639
|
.append('path')
|
|
1509
|
-
.attr('fill',
|
|
1640
|
+
.attr('fill', fill)
|
|
1510
1641
|
.attr('stroke-width', style.strokeWidth !== undefined ? style.strokeWidth : 1)
|
|
1511
1642
|
.attr('d', area(radialData));
|
|
1643
|
+
if (((_f = this.axes) === null || _f === void 0 ? void 0 : _f.y) !== undefined) {
|
|
1644
|
+
_super.onLegendReady.call(this, [{
|
|
1645
|
+
id: this.axes.y.key,
|
|
1646
|
+
label: (_g = chart.getAxisLabel(this.axes.y, 'y')) !== null && _g !== void 0 ? _g : this.axes.y.key,
|
|
1647
|
+
style: { backgroundColor: fill }
|
|
1648
|
+
}]);
|
|
1649
|
+
}
|
|
1512
1650
|
});
|
|
1513
1651
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
class AxiomRadialRose extends AxiomPlotRoot {
|
|
1655
|
+
constructor() {
|
|
1656
|
+
super(...arguments);
|
|
1657
|
+
this.data = undefined;
|
|
1658
|
+
this.numBins = 16;
|
|
1659
|
+
this.speedBins = d3.range(1, 8, 1);
|
|
1660
|
+
this.stacks = undefined;
|
|
1516
1661
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
return
|
|
1662
|
+
setStackedData() {
|
|
1663
|
+
var _a, _b;
|
|
1664
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1665
|
+
let parsedData = [];
|
|
1666
|
+
if (this.data === undefined) {
|
|
1667
|
+
this.data = yield this.getData();
|
|
1668
|
+
parsedData = (_b = (_a = this.data.parsed) === null || _a === void 0 ? void 0 : _a.data) !== null && _b !== void 0 ? _b : [];
|
|
1669
|
+
}
|
|
1670
|
+
if (this.stacks === undefined) {
|
|
1671
|
+
this.stacks = this.getStackedData(parsedData);
|
|
1672
|
+
}
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
getDomain(_axis) {
|
|
1676
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1677
|
+
yield this.setStackedData();
|
|
1678
|
+
if (this.stacks === undefined)
|
|
1679
|
+
return [0, 10];
|
|
1680
|
+
return [0, this.getMaxStackTotal(this.stacks)];
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
getStackedData(parsedData) {
|
|
1684
|
+
const degreesPerBin = 360 / this.numBins;
|
|
1685
|
+
const directionBins = d3.range(0, 360, degreesPerBin);
|
|
1686
|
+
const speedBins = this.speedBins;
|
|
1687
|
+
const stacks = [];
|
|
1688
|
+
directionBins.forEach(direction => {
|
|
1689
|
+
const newBin = {
|
|
1690
|
+
minDirection: direction
|
|
1691
|
+
};
|
|
1692
|
+
speedBins.forEach(bin => { newBin[bin] = 0; });
|
|
1693
|
+
stacks.push(newBin);
|
|
1694
|
+
});
|
|
1695
|
+
parsedData.forEach(row => {
|
|
1696
|
+
const directionBinIndex = Math.floor(row.direction / degreesPerBin);
|
|
1697
|
+
const stack = stacks[directionBinIndex];
|
|
1698
|
+
if (row.speed < speedBins[speedBins.length - 1]) {
|
|
1699
|
+
const speedBin = Math.floor(row.speed);
|
|
1700
|
+
stack[speedBin] += 1;
|
|
1701
|
+
}
|
|
1702
|
+
});
|
|
1703
|
+
return stacks;
|
|
1704
|
+
}
|
|
1705
|
+
getMaxStackTotal(stacks) {
|
|
1706
|
+
var _a;
|
|
1707
|
+
return (_a = d3.max(stacks, stack => {
|
|
1708
|
+
return this.speedBins.reduce((sum, current) => sum + stack[current], 0);
|
|
1709
|
+
})) !== null && _a !== void 0 ? _a : 0;
|
|
1710
|
+
}
|
|
1711
|
+
draw() {
|
|
1712
|
+
const _super = Object.create(null, {
|
|
1713
|
+
onLegendReady: { get: () => super.onLegendReady }
|
|
1714
|
+
});
|
|
1715
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1716
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1717
|
+
yield this.setStackedData();
|
|
1718
|
+
if (this.stacks === undefined || this.speedBins === undefined)
|
|
1719
|
+
return;
|
|
1720
|
+
const xAccessors = this.getAccessorsAtAxis('x');
|
|
1721
|
+
const xAccessor = this.getAccessorAtAxis('x');
|
|
1722
|
+
const x0Accessor = xAccessors.x0;
|
|
1723
|
+
const x1Accessor = xAccessors.x1;
|
|
1724
|
+
const yAccessors = this.getAccessorsAtAxis('y');
|
|
1725
|
+
const yAccessor = this.getAccessorAtAxis('y');
|
|
1726
|
+
const y0Accessor = yAccessors.y0;
|
|
1727
|
+
const y1Accessor = yAccessors.y1;
|
|
1728
|
+
const chart = this.chart;
|
|
1729
|
+
const chartElements = chart.getChartElements();
|
|
1730
|
+
if (chartElements === undefined || chartElements.svg === null) {
|
|
1731
|
+
return undefined;
|
|
1732
|
+
}
|
|
1733
|
+
const svg = chartElements.svg;
|
|
1734
|
+
const xAxisScale = (_b = (_a = this === null || this === void 0 ? void 0 : this.axes) === null || _a === void 0 ? void 0 : _a.x) === null || _b === void 0 ? void 0 : _b.scale;
|
|
1735
|
+
const yAxisScale = (_d = (_c = this === null || this === void 0 ? void 0 : this.axes) === null || _c === void 0 ? void 0 : _c.y) === null || _d === void 0 ? void 0 : _d.scale;
|
|
1736
|
+
if (xAxisScale === undefined ||
|
|
1737
|
+
yAxisScale === undefined ||
|
|
1738
|
+
(xAccessor === undefined && x0Accessor === undefined && x1Accessor === undefined) ||
|
|
1739
|
+
(yAccessor === undefined && y0Accessor === undefined && y1Accessor === undefined)) {
|
|
1740
|
+
console.log('Missing one of: xAxisScale, yAxisScale, parsedData, xAccessor(s), yAccessor(s)');
|
|
1741
|
+
return undefined;
|
|
1742
|
+
}
|
|
1743
|
+
const maxStackTotal = this.getMaxStackTotal(this.stacks);
|
|
1744
|
+
const xScale = d3.scaleBand()
|
|
1745
|
+
.domain(d3.range(0, 360, 360 / this.numBins))
|
|
1746
|
+
.range([0, 2 * Math.PI]);
|
|
1747
|
+
const yScale = d3.scaleRadial()
|
|
1748
|
+
.domain([0, maxStackTotal])
|
|
1749
|
+
.range([chart.getInnerRadius(), chart.getOuterRadius()]);
|
|
1750
|
+
const fillColors = (_f = (_e = this.style) === null || _e === void 0 ? void 0 : _e.colors) !== null && _f !== void 0 ? _f : d3.schemeTableau10;
|
|
1751
|
+
const zScale = d3
|
|
1752
|
+
.scaleOrdinal()
|
|
1753
|
+
.range(fillColors);
|
|
1754
|
+
const data = d3.stack().keys(this.speedBins.map(bin => String(bin)))(this.stacks);
|
|
1755
|
+
const chartOffset = chart.getChartOffset();
|
|
1756
|
+
const bandwidth = 2 * Math.PI / this.numBins;
|
|
1757
|
+
svg.insert('g', 'g.axis')
|
|
1758
|
+
.attr('transform', `translate(${chartOffset.x},${chartOffset.y})`)
|
|
1759
|
+
.selectAll('g')
|
|
1760
|
+
.data(data)
|
|
1761
|
+
.enter().append('g')
|
|
1762
|
+
.attr('fill', (d) => zScale(d.key))
|
|
1763
|
+
.selectAll('path')
|
|
1764
|
+
.data(d => d)
|
|
1765
|
+
.enter().append('path')
|
|
1766
|
+
.on('mouseover', (event, data) => { this.onMouseScrub(event, data); })
|
|
1767
|
+
.on('mousemove', (event, data) => { this.onMouseScrub(event, data); })
|
|
1768
|
+
.on('mouseleave', () => { this.onScrubEnd(); })
|
|
1769
|
+
.attr('d', d3.arc()
|
|
1770
|
+
.innerRadius(d => yScale(d[0]))
|
|
1771
|
+
.outerRadius(d => yScale(d[1]))
|
|
1772
|
+
.startAngle(d => { var _a; return ((_a = xScale(d.data.minDirection)) !== null && _a !== void 0 ? _a : 0) - (bandwidth / 2); })
|
|
1773
|
+
.endAngle(d => { var _a; return ((_a = xScale(d.data.minDirection)) !== null && _a !== void 0 ? _a : 0) + bandwidth / 2; })
|
|
1774
|
+
.padAngle(0.01)
|
|
1775
|
+
.padRadius(chart.getInnerRadius()));
|
|
1776
|
+
_super.onLegendReady.call(this, data.map((d, i) => {
|
|
1777
|
+
var _a, _b;
|
|
1778
|
+
return ({
|
|
1779
|
+
id: d.key,
|
|
1780
|
+
label: `< ${this.speedBins[i]} ${(_b = (_a = this.dimensions.z.displayUnits) === null || _a === void 0 ? void 0 : _a.label) !== null && _b !== void 0 ? _b : ''}`,
|
|
1781
|
+
style: {
|
|
1782
|
+
backgroundColor: zScale(d.key)
|
|
1783
|
+
}
|
|
1784
|
+
});
|
|
1785
|
+
}));
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
onMouseScrub(event, data) {
|
|
1789
|
+
if (this.chart.onPlotScrub === undefined)
|
|
1790
|
+
return;
|
|
1791
|
+
this.onScrub({
|
|
1792
|
+
x: {
|
|
1793
|
+
position: event.offsetX,
|
|
1794
|
+
value: data.data.angle
|
|
1795
|
+
},
|
|
1796
|
+
y: {
|
|
1797
|
+
position: event.offsetY,
|
|
1798
|
+
value: Object.assign({}, data)
|
|
1799
|
+
}
|
|
1800
|
+
});
|
|
1520
1801
|
}
|
|
1521
1802
|
}
|
|
1522
1803
|
|
|
@@ -1617,6 +1898,7 @@ class AxiomRadialChart extends AxiomChart {
|
|
|
1617
1898
|
return this.getChartOffset();
|
|
1618
1899
|
}
|
|
1619
1900
|
getXAxis(axis, _axisSettings) {
|
|
1901
|
+
var _a, _b, _c;
|
|
1620
1902
|
if (axis.scale === undefined) {
|
|
1621
1903
|
return g => g;
|
|
1622
1904
|
}
|
|
@@ -1627,7 +1909,7 @@ class AxiomRadialChart extends AxiomChart {
|
|
|
1627
1909
|
const tickFormat = d3Scale.tickFormat();
|
|
1628
1910
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1629
1911
|
// @ts-expect-error
|
|
1630
|
-
const ticks = d3Scale.ticks().map(tickFormat);
|
|
1912
|
+
const ticks = (_c = (_b = (_a = this.settings.axes) === null || _a === void 0 ? void 0 : _a.x) === null || _b === void 0 ? void 0 : _b.ticks) !== null && _c !== void 0 ? _c : d3Scale.ticks().map(tickFormat);
|
|
1631
1913
|
const rotateOffset = -90;
|
|
1632
1914
|
const getRotate = (index, len) => {
|
|
1633
1915
|
const r = (index * 360 / len);
|
|
@@ -1653,6 +1935,7 @@ class AxiomRadialChart extends AxiomChart {
|
|
|
1653
1935
|
.attr('x1', -5)
|
|
1654
1936
|
.attr('x2', outerRadius - innerRadius + 10))
|
|
1655
1937
|
.call(g => g.append('text')
|
|
1938
|
+
.attr('text-anchor', 'middle')
|
|
1656
1939
|
.attr('transform', 'rotate(90)translate(0,16)')
|
|
1657
1940
|
.text((d) => d)));
|
|
1658
1941
|
return g;
|
|
@@ -1670,553 +1953,10 @@ class AxiomRadialChart extends AxiomChart {
|
|
|
1670
1953
|
else if (plot.type === 'area') {
|
|
1671
1954
|
return new AxiomRadialArea(props);
|
|
1672
1955
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
var Direction;
|
|
1678
|
-
(function (Direction) {
|
|
1679
|
-
Direction["N"] = "N";
|
|
1680
|
-
Direction["NNE"] = "NNE";
|
|
1681
|
-
Direction["NE"] = "NE";
|
|
1682
|
-
Direction["ENE"] = "ENE";
|
|
1683
|
-
Direction["E"] = "E";
|
|
1684
|
-
Direction["ESE"] = "ESE";
|
|
1685
|
-
Direction["SE"] = "SE";
|
|
1686
|
-
Direction["SSE"] = "SSE";
|
|
1687
|
-
Direction["S"] = "S";
|
|
1688
|
-
Direction["SSW"] = "SSW";
|
|
1689
|
-
Direction["SW"] = "SW";
|
|
1690
|
-
Direction["WSW"] = "WSW";
|
|
1691
|
-
Direction["W"] = "W";
|
|
1692
|
-
Direction["WNW"] = "WNW";
|
|
1693
|
-
Direction["NW"] = "NW";
|
|
1694
|
-
Direction["NNW"] = "NNW";
|
|
1695
|
-
})(Direction || (Direction = {}));
|
|
1696
|
-
|
|
1697
|
-
const DEFAULT_COLUMNS = ['angle', '0-1', '1-2', '2-3', '3-4', '4-5', '5-6', '6-7', '7+'];
|
|
1698
|
-
function classifyDir(direction) {
|
|
1699
|
-
const dTh = 11.25;
|
|
1700
|
-
let dir;
|
|
1701
|
-
if (direction >= 360 - dTh || direction < dTh) {
|
|
1702
|
-
dir = Direction.N;
|
|
1703
|
-
}
|
|
1704
|
-
else if (direction >= dTh && direction < dTh * 3) {
|
|
1705
|
-
dir = Direction.NNE;
|
|
1706
|
-
}
|
|
1707
|
-
else if (direction >= dTh * 3 && direction < dTh * 5) {
|
|
1708
|
-
dir = Direction.NE;
|
|
1709
|
-
}
|
|
1710
|
-
else if (direction >= dTh * 5 && direction < dTh * 7) {
|
|
1711
|
-
dir = Direction.ENE;
|
|
1712
|
-
}
|
|
1713
|
-
else if (direction >= dTh * 7 && direction < dTh * 9) {
|
|
1714
|
-
dir = Direction.E;
|
|
1715
|
-
}
|
|
1716
|
-
else if (direction >= dTh * 9 && direction < dTh * 11) {
|
|
1717
|
-
dir = Direction.ESE;
|
|
1718
|
-
}
|
|
1719
|
-
else if (direction >= dTh * 11 && direction < dTh * 13) {
|
|
1720
|
-
dir = Direction.SE;
|
|
1721
|
-
}
|
|
1722
|
-
else if (direction >= dTh * 13 && direction < dTh * 15) {
|
|
1723
|
-
dir = Direction.SSE;
|
|
1724
|
-
}
|
|
1725
|
-
else if (direction >= dTh * 15 && direction < dTh * 17) {
|
|
1726
|
-
dir = Direction.S;
|
|
1727
|
-
}
|
|
1728
|
-
else if (direction >= dTh * 17 && direction < dTh * 19) {
|
|
1729
|
-
dir = Direction.SSW;
|
|
1730
|
-
}
|
|
1731
|
-
else if (direction >= dTh * 19 && direction < dTh * 21) {
|
|
1732
|
-
dir = Direction.SW;
|
|
1733
|
-
}
|
|
1734
|
-
else if (direction >= dTh * 21 && direction < dTh * 23) {
|
|
1735
|
-
dir = Direction.WSW;
|
|
1736
|
-
}
|
|
1737
|
-
else if (direction >= dTh * 23 && direction < dTh * 25) {
|
|
1738
|
-
dir = Direction.W;
|
|
1739
|
-
}
|
|
1740
|
-
else if (direction >= dTh * 25 && direction < dTh * 27) {
|
|
1741
|
-
dir = Direction.WNW;
|
|
1742
|
-
}
|
|
1743
|
-
else if (direction >= dTh * 27 && direction < dTh * 29) {
|
|
1744
|
-
dir = Direction.NW;
|
|
1745
|
-
}
|
|
1746
|
-
else if (direction >= dTh * 29 && direction < dTh * 31) {
|
|
1747
|
-
dir = Direction.NNW;
|
|
1748
|
-
}
|
|
1749
|
-
else {
|
|
1750
|
-
throw new Error('over 360 or under 0');
|
|
1751
|
-
}
|
|
1752
|
-
return dir;
|
|
1753
|
-
}
|
|
1754
|
-
function getNewCounts() {
|
|
1755
|
-
return {
|
|
1756
|
-
N: {
|
|
1757
|
-
'0-1': 0,
|
|
1758
|
-
'1-2': 0,
|
|
1759
|
-
'2-3': 0,
|
|
1760
|
-
'3-4': 0,
|
|
1761
|
-
'4-5': 0,
|
|
1762
|
-
'5-6': 0,
|
|
1763
|
-
'6-7': 0,
|
|
1764
|
-
'7+': 0
|
|
1765
|
-
},
|
|
1766
|
-
NNE: {
|
|
1767
|
-
'0-1': 0,
|
|
1768
|
-
'1-2': 0,
|
|
1769
|
-
'2-3': 0,
|
|
1770
|
-
'3-4': 0,
|
|
1771
|
-
'4-5': 0,
|
|
1772
|
-
'5-6': 0,
|
|
1773
|
-
'6-7': 0,
|
|
1774
|
-
'7+': 0
|
|
1775
|
-
},
|
|
1776
|
-
NE: {
|
|
1777
|
-
'0-1': 0,
|
|
1778
|
-
'1-2': 0,
|
|
1779
|
-
'2-3': 0,
|
|
1780
|
-
'3-4': 0,
|
|
1781
|
-
'4-5': 0,
|
|
1782
|
-
'5-6': 0,
|
|
1783
|
-
'6-7': 0,
|
|
1784
|
-
'7+': 0
|
|
1785
|
-
},
|
|
1786
|
-
ENE: {
|
|
1787
|
-
'0-1': 0,
|
|
1788
|
-
'1-2': 0,
|
|
1789
|
-
'2-3': 0,
|
|
1790
|
-
'3-4': 0,
|
|
1791
|
-
'4-5': 0,
|
|
1792
|
-
'5-6': 0,
|
|
1793
|
-
'6-7': 0,
|
|
1794
|
-
'7+': 0
|
|
1795
|
-
},
|
|
1796
|
-
E: {
|
|
1797
|
-
'0-1': 0,
|
|
1798
|
-
'1-2': 0,
|
|
1799
|
-
'2-3': 0,
|
|
1800
|
-
'3-4': 0,
|
|
1801
|
-
'4-5': 0,
|
|
1802
|
-
'5-6': 0,
|
|
1803
|
-
'6-7': 0,
|
|
1804
|
-
'7+': 0
|
|
1805
|
-
},
|
|
1806
|
-
ESE: {
|
|
1807
|
-
'0-1': 0,
|
|
1808
|
-
'1-2': 0,
|
|
1809
|
-
'2-3': 0,
|
|
1810
|
-
'3-4': 0,
|
|
1811
|
-
'4-5': 0,
|
|
1812
|
-
'5-6': 0,
|
|
1813
|
-
'6-7': 0,
|
|
1814
|
-
'7+': 0
|
|
1815
|
-
},
|
|
1816
|
-
SE: {
|
|
1817
|
-
'0-1': 0,
|
|
1818
|
-
'1-2': 0,
|
|
1819
|
-
'2-3': 0,
|
|
1820
|
-
'3-4': 0,
|
|
1821
|
-
'4-5': 0,
|
|
1822
|
-
'5-6': 0,
|
|
1823
|
-
'6-7': 0,
|
|
1824
|
-
'7+': 0
|
|
1825
|
-
},
|
|
1826
|
-
SSE: {
|
|
1827
|
-
'0-1': 0,
|
|
1828
|
-
'1-2': 0,
|
|
1829
|
-
'2-3': 0,
|
|
1830
|
-
'3-4': 0,
|
|
1831
|
-
'4-5': 0,
|
|
1832
|
-
'5-6': 0,
|
|
1833
|
-
'6-7': 0,
|
|
1834
|
-
'7+': 0
|
|
1835
|
-
},
|
|
1836
|
-
S: {
|
|
1837
|
-
'0-1': 0,
|
|
1838
|
-
'1-2': 0,
|
|
1839
|
-
'2-3': 0,
|
|
1840
|
-
'3-4': 0,
|
|
1841
|
-
'4-5': 0,
|
|
1842
|
-
'5-6': 0,
|
|
1843
|
-
'6-7': 0,
|
|
1844
|
-
'7+': 0
|
|
1845
|
-
},
|
|
1846
|
-
SSW: {
|
|
1847
|
-
'0-1': 0,
|
|
1848
|
-
'1-2': 0,
|
|
1849
|
-
'2-3': 0,
|
|
1850
|
-
'3-4': 0,
|
|
1851
|
-
'4-5': 0,
|
|
1852
|
-
'5-6': 0,
|
|
1853
|
-
'6-7': 0,
|
|
1854
|
-
'7+': 0
|
|
1855
|
-
},
|
|
1856
|
-
SW: {
|
|
1857
|
-
'0-1': 0,
|
|
1858
|
-
'1-2': 0,
|
|
1859
|
-
'2-3': 0,
|
|
1860
|
-
'3-4': 0,
|
|
1861
|
-
'4-5': 0,
|
|
1862
|
-
'5-6': 0,
|
|
1863
|
-
'6-7': 0,
|
|
1864
|
-
'7+': 0
|
|
1865
|
-
},
|
|
1866
|
-
WSW: {
|
|
1867
|
-
'0-1': 0,
|
|
1868
|
-
'1-2': 0,
|
|
1869
|
-
'2-3': 0,
|
|
1870
|
-
'3-4': 0,
|
|
1871
|
-
'4-5': 0,
|
|
1872
|
-
'5-6': 0,
|
|
1873
|
-
'6-7': 0,
|
|
1874
|
-
'7+': 0
|
|
1875
|
-
},
|
|
1876
|
-
W: {
|
|
1877
|
-
'0-1': 0,
|
|
1878
|
-
'1-2': 0,
|
|
1879
|
-
'2-3': 0,
|
|
1880
|
-
'3-4': 0,
|
|
1881
|
-
'4-5': 0,
|
|
1882
|
-
'5-6': 0,
|
|
1883
|
-
'6-7': 0,
|
|
1884
|
-
'7+': 0
|
|
1885
|
-
},
|
|
1886
|
-
WNW: {
|
|
1887
|
-
'0-1': 0,
|
|
1888
|
-
'1-2': 0,
|
|
1889
|
-
'2-3': 0,
|
|
1890
|
-
'3-4': 0,
|
|
1891
|
-
'4-5': 0,
|
|
1892
|
-
'5-6': 0,
|
|
1893
|
-
'6-7': 0,
|
|
1894
|
-
'7+': 0
|
|
1895
|
-
},
|
|
1896
|
-
NW: {
|
|
1897
|
-
'0-1': 0,
|
|
1898
|
-
'1-2': 0,
|
|
1899
|
-
'2-3': 0,
|
|
1900
|
-
'3-4': 0,
|
|
1901
|
-
'4-5': 0,
|
|
1902
|
-
'5-6': 0,
|
|
1903
|
-
'6-7': 0,
|
|
1904
|
-
'7+': 0
|
|
1905
|
-
},
|
|
1906
|
-
NNW: {
|
|
1907
|
-
'0-1': 0,
|
|
1908
|
-
'1-2': 0,
|
|
1909
|
-
'2-3': 0,
|
|
1910
|
-
'3-4': 0,
|
|
1911
|
-
'4-5': 0,
|
|
1912
|
-
'5-6': 0,
|
|
1913
|
-
'6-7': 0,
|
|
1914
|
-
'7+': 0
|
|
1915
|
-
}
|
|
1916
|
-
};
|
|
1917
|
-
}
|
|
1918
|
-
function countPush(count, dir, speed) {
|
|
1919
|
-
if (speed < 1) {
|
|
1920
|
-
count[dir]['0-1'] = count[dir]['0-1'] + 1;
|
|
1921
|
-
}
|
|
1922
|
-
else if (speed < 2) {
|
|
1923
|
-
count[dir]['1-2'] = count[dir]['1-2'] + 1;
|
|
1924
|
-
}
|
|
1925
|
-
else if (speed < 3) {
|
|
1926
|
-
count[dir]['2-3'] = count[dir]['2-3'] + 1;
|
|
1927
|
-
}
|
|
1928
|
-
else if (speed < 4) {
|
|
1929
|
-
count[dir]['3-4'] = count[dir]['3-4'] + 1;
|
|
1930
|
-
}
|
|
1931
|
-
else if (speed < 5) {
|
|
1932
|
-
count[dir]['5-6'] = count[dir]['5-6'] + 1;
|
|
1933
|
-
}
|
|
1934
|
-
else if (speed < 6) {
|
|
1935
|
-
count[dir]['6-7'] = count[dir]['6-7'] + 1;
|
|
1936
|
-
}
|
|
1937
|
-
else {
|
|
1938
|
-
count[dir]['7+'] = count[dir]['7+'] + 1;
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
function calculateWindRoseFrequency(data) {
|
|
1942
|
-
// const dataLength = data.direction.length
|
|
1943
|
-
const count = getNewCounts();
|
|
1944
|
-
data.direction.map((direction, index) => {
|
|
1945
|
-
const speed = data.speed[index];
|
|
1946
|
-
const dir = classifyDir(direction);
|
|
1947
|
-
countPush(count, dir, speed);
|
|
1948
|
-
return null;
|
|
1949
|
-
});
|
|
1950
|
-
const ret = Object.keys(count).map((key) => {
|
|
1951
|
-
let total = 0;
|
|
1952
|
-
const elements = Object.keys(count[key]).map((subkey) => {
|
|
1953
|
-
// const E = count[key as Direction][subkey as SubKey] / data.direction.length
|
|
1954
|
-
const E = count[key][subkey];
|
|
1955
|
-
total += E;
|
|
1956
|
-
return { [subkey]: E };
|
|
1957
|
-
});
|
|
1958
|
-
const obj = {};
|
|
1959
|
-
elements.map((val) => {
|
|
1960
|
-
const [elKey] = Object.keys(val);
|
|
1961
|
-
const [elVal] = Object.values(val);
|
|
1962
|
-
obj[elKey] = elVal;
|
|
1963
|
-
return obj;
|
|
1964
|
-
});
|
|
1965
|
-
return Object.assign(Object.assign({ angle: key }, obj), { total });
|
|
1966
|
-
});
|
|
1967
|
-
return ret;
|
|
1968
|
-
}
|
|
1969
|
-
function calculateWindRoseProportions(data) {
|
|
1970
|
-
const frequency = calculateWindRoseFrequency(data);
|
|
1971
|
-
frequency.forEach(direction => {
|
|
1972
|
-
Object.keys(direction).forEach(key => {
|
|
1973
|
-
if (key === 'angle' || key === 'total')
|
|
1974
|
-
return;
|
|
1975
|
-
direction[key] = direction[key] / direction.total;
|
|
1976
|
-
});
|
|
1977
|
-
});
|
|
1978
|
-
return frequency;
|
|
1979
|
-
}
|
|
1980
|
-
|
|
1981
|
-
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
1982
|
-
class AxiomWindrose extends AxiomPlotRoot {
|
|
1983
|
-
constructor(props) {
|
|
1984
|
-
super(props);
|
|
1985
|
-
this.Y_DOMAIN_TYPE = 'frequency';
|
|
1986
|
-
this.raw = {
|
|
1987
|
-
direction: [],
|
|
1988
|
-
speed: []
|
|
1989
|
-
};
|
|
1990
|
-
this.columns = DEFAULT_COLUMNS;
|
|
1991
|
-
const chart = this.chart;
|
|
1992
|
-
const chartElements = chart.getChartElements();
|
|
1993
|
-
if ((chartElements === null || chartElements === void 0 ? void 0 : chartElements.svg) !== undefined) {
|
|
1994
|
-
chartElements.svg.style('z-index', 20);
|
|
1995
|
-
// TODO find better way ^. Doing this so that svg can get events.
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
initializeData() {
|
|
1999
|
-
if (this.Y_DOMAIN_TYPE === 'frequency') {
|
|
2000
|
-
return calculateWindRoseFrequency(this.raw);
|
|
2001
|
-
}
|
|
2002
|
-
else {
|
|
2003
|
-
return calculateWindRoseProportions(this.raw);
|
|
1956
|
+
else if (plot.type === 'windrose') {
|
|
1957
|
+
return new AxiomRadialRose(props);
|
|
2004
1958
|
}
|
|
2005
|
-
|
|
2006
|
-
draw() {
|
|
2007
|
-
var _a, _b, _c, _d, _e;
|
|
2008
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
2009
|
-
const chart = this.chart;
|
|
2010
|
-
const chartElements = chart.getChartElements();
|
|
2011
|
-
if (chartElements === undefined || chartElements.svg === null) {
|
|
2012
|
-
return undefined;
|
|
2013
|
-
}
|
|
2014
|
-
let { width, height } = chart.settings;
|
|
2015
|
-
width = Number(width);
|
|
2016
|
-
height = Number(height);
|
|
2017
|
-
const { margin } = chart.settings;
|
|
2018
|
-
const innerRadius = 20;
|
|
2019
|
-
const chartWidth = width - margin.left - margin.right;
|
|
2020
|
-
const chartHeight = height - margin.top - margin.bottom;
|
|
2021
|
-
const legendGap = 10;
|
|
2022
|
-
const outerRadius = Math.min(chartWidth, chartHeight) / 2.4 - legendGap / 2;
|
|
2023
|
-
this.raw = {
|
|
2024
|
-
direction: [],
|
|
2025
|
-
speed: []
|
|
2026
|
-
};
|
|
2027
|
-
this.runtime.data.parsed.data.forEach((row) => {
|
|
2028
|
-
this.raw.direction.push(row.direction);
|
|
2029
|
-
this.raw.speed.push(row.speed);
|
|
2030
|
-
});
|
|
2031
|
-
const data = this.initializeData();
|
|
2032
|
-
if (this.g !== undefined) {
|
|
2033
|
-
this.g.remove();
|
|
2034
|
-
}
|
|
2035
|
-
const svg = chartElements.svg;
|
|
2036
|
-
const g = svg
|
|
2037
|
-
.insert('g')
|
|
2038
|
-
.attr('transform', `translate(${width / 2},${height / 2})`);
|
|
2039
|
-
this.g = g;
|
|
2040
|
-
const angle = d3.scaleLinear().range([0, 2 * Math.PI]);
|
|
2041
|
-
const radius = d3.scaleLinear().range([innerRadius, outerRadius]);
|
|
2042
|
-
const x = d3
|
|
2043
|
-
.scaleBand()
|
|
2044
|
-
.range([0, 2 * Math.PI])
|
|
2045
|
-
.align(0);
|
|
2046
|
-
const y = d3
|
|
2047
|
-
.scaleLinear() // you can try scaleRadial but it scales differently
|
|
2048
|
-
.range([innerRadius, outerRadius]);
|
|
2049
|
-
const fillColors = (_b = (_a = this.style) === null || _a === void 0 ? void 0 : _a.colors) !== null && _b !== void 0 ? _b : d3.schemeTableau10;
|
|
2050
|
-
const z = d3
|
|
2051
|
-
.scaleOrdinal()
|
|
2052
|
-
.range(fillColors);
|
|
2053
|
-
x.domain(data.map((d) => String(d.angle)));
|
|
2054
|
-
if (this.Y_DOMAIN_TYPE === 'frequency') {
|
|
2055
|
-
y.domain([
|
|
2056
|
-
0,
|
|
2057
|
-
((_c = d3.max(data, (d) => d.total)) !== null && _c !== void 0 ? _c : 0) > 1
|
|
2058
|
-
? (_d = d3.max(data, (d) => d.total)) !== null && _d !== void 0 ? _d : 0
|
|
2059
|
-
: 1
|
|
2060
|
-
]);
|
|
2061
|
-
}
|
|
2062
|
-
else if (this.Y_DOMAIN_TYPE === 'proportion') {
|
|
2063
|
-
y.domain([0, 1]);
|
|
2064
|
-
}
|
|
2065
|
-
z.domain(this.columns.slice(1));
|
|
2066
|
-
// Extend the domain slightly to match the range of [0, 2π].
|
|
2067
|
-
angle.domain([0, (_e = d3.max(data, (_, i) => i + 1)) !== null && _e !== void 0 ? _e : 0]);
|
|
2068
|
-
// radius.domain([0, d3.max(data, (): number => 0) || 0])
|
|
2069
|
-
radius.domain([0, 0]);
|
|
2070
|
-
// radius.domain([innerRadius, outerRadius]);
|
|
2071
|
-
const angleOffset = -360.0 / data.length / 2.0;
|
|
2072
|
-
// @ts-expect-error
|
|
2073
|
-
const stackGen = d3
|
|
2074
|
-
.stack()
|
|
2075
|
-
.keys(this.columns.slice(1));
|
|
2076
|
-
const arcVal = d3 // d3.DefaultArcObject
|
|
2077
|
-
.arc()
|
|
2078
|
-
// @ts-expect-error
|
|
2079
|
-
.innerRadius((d) => Number(y(d[0])))
|
|
2080
|
-
// @ts-expect-error
|
|
2081
|
-
.outerRadius((d) => Number(y(d[1])))
|
|
2082
|
-
// @ts-expect-error
|
|
2083
|
-
.startAngle((d) => Number(x(d.data.angle)))
|
|
2084
|
-
// .startAngle((d) => x(String(d.startAngle)) || 0)
|
|
2085
|
-
// @ts-expect-error
|
|
2086
|
-
.endAngle((d) => Number(x(d.data.angle)) + x.bandwidth())
|
|
2087
|
-
// .endAngle((d) => x(String(d.endAngle)) || 0 + x.bandwidth())
|
|
2088
|
-
.padAngle(0.0)
|
|
2089
|
-
.padRadius(innerRadius);
|
|
2090
|
-
const arcParent = g
|
|
2091
|
-
.append('g')
|
|
2092
|
-
.selectAll('g')
|
|
2093
|
-
// @ts-expect-error
|
|
2094
|
-
.data(stackGen(data))
|
|
2095
|
-
.enter()
|
|
2096
|
-
.append('g')
|
|
2097
|
-
.attr('fill', (d) => {
|
|
2098
|
-
return z(d.key);
|
|
2099
|
-
});
|
|
2100
|
-
// @ts-expect-error
|
|
2101
|
-
const arc = arcParent
|
|
2102
|
-
.selectAll('path')
|
|
2103
|
-
.data((d) => d)
|
|
2104
|
-
.enter()
|
|
2105
|
-
.append('path');
|
|
2106
|
-
arc
|
|
2107
|
-
// @ts-expect-error
|
|
2108
|
-
.attr('d', arcVal)
|
|
2109
|
-
.attr('transform', `rotate(${angleOffset})`)
|
|
2110
|
-
.on('mouseover', (event, data) => { this.onMouseEnterOrMove(event, data); })
|
|
2111
|
-
.on('mousemove', (event, data) => { this.onMouseEnterOrMove(event, data); })
|
|
2112
|
-
.on('mouseleave', () => { this.onMouseLeave(); });
|
|
2113
|
-
const label = g
|
|
2114
|
-
.append('g')
|
|
2115
|
-
.selectAll('g')
|
|
2116
|
-
.data(data)
|
|
2117
|
-
.enter()
|
|
2118
|
-
.append('g')
|
|
2119
|
-
.attr('text-anchor', 'middle')
|
|
2120
|
-
.attr('transform', (d) => {
|
|
2121
|
-
return `rotate(${((Number(x(d.angle)) + x.bandwidth() / 2) * 180) / Math.PI -
|
|
2122
|
-
(90 - angleOffset)})translate(${outerRadius + 30},0)`;
|
|
2123
|
-
});
|
|
2124
|
-
label
|
|
2125
|
-
.append('text')
|
|
2126
|
-
// eslint-disable-next-line no-confusing-arrow
|
|
2127
|
-
.attr('transform', (d, _i) => {
|
|
2128
|
-
var _a;
|
|
2129
|
-
// @ts-expect-error
|
|
2130
|
-
return (!Number.isNaN((((_a = x(d.angle)) !== null && _a !== void 0 ? _a : 0 + x.bandwidth() / 2 + Math.PI / 2) % (2 * Math.PI < Math.PI))))
|
|
2131
|
-
? 'rotate(90)translate(0,16)'
|
|
2132
|
-
: 'rotate(-90)translate(0,-9)';
|
|
2133
|
-
})
|
|
2134
|
-
.attr('transform', 'rotate(90)translate(0,-9)')
|
|
2135
|
-
.text((d) => d.angle)
|
|
2136
|
-
.style('font-size', 14);
|
|
2137
|
-
g.selectAll('.axis')
|
|
2138
|
-
.data(d3.range(angle.domain()[1]))
|
|
2139
|
-
.enter()
|
|
2140
|
-
.append('g')
|
|
2141
|
-
.attr('class', 'axis')
|
|
2142
|
-
.attr('transform', (d) => `rotate(${(angle(d) * 180) / Math.PI})`)
|
|
2143
|
-
.call(d3
|
|
2144
|
-
// @ts-expect-error
|
|
2145
|
-
.axisLeft()
|
|
2146
|
-
// @ts-expect-error
|
|
2147
|
-
.scale(radius.copy().range([-innerRadius, -(outerRadius + 10)])));
|
|
2148
|
-
const yAxis = g.append('g').attr('text-anchor', 'middle');
|
|
2149
|
-
const yTick = yAxis
|
|
2150
|
-
.selectAll('g')
|
|
2151
|
-
.data(y.ticks(5).slice(1))
|
|
2152
|
-
.enter()
|
|
2153
|
-
.append('g');
|
|
2154
|
-
yTick
|
|
2155
|
-
.append('circle')
|
|
2156
|
-
.attr('fill', 'none')
|
|
2157
|
-
.attr('stroke', 'gray')
|
|
2158
|
-
.attr('stroke-dasharray', '4,4')
|
|
2159
|
-
.attr('r', y);
|
|
2160
|
-
yTick
|
|
2161
|
-
.append('text')
|
|
2162
|
-
.attr('y', (d) => -y(d))
|
|
2163
|
-
.attr('dy', '-0.35em')
|
|
2164
|
-
.attr('x', () => -10)
|
|
2165
|
-
.text(y.tickFormat(5, 's'))
|
|
2166
|
-
.style('font-size', 14);
|
|
2167
|
-
const legend = g
|
|
2168
|
-
.append('g')
|
|
2169
|
-
.selectAll('g')
|
|
2170
|
-
.data(this.columns.slice(1).reverse())
|
|
2171
|
-
.enter()
|
|
2172
|
-
.append('g')
|
|
2173
|
-
.attr('transform', (d, i) => `translate(${outerRadius + 45 + legendGap / 2},${-outerRadius + 40 + (i - (this.columns.length - 1) / 2) * 20})`);
|
|
2174
|
-
legend
|
|
2175
|
-
.append('rect')
|
|
2176
|
-
.attr('width', 18)
|
|
2177
|
-
.attr('height', 18)
|
|
2178
|
-
// @ts-expect-error
|
|
2179
|
-
.attr('fill', z);
|
|
2180
|
-
legend
|
|
2181
|
-
.append('text')
|
|
2182
|
-
.attr('x', -24)
|
|
2183
|
-
.attr('y', 9)
|
|
2184
|
-
.attr('dy', '0.35em')
|
|
2185
|
-
.text((d) => d)
|
|
2186
|
-
.style('font-size', 12);
|
|
2187
|
-
g.exit().remove();
|
|
2188
|
-
});
|
|
2189
|
-
}
|
|
2190
|
-
getScrubPosition(mouseX) {
|
|
2191
|
-
return undefined;
|
|
2192
|
-
}
|
|
2193
|
-
drawScrubbing(mouseX) {
|
|
2194
|
-
}
|
|
2195
|
-
onMouseEnterOrMove(event, data) {
|
|
2196
|
-
if (this.chart.onScrub === undefined)
|
|
2197
|
-
return;
|
|
2198
|
-
const scrubPositions = {
|
|
2199
|
-
[this.id]: {
|
|
2200
|
-
xPosition: event.offsetX,
|
|
2201
|
-
xValue: data.data.angle,
|
|
2202
|
-
yPosition: event.offsetY,
|
|
2203
|
-
yValue: data[1] - data[0],
|
|
2204
|
-
properties: Object.assign({}, data)
|
|
2205
|
-
}
|
|
2206
|
-
};
|
|
2207
|
-
this.chart.onScrub(scrubPositions);
|
|
2208
|
-
}
|
|
2209
|
-
onMouseLeave() {
|
|
2210
|
-
if (this.chart.onScrubEnd === undefined)
|
|
2211
|
-
return;
|
|
2212
|
-
this.chart.onScrubEnd();
|
|
2213
|
-
}
|
|
2214
|
-
}
|
|
2215
|
-
|
|
2216
|
-
class AxiomWindroseChart extends AxiomChart {
|
|
2217
|
-
createPlot(plot) {
|
|
2218
|
-
const props = this.getPlotImplementationProps(plot);
|
|
2219
|
-
return new AxiomWindrose(props);
|
|
1959
|
+
return new AxiomRadialLine(props);
|
|
2220
1960
|
}
|
|
2221
1961
|
}
|
|
2222
1962
|
|
|
@@ -2255,7 +1995,6 @@ const axiomPlotTypes = Object.assign(Object.assign({}, plotsOb), { line: AxiomLi
|
|
|
2255
1995
|
const axiomLibrary = {
|
|
2256
1996
|
Chart: AxiomChart,
|
|
2257
1997
|
RadialChart: AxiomRadialChart,
|
|
2258
|
-
WindroseChart: AxiomWindroseChart,
|
|
2259
1998
|
plotTypes: axiomPlotTypes
|
|
2260
1999
|
};
|
|
2261
2000
|
|