minerva-design 1.13.2 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/custom-elements.json +194 -12
- package/dist/angular/fesm2022/minerva-angular.mjs +20 -0
- package/dist/angular/fesm2022/minerva-angular.mjs.map +1 -1
- package/dist/angular/types/minerva-angular.d.ts +10 -1
- package/dist/charts/canvas.d.cts +1 -0
- package/dist/charts/canvas.d.ts +1 -0
- package/dist/charts/engine/canvas.cjs +1 -1
- package/dist/charts/engine/canvas.cjs.map +1 -1
- package/dist/charts/engine/canvas.js +152 -128
- package/dist/charts/engine/canvas.js.map +1 -1
- package/dist/charts/engine/chart.cjs +1 -1
- package/dist/charts/engine/chart.cjs.map +1 -1
- package/dist/charts/engine/chart.js +475 -405
- package/dist/charts/engine/chart.js.map +1 -1
- package/dist/charts/engine/controller.cjs +2 -2
- package/dist/charts/engine/controller.cjs.map +1 -1
- package/dist/charts/engine/controller.js +263 -248
- package/dist/charts/engine/controller.js.map +1 -1
- package/dist/charts/engine/dom.cjs +1 -1
- package/dist/charts/engine/dom.cjs.map +1 -1
- package/dist/charts/engine/dom.d.cts +34 -0
- package/dist/charts/engine/dom.d.ts +34 -0
- package/dist/charts/engine/dom.js +62 -51
- package/dist/charts/engine/dom.js.map +1 -1
- package/dist/charts/engine/graph/chart.cjs +1 -1
- package/dist/charts/engine/graph/chart.cjs.map +1 -1
- package/dist/charts/engine/graph/chart.js +835 -821
- package/dist/charts/engine/graph/chart.js.map +1 -1
- package/dist/charts/engine/graph/dom.cjs +1 -1
- package/dist/charts/engine/graph/dom.cjs.map +1 -1
- package/dist/charts/engine/graph/dom.js +44 -51
- package/dist/charts/engine/graph/dom.js.map +1 -1
- package/dist/charts/engine/hover-rect.cjs +2 -0
- package/dist/charts/engine/hover-rect.cjs.map +1 -0
- package/dist/charts/engine/hover-rect.d.cts +10 -0
- package/dist/charts/engine/hover-rect.d.ts +10 -0
- package/dist/charts/engine/hover-rect.js +47 -0
- package/dist/charts/engine/hover-rect.js.map +1 -0
- package/dist/charts/engine/items.cjs +1 -1
- package/dist/charts/engine/items.cjs.map +1 -1
- package/dist/charts/engine/items.js +8 -8
- package/dist/charts/engine/model.cjs +1 -1
- package/dist/charts/engine/model.cjs.map +1 -1
- package/dist/charts/engine/model.js +191 -191
- package/dist/charts/engine/model.js.map +1 -1
- package/dist/charts/engine/orderbook/depth.cjs +1 -1
- package/dist/charts/engine/orderbook/depth.cjs.map +1 -1
- package/dist/charts/engine/orderbook/depth.js +63 -63
- package/dist/charts/engine/orderbook/heatmap.cjs +1 -1
- package/dist/charts/engine/orderbook/heatmap.cjs.map +1 -1
- package/dist/charts/engine/orderbook/heatmap.js +155 -155
- package/dist/charts/engine/orderbook/host.cjs +1 -1
- package/dist/charts/engine/orderbook/host.cjs.map +1 -1
- package/dist/charts/engine/orderbook/host.js +87 -75
- package/dist/charts/engine/orderbook/host.js.map +1 -1
- package/dist/charts/engine/orderbook/ladder.cjs +1 -1
- package/dist/charts/engine/orderbook/ladder.cjs.map +1 -1
- package/dist/charts/engine/orderbook/ladder.js +41 -41
- package/dist/charts/engine/orderbook/tape.cjs +1 -1
- package/dist/charts/engine/orderbook/tape.cjs.map +1 -1
- package/dist/charts/engine/orderbook/tape.js +31 -31
- package/dist/charts/engine/presets.cjs +2 -2
- package/dist/charts/engine/presets.cjs.map +1 -1
- package/dist/charts/engine/presets.js +15 -15
- package/dist/charts/engine/renderers/vector-gpu.cjs +24 -0
- package/dist/charts/engine/renderers/vector-gpu.cjs.map +1 -0
- package/dist/charts/engine/renderers/vector-gpu.d.cts +22 -0
- package/dist/charts/engine/renderers/vector-gpu.d.ts +22 -0
- package/dist/charts/engine/renderers/vector-gpu.js +301 -0
- package/dist/charts/engine/renderers/vector-gpu.js.map +1 -0
- package/dist/charts/engine/renderers/vector.cjs +2 -0
- package/dist/charts/engine/renderers/vector.cjs.map +1 -0
- package/dist/charts/engine/renderers/vector.d.cts +140 -0
- package/dist/charts/engine/renderers/vector.d.ts +140 -0
- package/dist/charts/engine/renderers/vector.js +568 -0
- package/dist/charts/engine/renderers/vector.js.map +1 -0
- package/dist/charts/engine/scene.cjs +1 -1
- package/dist/charts/engine/scene.cjs.map +1 -1
- package/dist/charts/engine/scene.d.cts +43 -3
- package/dist/charts/engine/scene.d.ts +43 -3
- package/dist/charts/engine/scene.js +411 -338
- package/dist/charts/engine/scene.js.map +1 -1
- package/dist/charts/engine/shapes/bullet.cjs +1 -1
- package/dist/charts/engine/shapes/bullet.cjs.map +1 -1
- package/dist/charts/engine/shapes/bullet.js +36 -36
- package/dist/charts/engine/shapes/calendar.cjs +1 -1
- package/dist/charts/engine/shapes/calendar.cjs.map +1 -1
- package/dist/charts/engine/shapes/calendar.js +53 -53
- package/dist/charts/engine/shapes/chart.cjs +1 -1
- package/dist/charts/engine/shapes/chart.cjs.map +1 -1
- package/dist/charts/engine/shapes/chart.d.cts +3 -2
- package/dist/charts/engine/shapes/chart.d.ts +3 -2
- package/dist/charts/engine/shapes/chart.js +192 -162
- package/dist/charts/engine/shapes/chart.js.map +1 -1
- package/dist/charts/engine/shapes/draw.cjs +1 -1
- package/dist/charts/engine/shapes/draw.cjs.map +1 -1
- package/dist/charts/engine/shapes/draw.js +87 -86
- package/dist/charts/engine/shapes/draw.js.map +1 -1
- package/dist/charts/engine/shapes/layout.cjs +2 -2
- package/dist/charts/engine/shapes/layout.cjs.map +1 -1
- package/dist/charts/engine/shapes/layout.d.cts +8 -7
- package/dist/charts/engine/shapes/layout.d.ts +8 -7
- package/dist/charts/engine/shapes/layout.js +262 -166
- package/dist/charts/engine/shapes/layout.js.map +1 -1
- package/dist/charts/engine/shapes/types.d.cts +13 -1
- package/dist/charts/engine/shapes/types.d.ts +13 -1
- package/dist/charts/engine/three-d/chart.cjs +1 -1
- package/dist/charts/engine/three-d/chart.cjs.map +1 -1
- package/dist/charts/engine/three-d/chart.js +266 -257
- package/dist/charts/engine/three-d/chart.js.map +1 -1
- package/dist/charts/engine/three-d/dom.cjs +1 -1
- package/dist/charts/engine/three-d/dom.cjs.map +1 -1
- package/dist/charts/engine/three-d/dom.js +28 -35
- package/dist/charts/engine/three-d/dom.js.map +1 -1
- package/dist/charts/engine/three-d/geometry.cjs +1 -1
- package/dist/charts/engine/three-d/geometry.cjs.map +1 -1
- package/dist/charts/engine/three-d/geometry.js +70 -70
- package/dist/charts/engine/three-d/overlay.cjs +1 -1
- package/dist/charts/engine/three-d/overlay.cjs.map +1 -1
- package/dist/charts/engine/three-d/overlay.js +15 -15
- package/dist/charts/engine/tooltip.cjs +2 -0
- package/dist/charts/engine/tooltip.cjs.map +1 -0
- package/dist/charts/engine/tooltip.d.cts +94 -0
- package/dist/charts/engine/tooltip.d.ts +94 -0
- package/dist/charts/engine/tooltip.js +242 -0
- package/dist/charts/engine/tooltip.js.map +1 -0
- package/dist/charts/engine/trading/chart.cjs +1 -1
- package/dist/charts/engine/trading/chart.cjs.map +1 -1
- package/dist/charts/engine/trading/chart.js +135 -135
- package/dist/charts/react/shapes.cjs +1 -1
- package/dist/charts/react/shapes.cjs.map +1 -1
- package/dist/charts/react/shapes.d.cts +10 -1
- package/dist/charts/react/shapes.d.ts +10 -1
- package/dist/charts/react/shapes.js +24 -19
- package/dist/charts/react/shapes.js.map +1 -1
- package/dist/core/contracts.cjs +1 -1
- package/dist/core/contracts.cjs.map +1 -1
- package/dist/core/contracts.js +1 -1
- package/dist/core/contracts.js.map +1 -1
- package/dist/native/charts/src/canvas.cjs +1 -1
- package/dist/native/charts/src/canvas.js +8 -7
- package/dist/native/charts/src/engine/canvas.cjs +1 -1
- package/dist/native/charts/src/engine/canvas.js +2 -0
- package/dist/native/charts/src/engine/controller.cjs +2 -2
- package/dist/native/charts/src/engine/controller.cjs.map +1 -1
- package/dist/native/charts/src/engine/controller.js +102 -87
- package/dist/native/charts/src/engine/controller.js.map +1 -1
- package/dist/native/charts/src/engine/hover-rect.cjs +2 -0
- package/dist/native/charts/src/engine/hover-rect.cjs.map +1 -0
- package/dist/native/charts/src/engine/hover-rect.js +47 -0
- package/dist/native/charts/src/engine/hover-rect.js.map +1 -0
- package/dist/native/charts/src/engine/scene.cjs +1 -1
- package/dist/native/charts/src/engine/scene.cjs.map +1 -1
- package/dist/native/charts/src/engine/scene.js +318 -245
- package/dist/native/charts/src/engine/scene.js.map +1 -1
- package/dist/native/charts/src/engine/shapes/draw.cjs +1 -1
- package/dist/native/charts/src/engine/shapes/draw.cjs.map +1 -1
- package/dist/native/charts/src/engine/shapes/draw.js +81 -80
- package/dist/native/charts/src/engine/shapes/draw.js.map +1 -1
- package/dist/native/charts/src/engine/shapes/layout.cjs +2 -2
- package/dist/native/charts/src/engine/shapes/layout.cjs.map +1 -1
- package/dist/native/charts/src/engine/shapes/layout.js +239 -143
- package/dist/native/charts/src/engine/shapes/layout.js.map +1 -1
- package/dist/native/charts/src/engine/tooltip.cjs +2 -0
- package/dist/native/charts/src/engine/tooltip.cjs.map +1 -0
- package/dist/native/charts/src/engine/tooltip.js +164 -0
- package/dist/native/charts/src/engine/tooltip.js.map +1 -0
- package/dist/native/components/Chart/scene.cjs +1 -1
- package/dist/native/components/Chart/scene.cjs.map +1 -1
- package/dist/native/components/Chart/scene.js +210 -187
- package/dist/native/components/Chart/scene.js.map +1 -1
- package/dist/native/source/components/Chart/scene.tsx +55 -2
- package/dist/taro/index.js +2026 -1626
- package/dist/uni/charts-canvas.js +1 -0
- package/dist/vue/components/Chart/BulletChart.vue.d.ts +2 -0
- package/dist/vue/components/Chart/CalendarHeatmap.vue.d.ts +2 -0
- package/dist/vue/components/Chart/ChordChart.vue.d.ts +2 -0
- package/dist/vue/components/Chart/DonutChart.vue.d.ts +2 -0
- package/dist/vue/components/Chart/FunnelChart.vue.d.ts +2 -0
- package/dist/vue/components/Chart/GaugeChart.vue.d.ts +2 -0
- package/dist/vue/components/Chart/PieChart.vue.d.ts +2 -0
- package/dist/vue/components/Chart/RadarChart.vue.d.ts +2 -0
- package/dist/vue/components/Chart/RadialBarChart.vue.d.ts +2 -0
- package/dist/vue/components/Chart/SankeyChart.vue.d.ts +2 -0
- package/dist/vue/components/Chart/SunburstChart.vue.d.ts +2 -0
- package/dist/vue/components/Chart/TreemapChart.vue.d.ts +2 -0
- package/dist/vue/components/Chart/props.d.ts +2 -0
- package/dist/vue/components/Chart/props.js +1 -0
- package/dist/vue/components/Chart/props.js.map +1 -1
- package/dist/vue/components/Chart/shape.d.ts +2 -1
- package/dist/vue/components/Chart/shape.js +2 -0
- package/dist/vue/components/Chart/shape.js.map +1 -1
- package/dist/vue/components/Chart/types.d.ts +2 -0
- package/dist/weapp/charts/canvas.js +2576 -1969
- package/dist/weapp/charts/core.js +1 -0
- package/dist/web-components/components/chart/shape-chart.d.ts +9 -7
- package/dist/web-components/components/chart/shape-chart.js +3 -0
- package/dist/web-components/components/chart/shape-chart.js.map +1 -1
- package/dist/web-components/components/chart/shapes-extended.d.ts +6 -6
- package/dist/web-components/components/chart/shapes-extended.js.map +1 -1
- package/package.json +1 -1
|
@@ -708,117 +708,6 @@ var decimateMinMax = (points, start, end, column, gap = Infinity) => {
|
|
|
708
708
|
};
|
|
709
709
|
};
|
|
710
710
|
//#endregion
|
|
711
|
-
//#region ../charts/src/engine/layout.ts
|
|
712
|
-
var createScales = (plot, view, reverseX, reverseY, horizontal = false) => {
|
|
713
|
-
const [x0, x1] = view.x;
|
|
714
|
-
const [y0, y1] = view.y;
|
|
715
|
-
const left = plot.x;
|
|
716
|
-
const right = plot.x + plot.width;
|
|
717
|
-
const top = plot.y;
|
|
718
|
-
const bottom = plot.y + plot.height;
|
|
719
|
-
if (horizontal) {
|
|
720
|
-
const xs = plot.height / (x1 - x0 || 1);
|
|
721
|
-
const ys = plot.width / (y1 - y0 || 1);
|
|
722
|
-
return {
|
|
723
|
-
plot,
|
|
724
|
-
view,
|
|
725
|
-
horizontal,
|
|
726
|
-
xToPx: reverseX ? (v) => bottom - (v - x0) * xs : (v) => top + (v - x0) * xs,
|
|
727
|
-
yToPx: reverseY ? (v) => right - (v - y0) * ys : (v) => left + (v - y0) * ys,
|
|
728
|
-
pxToX: reverseX ? (p) => x0 + (bottom - p) / xs : (p) => x0 + (p - top) / xs,
|
|
729
|
-
pxToY: reverseY ? (p) => y0 + (right - p) / ys : (p) => y0 + (p - left) / ys,
|
|
730
|
-
xPerPx: 1 / xs,
|
|
731
|
-
yPerPx: 1 / ys
|
|
732
|
-
};
|
|
733
|
-
}
|
|
734
|
-
const xs = plot.width / (x1 - x0 || 1);
|
|
735
|
-
const ys = plot.height / (y1 - y0 || 1);
|
|
736
|
-
return {
|
|
737
|
-
plot,
|
|
738
|
-
view,
|
|
739
|
-
horizontal,
|
|
740
|
-
xToPx: reverseX ? (v) => right - (v - x0) * xs : (v) => left + (v - x0) * xs,
|
|
741
|
-
yToPx: reverseY ? (v) => top + (v - y0) * ys : (v) => bottom - (v - y0) * ys,
|
|
742
|
-
pxToX: reverseX ? (p) => x0 + (right - p) / xs : (p) => x0 + (p - left) / xs,
|
|
743
|
-
pxToY: reverseY ? (p) => y0 + (p - top) / ys : (p) => y0 + (bottom - p) / ys,
|
|
744
|
-
xPerPx: 1 / xs,
|
|
745
|
-
yPerPx: 1 / ys
|
|
746
|
-
};
|
|
747
|
-
};
|
|
748
|
-
/** Screen pixel of a data point */
|
|
749
|
-
var toScreen = (s, x, y) => s.horizontal ? {
|
|
750
|
-
x: s.yToPx(y),
|
|
751
|
-
y: s.xToPx(x)
|
|
752
|
-
} : {
|
|
753
|
-
x: s.xToPx(x),
|
|
754
|
-
y: s.yToPx(y)
|
|
755
|
-
};
|
|
756
|
-
/** Data point under a screen pixel */
|
|
757
|
-
var toData = (s, px, py) => s.horizontal ? {
|
|
758
|
-
x: s.pxToX(py),
|
|
759
|
-
y: s.pxToY(px)
|
|
760
|
-
} : {
|
|
761
|
-
x: s.pxToX(px),
|
|
762
|
-
y: s.pxToY(py)
|
|
763
|
-
};
|
|
764
|
-
/** Coordinate of a screen pixel along the x axis (`xToPx` space) */
|
|
765
|
-
var alongX = (s, px, py) => s.horizontal ? py : px;
|
|
766
|
-
/** Coordinate of a screen pixel along the value axis (`yToPx` space) */
|
|
767
|
-
var alongY = (s, px, py) => s.horizontal ? px : py;
|
|
768
|
-
/**
|
|
769
|
-
* Screen rectangle between two pixels along x (`xa`, `xb`) and two along
|
|
770
|
-
* y (`ya`, `yb`)
|
|
771
|
-
*/
|
|
772
|
-
var screenRect = (s, xa, xb, ya, yb) => {
|
|
773
|
-
const a = Math.min(xa, xb);
|
|
774
|
-
const b = Math.max(xa, xb);
|
|
775
|
-
const c = Math.min(ya, yb);
|
|
776
|
-
const d = Math.max(ya, yb);
|
|
777
|
-
return s.horizontal ? {
|
|
778
|
-
x: c,
|
|
779
|
-
y: a,
|
|
780
|
-
width: d - c,
|
|
781
|
-
height: b - a
|
|
782
|
-
} : {
|
|
783
|
-
x: a,
|
|
784
|
-
y: c,
|
|
785
|
-
width: b - a,
|
|
786
|
-
height: d - c
|
|
787
|
-
};
|
|
788
|
-
};
|
|
789
|
-
/**
|
|
790
|
-
* Inset (per side) and corner radius of heatmap cells of `width` ×
|
|
791
|
-
* `height` CSS pixels: a hairline apart (1px, 2px on large cells), corners
|
|
792
|
-
* of `radius` at most 20% of the cell; tiny cells touch
|
|
793
|
-
*/
|
|
794
|
-
var heatCellStyle = (width, height, radius) => {
|
|
795
|
-
const size = Math.min(Math.abs(width), Math.abs(height));
|
|
796
|
-
if (!(size >= 4)) return {
|
|
797
|
-
inset: 0,
|
|
798
|
-
radius: 0
|
|
799
|
-
};
|
|
800
|
-
return {
|
|
801
|
-
inset: Math.round(Math.min(1, .5 + size / 96) * 4) / 4,
|
|
802
|
-
radius: Math.round(Math.max(0, Math.min(radius, size * .2)) * 4) / 4
|
|
803
|
-
};
|
|
804
|
-
};
|
|
805
|
-
/** Room for the axes, then the plot area */
|
|
806
|
-
var computePlot = (input) => {
|
|
807
|
-
const { width, height, padding, fontSize } = input;
|
|
808
|
-
const line = Math.ceil(fontSize * 1.4);
|
|
809
|
-
const left = padding.left + (input.yLabelWidth > 0 ? input.yLabelWidth + 8 : 0) + (input.yTitle ? line + 6 : 0);
|
|
810
|
-
const bottom = padding.bottom + (input.xAxisVisible ? line + 8 : 0) + (input.xTitle ? line + 4 : 0);
|
|
811
|
-
const top = padding.top;
|
|
812
|
-
const y2Width = input.y2LabelWidth ?? 0;
|
|
813
|
-
const right = y2Width > 0 || input.y2Title ? Math.max(4, padding.left) + (y2Width > 0 ? y2Width + 8 : 0) + (input.y2Title ? line + 6 : 0) : padding.right;
|
|
814
|
-
return {
|
|
815
|
-
x: Math.round(left),
|
|
816
|
-
y: Math.round(top),
|
|
817
|
-
width: Math.max(10, Math.round(width - left - right)),
|
|
818
|
-
height: Math.max(10, Math.round(height - top - bottom))
|
|
819
|
-
};
|
|
820
|
-
};
|
|
821
|
-
//#endregion
|
|
822
711
|
//#region ../charts/src/engine/locale.ts
|
|
823
712
|
/**
|
|
824
713
|
* Locale of a chart: the `locale` option, else the `lang` of the document
|
|
@@ -1087,1920 +976,2031 @@ var formatPercent = (value, locale, fixed = false) => locale ? numberFormat(loca
|
|
|
1087
976
|
maximumFractionDigits: 1
|
|
1088
977
|
}).format(value / 100) : `${fixed ? value.toFixed(1) : +value.toFixed(1)}%`;
|
|
1089
978
|
//#endregion
|
|
1090
|
-
//#region ../charts/src/engine/
|
|
1091
|
-
var
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
outliers: "Outliers",
|
|
1103
|
-
value: "Value",
|
|
1104
|
-
x: "x",
|
|
1105
|
-
y: "y",
|
|
1106
|
-
series: "Series",
|
|
1107
|
-
chart: "Chart",
|
|
1108
|
-
chartOf: "Chart: {series}",
|
|
1109
|
-
start: "Start",
|
|
1110
|
-
end: "End",
|
|
1111
|
-
duration: "Duration",
|
|
1112
|
-
progress: "Progress",
|
|
1113
|
-
dependencies: "Depends on",
|
|
1114
|
-
outlier: "Outlier"
|
|
979
|
+
//#region ../charts/src/engine/time.ts
|
|
980
|
+
var SECOND = 1e3;
|
|
981
|
+
var MINUTE = 60 * SECOND;
|
|
982
|
+
var HOUR = 60 * MINUTE;
|
|
983
|
+
var DAY = 24 * HOUR;
|
|
984
|
+
var MONTH = 30 * DAY;
|
|
985
|
+
var YEAR = 365 * DAY;
|
|
986
|
+
var INTERVALS = [
|
|
987
|
+
{
|
|
988
|
+
unit: "second",
|
|
989
|
+
step: 1,
|
|
990
|
+
ms: SECOND
|
|
1115
991
|
},
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
close: "收盘",
|
|
1121
|
-
max: "最大值",
|
|
1122
|
-
q3: "上四分位",
|
|
1123
|
-
median: "中位数",
|
|
1124
|
-
q1: "下四分位",
|
|
1125
|
-
min: "最小值",
|
|
1126
|
-
outliers: "异常值",
|
|
1127
|
-
value: "数值",
|
|
1128
|
-
series: "系列",
|
|
1129
|
-
chart: "图表",
|
|
1130
|
-
chartOf: "图表:{series}",
|
|
1131
|
-
start: "开始",
|
|
1132
|
-
end: "结束",
|
|
1133
|
-
duration: "时长",
|
|
1134
|
-
progress: "进度",
|
|
1135
|
-
dependencies: "前置任务",
|
|
1136
|
-
outlier: "异常值"
|
|
992
|
+
{
|
|
993
|
+
unit: "second",
|
|
994
|
+
step: 5,
|
|
995
|
+
ms: 5 * SECOND
|
|
1137
996
|
},
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
close: "終値",
|
|
1143
|
-
max: "最大値",
|
|
1144
|
-
q3: "第3四分位",
|
|
1145
|
-
median: "中央値",
|
|
1146
|
-
q1: "第1四分位",
|
|
1147
|
-
min: "最小値",
|
|
1148
|
-
outliers: "外れ値",
|
|
1149
|
-
value: "値",
|
|
1150
|
-
series: "系列",
|
|
1151
|
-
chart: "グラフ",
|
|
1152
|
-
chartOf: "グラフ:{series}",
|
|
1153
|
-
start: "開始",
|
|
1154
|
-
end: "終了",
|
|
1155
|
-
duration: "期間",
|
|
1156
|
-
progress: "進捗",
|
|
1157
|
-
dependencies: "先行タスク",
|
|
1158
|
-
outlier: "外れ値"
|
|
997
|
+
{
|
|
998
|
+
unit: "second",
|
|
999
|
+
step: 15,
|
|
1000
|
+
ms: 15 * SECOND
|
|
1159
1001
|
},
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
this.cellW = valid && xMax > xMin ? (xMax - xMin) / side : 1;
|
|
1240
|
-
this.cellH = valid && yMax > yMin ? (yMax - yMin) / side : 1;
|
|
1241
|
-
const cells = new Int32Array(count);
|
|
1242
|
-
const counts = new Uint32Array(side * side + 1);
|
|
1243
|
-
for (let i = 0; i < count; i++) {
|
|
1244
|
-
const xi = x[i];
|
|
1245
|
-
const yi = y[i];
|
|
1246
|
-
if (!Number.isFinite(xi) || !Number.isFinite(yi)) {
|
|
1247
|
-
cells[i] = -1;
|
|
1248
|
-
continue;
|
|
1249
|
-
}
|
|
1250
|
-
const cell = this.cellOf(xi, yi);
|
|
1251
|
-
cells[i] = cell;
|
|
1252
|
-
counts[cell + 1]++;
|
|
1253
|
-
}
|
|
1254
|
-
for (let c = 1; c < counts.length; c++) counts[c] += counts[c - 1];
|
|
1255
|
-
this.starts = counts.slice();
|
|
1256
|
-
const cursor = counts;
|
|
1257
|
-
this.order = new Uint32Array(valid);
|
|
1258
|
-
for (let i = 0; i < count; i++) {
|
|
1259
|
-
const cell = cells[i];
|
|
1260
|
-
if (cell >= 0) this.order[cursor[cell]++] = i;
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
column(xi) {
|
|
1264
|
-
const c = Math.floor((xi - this.x0) / this.cellW);
|
|
1265
|
-
return c < 0 ? 0 : c >= this.cols ? this.cols - 1 : c;
|
|
1266
|
-
}
|
|
1267
|
-
row(yi) {
|
|
1268
|
-
const r = Math.floor((yi - this.y0) / this.cellH);
|
|
1269
|
-
return r < 0 ? 0 : r >= this.rows ? this.rows - 1 : r;
|
|
1270
|
-
}
|
|
1271
|
-
cellOf(xi, yi) {
|
|
1272
|
-
return this.row(yi) * this.cols + this.column(xi);
|
|
1002
|
+
{
|
|
1003
|
+
unit: "second",
|
|
1004
|
+
step: 30,
|
|
1005
|
+
ms: 30 * SECOND
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
unit: "minute",
|
|
1009
|
+
step: 1,
|
|
1010
|
+
ms: MINUTE
|
|
1011
|
+
},
|
|
1012
|
+
{
|
|
1013
|
+
unit: "minute",
|
|
1014
|
+
step: 5,
|
|
1015
|
+
ms: 5 * MINUTE
|
|
1016
|
+
},
|
|
1017
|
+
{
|
|
1018
|
+
unit: "minute",
|
|
1019
|
+
step: 15,
|
|
1020
|
+
ms: 15 * MINUTE
|
|
1021
|
+
},
|
|
1022
|
+
{
|
|
1023
|
+
unit: "minute",
|
|
1024
|
+
step: 30,
|
|
1025
|
+
ms: 30 * MINUTE
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
unit: "hour",
|
|
1029
|
+
step: 1,
|
|
1030
|
+
ms: HOUR
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
unit: "hour",
|
|
1034
|
+
step: 2,
|
|
1035
|
+
ms: 2 * HOUR
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
unit: "hour",
|
|
1039
|
+
step: 3,
|
|
1040
|
+
ms: 3 * HOUR
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
unit: "hour",
|
|
1044
|
+
step: 6,
|
|
1045
|
+
ms: 6 * HOUR
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
unit: "hour",
|
|
1049
|
+
step: 12,
|
|
1050
|
+
ms: 12 * HOUR
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
unit: "day",
|
|
1054
|
+
step: 1,
|
|
1055
|
+
ms: DAY
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
unit: "day",
|
|
1059
|
+
step: 2,
|
|
1060
|
+
ms: 2 * DAY
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
unit: "day",
|
|
1064
|
+
step: 7,
|
|
1065
|
+
ms: 7 * DAY
|
|
1066
|
+
},
|
|
1067
|
+
{
|
|
1068
|
+
unit: "month",
|
|
1069
|
+
step: 1,
|
|
1070
|
+
ms: MONTH
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
unit: "month",
|
|
1074
|
+
step: 3,
|
|
1075
|
+
ms: 3 * MONTH
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
unit: "month",
|
|
1079
|
+
step: 6,
|
|
1080
|
+
ms: 6 * MONTH
|
|
1273
1081
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1082
|
+
];
|
|
1083
|
+
var pad = (n, width = 2) => String(n).padStart(width, "0");
|
|
1084
|
+
var partsOf = (t, utc) => {
|
|
1085
|
+
const d = new Date(t);
|
|
1086
|
+
return utc ? {
|
|
1087
|
+
year: d.getUTCFullYear(),
|
|
1088
|
+
month: d.getUTCMonth(),
|
|
1089
|
+
date: d.getUTCDate(),
|
|
1090
|
+
day: d.getUTCDay(),
|
|
1091
|
+
hours: d.getUTCHours(),
|
|
1092
|
+
minutes: d.getUTCMinutes(),
|
|
1093
|
+
seconds: d.getUTCSeconds(),
|
|
1094
|
+
ms: d.getUTCMilliseconds()
|
|
1095
|
+
} : {
|
|
1096
|
+
year: d.getFullYear(),
|
|
1097
|
+
month: d.getMonth(),
|
|
1098
|
+
date: d.getDate(),
|
|
1099
|
+
day: d.getDay(),
|
|
1100
|
+
hours: d.getHours(),
|
|
1101
|
+
minutes: d.getMinutes(),
|
|
1102
|
+
seconds: d.getSeconds(),
|
|
1103
|
+
ms: d.getMilliseconds()
|
|
1104
|
+
};
|
|
1105
|
+
};
|
|
1106
|
+
/** Timestamp of calendar fields (out of range fields carry over) */
|
|
1107
|
+
var timeOf = (utc, year, month, date = 1, hours = 0, minutes = 0, seconds = 0) => {
|
|
1108
|
+
if (utc) {
|
|
1109
|
+
const t = Date.UTC(year, month, date, hours, minutes, seconds);
|
|
1110
|
+
if (year >= 0 && year < 100) {
|
|
1111
|
+
const d = new Date(t);
|
|
1112
|
+
d.setUTCFullYear(d.getUTCFullYear() - 1900);
|
|
1113
|
+
return d.getTime();
|
|
1284
1114
|
}
|
|
1115
|
+
return t;
|
|
1285
1116
|
}
|
|
1117
|
+
const d = new Date(year, month, date, hours, minutes, seconds);
|
|
1118
|
+
if (year >= 0 && year < 100) d.setFullYear(d.getFullYear() - 1900);
|
|
1119
|
+
return d.getTime();
|
|
1286
1120
|
};
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1121
|
+
var chooseInterval = (span, count) => {
|
|
1122
|
+
const target = span / Math.max(1, count);
|
|
1123
|
+
if (target < SECOND * .75) return {
|
|
1124
|
+
unit: "millisecond",
|
|
1125
|
+
step: tickStep(0, span, count),
|
|
1126
|
+
ms: 1
|
|
1127
|
+
};
|
|
1128
|
+
for (const interval of INTERVALS) if (interval.ms >= target * .75) return interval;
|
|
1129
|
+
const years = Math.max(1, tickStep(0, span / YEAR, count));
|
|
1130
|
+
return {
|
|
1131
|
+
unit: "year",
|
|
1132
|
+
step: Math.round(years),
|
|
1133
|
+
ms: Math.round(years) * YEAR
|
|
1134
|
+
};
|
|
1294
1135
|
};
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1136
|
+
/** Start of the tick sequence: `t` floored to the unit, aligned on the step */
|
|
1137
|
+
var floorTo = (t, { unit, step }, utc) => {
|
|
1138
|
+
const p = partsOf(t, utc);
|
|
1139
|
+
switch (unit) {
|
|
1140
|
+
case "second": return timeOf(utc, p.year, p.month, p.date, p.hours, p.minutes, p.seconds - p.seconds % step);
|
|
1141
|
+
case "minute": return timeOf(utc, p.year, p.month, p.date, p.hours, p.minutes - p.minutes % step);
|
|
1142
|
+
case "hour": return timeOf(utc, p.year, p.month, p.date, p.hours - p.hours % step);
|
|
1143
|
+
case "day": return timeOf(utc, p.year, p.month, p.date - (step === 7 ? (p.day + 6) % 7 : 0));
|
|
1144
|
+
case "month": return timeOf(utc, p.year, p.month - p.month % step);
|
|
1145
|
+
case "year": return timeOf(utc, p.year - p.year % step, 0);
|
|
1146
|
+
default: return t;
|
|
1305
1147
|
}
|
|
1306
1148
|
};
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1149
|
+
var advance = (t, { unit, step }, utc) => {
|
|
1150
|
+
const { year, month, date, hours, minutes, seconds } = partsOf(t, utc);
|
|
1151
|
+
switch (unit) {
|
|
1152
|
+
case "second": return timeOf(utc, year, month, date, hours, minutes, seconds + step);
|
|
1153
|
+
case "minute": return timeOf(utc, year, month, date, hours, minutes + step, seconds);
|
|
1154
|
+
case "hour": return timeOf(utc, year, month, date, hours + step, minutes, seconds);
|
|
1155
|
+
case "day": return timeOf(utc, year, month, date + step, hours, minutes, seconds);
|
|
1156
|
+
case "month": return timeOf(utc, year, month + step, date, hours, minutes, seconds);
|
|
1157
|
+
case "year": return timeOf(utc, year + step, month, date, hours, minutes, seconds);
|
|
1158
|
+
default: return t + step;
|
|
1313
1159
|
}
|
|
1314
|
-
return "linear";
|
|
1315
1160
|
};
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
const
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
let min = Infinity;
|
|
1337
|
-
let max = -Infinity;
|
|
1338
|
-
for (let i = 0; i < values.length; i++) {
|
|
1339
|
-
const v = values[i];
|
|
1340
|
-
if (!Number.isFinite(v)) continue;
|
|
1341
|
-
if (v < min) min = v;
|
|
1342
|
-
if (v > max) max = v;
|
|
1161
|
+
/**
|
|
1162
|
+
* Calendar-aligned ticks between two timestamps
|
|
1163
|
+
* @param locale labels in this locale (ISO-like labels without it)
|
|
1164
|
+
* @param timeZone calendar of the steps and labels
|
|
1165
|
+
*/
|
|
1166
|
+
var timeTicks = (min, max, count, locale, timeZone = "local") => {
|
|
1167
|
+
if (min > max) [min, max] = [max, min];
|
|
1168
|
+
const interval = chooseInterval(max - min, count);
|
|
1169
|
+
if (interval.unit === "millisecond") return {
|
|
1170
|
+
values: linearTicks(min, max, count),
|
|
1171
|
+
unit: "millisecond",
|
|
1172
|
+
format: (v) => formatTime(v, "millisecond", locale, timeZone)
|
|
1173
|
+
};
|
|
1174
|
+
const utc = timeZone === "utc";
|
|
1175
|
+
const values = [];
|
|
1176
|
+
let t = floorTo(min, interval, utc);
|
|
1177
|
+
while (t < min) t = advance(t, interval, utc);
|
|
1178
|
+
while (t <= max && values.length < 500) {
|
|
1179
|
+
values.push(t);
|
|
1180
|
+
t = advance(t, interval, utc);
|
|
1343
1181
|
}
|
|
1344
|
-
|
|
1182
|
+
const { unit } = interval;
|
|
1183
|
+
return {
|
|
1184
|
+
values,
|
|
1185
|
+
unit,
|
|
1186
|
+
format: (v) => formatTick(v, unit, locale, timeZone)
|
|
1187
|
+
};
|
|
1345
1188
|
};
|
|
1346
|
-
|
|
1347
|
-
var
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
const d = xs[i] - xs[i - 1];
|
|
1353
|
-
if (d > 0 && d < step) step = d;
|
|
1354
|
-
}
|
|
1355
|
-
});
|
|
1356
|
-
return step;
|
|
1189
|
+
var ymd$1 = (p) => `${p.year}-${pad(p.month + 1)}-${pad(p.date)}`;
|
|
1190
|
+
var hm = (p) => `${pad(p.hours)}:${pad(p.minutes)}`;
|
|
1191
|
+
var HM = {
|
|
1192
|
+
hour: "2-digit",
|
|
1193
|
+
minute: "2-digit",
|
|
1194
|
+
hourCycle: "h23"
|
|
1357
1195
|
};
|
|
1358
|
-
var
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
return Number.isFinite(step) ? step : 1;
|
|
1196
|
+
var HMS = {
|
|
1197
|
+
...HM,
|
|
1198
|
+
second: "2-digit"
|
|
1362
1199
|
};
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
var
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1200
|
+
var MONTH_DAY = {
|
|
1201
|
+
month: "short",
|
|
1202
|
+
day: "numeric"
|
|
1203
|
+
};
|
|
1204
|
+
var DATE = {
|
|
1205
|
+
year: "numeric",
|
|
1206
|
+
month: "short",
|
|
1207
|
+
day: "numeric"
|
|
1208
|
+
};
|
|
1209
|
+
/** Cached formatter of a locale and time zone */
|
|
1210
|
+
var intl = (locale, timeZone, options) => dateFormat(locale, timeZone === "utc" ? {
|
|
1211
|
+
...options,
|
|
1212
|
+
timeZone: "UTC"
|
|
1213
|
+
} : options);
|
|
1214
|
+
/** Short tick label; day boundaries show the date, year boundaries the year */
|
|
1215
|
+
var formatTick = (value, unit, locale, timeZone) => {
|
|
1216
|
+
const p = partsOf(value, timeZone === "utc");
|
|
1217
|
+
const midnight = p.hours === 0 && p.minutes === 0 && p.seconds === 0;
|
|
1218
|
+
const newYear = p.month === 0 && p.date === 1;
|
|
1219
|
+
if (locale) {
|
|
1220
|
+
const f = (options) => intl(locale, timeZone, options).format(value);
|
|
1221
|
+
switch (unit) {
|
|
1222
|
+
case "second": return f(HMS);
|
|
1223
|
+
case "minute":
|
|
1224
|
+
case "hour": return f(midnight ? MONTH_DAY : HM);
|
|
1225
|
+
case "day": return f(newYear ? { year: "numeric" } : MONTH_DAY);
|
|
1226
|
+
case "month": return f(p.month === 0 ? { year: "numeric" } : { month: "short" });
|
|
1227
|
+
default: return f({ year: "numeric" });
|
|
1380
1228
|
}
|
|
1381
1229
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
for (let i = 0; i < count; i++) {
|
|
1391
|
-
const v = y[i];
|
|
1392
|
-
const valid = Number.isFinite(v);
|
|
1393
|
-
base[i] = valid ? 0 : NaN;
|
|
1394
|
-
top[i] = valid ? v : NaN;
|
|
1395
|
-
}
|
|
1396
|
-
return {
|
|
1397
|
-
base,
|
|
1398
|
-
top
|
|
1399
|
-
};
|
|
1400
|
-
}
|
|
1401
|
-
let byX = running.get(stack);
|
|
1402
|
-
if (!byX) running.set(stack, byX = /* @__PURE__ */ new Map());
|
|
1403
|
-
const sums = totals.get(stack);
|
|
1404
|
-
const base = new Float64Array(count);
|
|
1405
|
-
const top = new Float64Array(count);
|
|
1406
|
-
for (let i = 0; i < count; i++) {
|
|
1407
|
-
let v = y[i];
|
|
1408
|
-
if (!Number.isFinite(v)) {
|
|
1409
|
-
base[i] = NaN;
|
|
1410
|
-
top[i] = NaN;
|
|
1411
|
-
continue;
|
|
1412
|
-
}
|
|
1413
|
-
if (sums) {
|
|
1414
|
-
const t = sums.get(x[i]);
|
|
1415
|
-
const total = t ? v >= 0 ? t[0] : t[1] : 0;
|
|
1416
|
-
v = total > 0 ? v / total * 100 : 0;
|
|
1417
|
-
}
|
|
1418
|
-
const t = byX.get(x[i]) ?? [0, 0];
|
|
1419
|
-
const b = v >= 0 ? t[0] : t[1];
|
|
1420
|
-
base[i] = b;
|
|
1421
|
-
top[i] = b + v;
|
|
1422
|
-
if (v >= 0) t[0] += v;
|
|
1423
|
-
else t[1] += v;
|
|
1424
|
-
byX.set(x[i], t);
|
|
1425
|
-
}
|
|
1426
|
-
return {
|
|
1427
|
-
base,
|
|
1428
|
-
top
|
|
1429
|
-
};
|
|
1430
|
-
});
|
|
1230
|
+
switch (unit) {
|
|
1231
|
+
case "second": return `${hm(p)}:${pad(p.seconds)}`;
|
|
1232
|
+
case "minute":
|
|
1233
|
+
case "hour": return midnight ? `${pad(p.month + 1)}-${pad(p.date)}` : hm(p);
|
|
1234
|
+
case "day": return newYear ? String(p.year) : `${pad(p.month + 1)}-${pad(p.date)}`;
|
|
1235
|
+
case "month": return p.month === 0 ? String(p.year) : `${p.year}-${pad(p.month + 1)}`;
|
|
1236
|
+
default: return String(p.year);
|
|
1237
|
+
}
|
|
1431
1238
|
};
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1239
|
+
/**
|
|
1240
|
+
* Full label (tooltips): precision follows the unit of the ticks; dates
|
|
1241
|
+
* show the weekday with a `locale`
|
|
1242
|
+
*/
|
|
1243
|
+
var formatTime = (value, unit = "minute", locale, timeZone = "local") => {
|
|
1244
|
+
if (!Number.isFinite(value) || Number.isNaN(new Date(value).getTime())) return "–";
|
|
1245
|
+
const p = partsOf(value, timeZone === "utc");
|
|
1246
|
+
if (locale) {
|
|
1247
|
+
const f = (options) => intl(locale, timeZone, options).format(value);
|
|
1248
|
+
switch (unit) {
|
|
1249
|
+
case "millisecond": return `${f({
|
|
1250
|
+
...DATE,
|
|
1251
|
+
...HMS
|
|
1252
|
+
})}.${pad(p.ms, 3)}`;
|
|
1253
|
+
case "second": return f({
|
|
1254
|
+
...DATE,
|
|
1255
|
+
...HMS
|
|
1256
|
+
});
|
|
1257
|
+
case "minute":
|
|
1258
|
+
case "hour": return f({
|
|
1259
|
+
...DATE,
|
|
1260
|
+
...HM
|
|
1261
|
+
});
|
|
1262
|
+
default: return f({
|
|
1263
|
+
...DATE,
|
|
1264
|
+
weekday: "short"
|
|
1265
|
+
});
|
|
1445
1266
|
}
|
|
1446
1267
|
}
|
|
1447
|
-
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
return (f < 1.5 ? 2 : f < 3.5 ? 5 : 10) * power;
|
|
1268
|
+
switch (unit) {
|
|
1269
|
+
case "millisecond": return `${ymd$1(p)} ${hm(p)}:${pad(p.seconds)}.${pad(p.ms, 3)}`;
|
|
1270
|
+
case "second": return `${ymd$1(p)} ${hm(p)}:${pad(p.seconds)}`;
|
|
1271
|
+
case "minute":
|
|
1272
|
+
case "hour": return `${ymd$1(p)} ${hm(p)}`;
|
|
1273
|
+
default: return ymd$1(p);
|
|
1274
|
+
}
|
|
1455
1275
|
};
|
|
1276
|
+
//#endregion
|
|
1277
|
+
//#region ../charts/src/engine/axis.ts
|
|
1456
1278
|
/**
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
1279
|
+
* @param count approximate number of ticks (numeric / time axes) or the
|
|
1280
|
+
* maximum number of labels (category axes)
|
|
1459
1281
|
*/
|
|
1460
|
-
var
|
|
1461
|
-
const [
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
const
|
|
1469
|
-
|
|
1470
|
-
|
|
1282
|
+
var axisTicks = (axis, range, count) => {
|
|
1283
|
+
const [min, max] = range[0] <= range[1] ? range : [range[1], range[0]];
|
|
1284
|
+
if (axis.type === "category") {
|
|
1285
|
+
const n = axis.categories.length;
|
|
1286
|
+
const first = Math.max(0, Math.ceil(min - 1e-9));
|
|
1287
|
+
const last = Math.min(n - 1, Math.floor(max + 1e-9));
|
|
1288
|
+
const visible = Math.max(0, last - first + 1);
|
|
1289
|
+
const step = Math.max(1, Math.ceil(visible / Math.max(1, count)));
|
|
1290
|
+
const values = [];
|
|
1291
|
+
for (let i = first - first % step; i <= last; i += step) if (i >= first) values.push(i);
|
|
1292
|
+
const name = (v) => {
|
|
1293
|
+
const i = Math.round(v);
|
|
1294
|
+
return axis.format ? axis.format(i) : axis.categories[i] ?? String(i);
|
|
1295
|
+
};
|
|
1296
|
+
return {
|
|
1297
|
+
values,
|
|
1298
|
+
labels: values.map(name),
|
|
1299
|
+
formatValue: name
|
|
1300
|
+
};
|
|
1471
1301
|
}
|
|
1472
|
-
|
|
1473
|
-
};
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
const column = (a) => a ? a.map(f) : null;
|
|
1482
|
-
const raw = src;
|
|
1483
|
-
const cols = {
|
|
1484
|
-
...raw,
|
|
1485
|
-
y: raw.y.map(f),
|
|
1486
|
-
open: column(raw.open),
|
|
1487
|
-
high: column(raw.high),
|
|
1488
|
-
low: column(raw.low),
|
|
1489
|
-
y0: column(raw.y0),
|
|
1490
|
-
q1: column(raw.q1),
|
|
1491
|
-
q3: column(raw.q3),
|
|
1492
|
-
outliers: raw.outliers ? raw.outliers.map((list) => list?.map(f)) : null
|
|
1493
|
-
};
|
|
1494
|
-
if (map.log) {
|
|
1495
|
-
const range = [Infinity, -Infinity];
|
|
1496
|
-
const add = (a) => {
|
|
1497
|
-
if (!a) return;
|
|
1498
|
-
for (let i = 0; i < a.length; i++) {
|
|
1499
|
-
const v = a[i];
|
|
1500
|
-
if (!Number.isFinite(v)) continue;
|
|
1501
|
-
if (v < range[0]) range[0] = v;
|
|
1502
|
-
if (v > range[1]) range[1] = v;
|
|
1503
|
-
}
|
|
1302
|
+
if (axis.type === "time") {
|
|
1303
|
+
const { locale, timeZone = "local" } = axis;
|
|
1304
|
+
const ticks = timeTicks(min, max, count, locale, timeZone);
|
|
1305
|
+
const unit = ticks.unit === "day" || ticks.unit === "month" || ticks.unit === "year" ? "day" : ticks.unit;
|
|
1306
|
+
const full = axis.format ?? ((v) => formatTime(v, unit, locale, timeZone));
|
|
1307
|
+
return {
|
|
1308
|
+
values: ticks.values,
|
|
1309
|
+
labels: ticks.values.map(axis.format ?? ticks.format),
|
|
1310
|
+
formatValue: full
|
|
1504
1311
|
};
|
|
1505
|
-
for (const c of [
|
|
1506
|
-
cols.y,
|
|
1507
|
-
cols.y0,
|
|
1508
|
-
cols.high,
|
|
1509
|
-
cols.low,
|
|
1510
|
-
cols.q1,
|
|
1511
|
-
cols.q3
|
|
1512
|
-
]) add(c);
|
|
1513
|
-
cols.outliers?.forEach((list) => add(list));
|
|
1514
|
-
cols.yMin = range[0] <= range[1] ? range[0] : NaN;
|
|
1515
|
-
cols.yMax = range[0] <= range[1] ? range[1] : NaN;
|
|
1516
|
-
} else {
|
|
1517
|
-
const lo = f(raw.yMin);
|
|
1518
|
-
const hi = f(raw.yMax);
|
|
1519
|
-
cols.yMin = Math.min(lo, hi);
|
|
1520
|
-
cols.yMax = Math.max(lo, hi);
|
|
1521
1312
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
return
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
const hi = Math.ceil(range[1] - 1e-9);
|
|
1532
|
-
return [lo, hi > lo ? hi : lo + 1];
|
|
1313
|
+
const values = linearTicks(min, max, count);
|
|
1314
|
+
const step = tickStep(min, max, count);
|
|
1315
|
+
if (axis.type === "log") return logTicks(axis, min, max, count);
|
|
1316
|
+
const tick = axis.format ?? tickFormatter(step, Math.max(Math.abs(min), Math.abs(max)), axis.locale);
|
|
1317
|
+
return {
|
|
1318
|
+
values,
|
|
1319
|
+
labels: values.map(tick),
|
|
1320
|
+
formatValue: axis.format ?? ((value) => formatValue(value, axis.locale))
|
|
1321
|
+
};
|
|
1533
1322
|
};
|
|
1534
|
-
/** Exponent of `m × 10^k`, exact for the values of the ticks */
|
|
1535
|
-
var niceExponent = (m, k) => Math.log10(Number((m * 10 ** k).toPrecision(12)));
|
|
1536
1323
|
/**
|
|
1537
|
-
*
|
|
1538
|
-
*
|
|
1324
|
+
* Ticks of a log axis (`min` / `max` are exponents): every decade, with 2
|
|
1325
|
+
* and 5 between them on short ranges, every few decades on long ones;
|
|
1326
|
+
* labelled with the values (`1k`, `10k`)
|
|
1539
1327
|
*/
|
|
1540
|
-
var
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1328
|
+
var logTicks = (axis, min, max, count) => {
|
|
1329
|
+
const span = max - min;
|
|
1330
|
+
const values = [];
|
|
1331
|
+
const raw = [];
|
|
1332
|
+
const push = (v) => {
|
|
1333
|
+
const e = Math.log10(v);
|
|
1334
|
+
if (e >= min - 1e-9 && e <= max + 1e-9) {
|
|
1335
|
+
values.push(e);
|
|
1336
|
+
raw.push(v);
|
|
1547
1337
|
}
|
|
1548
|
-
return best;
|
|
1549
1338
|
};
|
|
1550
|
-
|
|
1551
|
-
const
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
const
|
|
1558
|
-
|
|
1339
|
+
if (span < 1) {
|
|
1340
|
+
for (const v of linearTicks(10 ** min, 10 ** max, count)) if (v > 0) push(v);
|
|
1341
|
+
} else {
|
|
1342
|
+
const every = Math.max(1, Math.ceil(span / Math.max(1, count)));
|
|
1343
|
+
const between = span * 3 <= count * 1.6 ? [2, 5] : [];
|
|
1344
|
+
for (let e = Math.floor(min); e <= Math.ceil(max); e++) {
|
|
1345
|
+
if (e % every !== 0) continue;
|
|
1346
|
+
const decade = Number((10 ** e).toPrecision(12));
|
|
1347
|
+
push(decade);
|
|
1348
|
+
for (const m of between) push(Number((m * decade).toPrecision(12)));
|
|
1559
1349
|
}
|
|
1560
|
-
|
|
1350
|
+
}
|
|
1351
|
+
const label = axis.format ?? ((v) => formatCompact(v, 2, axis.locale));
|
|
1352
|
+
return {
|
|
1353
|
+
values,
|
|
1354
|
+
labels: raw.map(label),
|
|
1355
|
+
formatValue: axis.format ?? ((value) => formatValue(value, axis.locale))
|
|
1561
1356
|
};
|
|
1562
|
-
const lo = below(range[0]);
|
|
1563
|
-
const hi = above(range[1]);
|
|
1564
|
-
return [lo, hi > lo + 1e-9 ? hi : above(lo + 1e-6)];
|
|
1565
1357
|
};
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
const
|
|
1572
|
-
const
|
|
1573
|
-
const
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
var fromPlotX = (model, value) => model.x.type === "log" ? 10 ** value : value;
|
|
1590
|
-
/** A view in plotted units -> the values of the axes (zoom events, `getView`) */
|
|
1591
|
-
var viewToValues = (model, view) => ({
|
|
1592
|
-
x: [fromPlotX(model, view.x[0]), fromPlotX(model, view.x[1])],
|
|
1593
|
-
y: [fromPlotY(model, view.y[0]), fromPlotY(model, view.y[1])]
|
|
1594
|
-
});
|
|
1595
|
-
/** Values of the axes -> a view in plotted units (`setView`); missing axes keep `current` */
|
|
1596
|
-
var viewFromValues = (model, next, current) => ({
|
|
1597
|
-
x: next.x ? [toPlotX(model, next.x[0]), toPlotX(model, next.x[1])] : current.x,
|
|
1598
|
-
y: next.y ? [toPlotY(model, next.y[0]), toPlotY(model, next.y[1])] : current.y
|
|
1599
|
-
});
|
|
1600
|
-
var logXCache = /* @__PURE__ */ new WeakMap();
|
|
1601
|
-
/**
|
|
1602
|
-
* Columns plotted on a log x axis: the logarithms of the x values; points
|
|
1603
|
-
* without one (x <= 0) become gaps (cached with the data)
|
|
1604
|
-
*/
|
|
1605
|
-
var logXColumns = (raw) => {
|
|
1606
|
-
const hit = logXCache.get(raw);
|
|
1607
|
-
if (hit) return hit;
|
|
1608
|
-
const x = raw.x.map((v) => v > 0 ? Math.log10(v) : NaN);
|
|
1609
|
-
let y = raw.y;
|
|
1610
|
-
let gaps = false;
|
|
1611
|
-
for (let i = 0; i < raw.count; i++) {
|
|
1612
|
-
if (Number.isFinite(x[i])) continue;
|
|
1613
|
-
if (!gaps) y = Float64Array.from(raw.y);
|
|
1614
|
-
gaps = true;
|
|
1615
|
-
y[i] = NaN;
|
|
1358
|
+
//#endregion
|
|
1359
|
+
//#region ../charts/src/engine/layout.ts
|
|
1360
|
+
var createScales = (plot, view, reverseX, reverseY, horizontal = false) => {
|
|
1361
|
+
const [x0, x1] = view.x;
|
|
1362
|
+
const [y0, y1] = view.y;
|
|
1363
|
+
const left = plot.x;
|
|
1364
|
+
const right = plot.x + plot.width;
|
|
1365
|
+
const top = plot.y;
|
|
1366
|
+
const bottom = plot.y + plot.height;
|
|
1367
|
+
if (horizontal) {
|
|
1368
|
+
const xs = plot.height / (x1 - x0 || 1);
|
|
1369
|
+
const ys = plot.width / (y1 - y0 || 1);
|
|
1370
|
+
return {
|
|
1371
|
+
plot,
|
|
1372
|
+
view,
|
|
1373
|
+
horizontal,
|
|
1374
|
+
xToPx: reverseX ? (v) => bottom - (v - x0) * xs : (v) => top + (v - x0) * xs,
|
|
1375
|
+
yToPx: reverseY ? (v) => right - (v - y0) * ys : (v) => left + (v - y0) * ys,
|
|
1376
|
+
pxToX: reverseX ? (p) => x0 + (bottom - p) / xs : (p) => x0 + (p - top) / xs,
|
|
1377
|
+
pxToY: reverseY ? (p) => y0 + (right - p) / ys : (p) => y0 + (p - left) / ys,
|
|
1378
|
+
xPerPx: 1 / xs,
|
|
1379
|
+
yPerPx: 1 / ys
|
|
1380
|
+
};
|
|
1616
1381
|
}
|
|
1617
|
-
const
|
|
1618
|
-
const
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1382
|
+
const xs = plot.width / (x1 - x0 || 1);
|
|
1383
|
+
const ys = plot.height / (y1 - y0 || 1);
|
|
1384
|
+
return {
|
|
1385
|
+
plot,
|
|
1386
|
+
view,
|
|
1387
|
+
horizontal,
|
|
1388
|
+
xToPx: reverseX ? (v) => right - (v - x0) * xs : (v) => left + (v - x0) * xs,
|
|
1389
|
+
yToPx: reverseY ? (v) => top + (v - y0) * ys : (v) => bottom - (v - y0) * ys,
|
|
1390
|
+
pxToX: reverseX ? (p) => x0 + (right - p) / xs : (p) => x0 + (p - left) / xs,
|
|
1391
|
+
pxToY: reverseY ? (p) => y0 + (p - top) / ys : (p) => y0 + (bottom - p) / ys,
|
|
1392
|
+
xPerPx: 1 / xs,
|
|
1393
|
+
yPerPx: 1 / ys
|
|
1625
1394
|
};
|
|
1626
|
-
logXCache.set(raw, cols);
|
|
1627
|
-
return cols;
|
|
1628
1395
|
};
|
|
1629
|
-
/**
|
|
1630
|
-
var
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1396
|
+
/** Screen pixel of a data point */
|
|
1397
|
+
var toScreen = (s, x, y) => s.horizontal ? {
|
|
1398
|
+
x: s.yToPx(y),
|
|
1399
|
+
y: s.xToPx(x)
|
|
1400
|
+
} : {
|
|
1401
|
+
x: s.xToPx(x),
|
|
1402
|
+
y: s.yToPx(y)
|
|
1635
1403
|
};
|
|
1636
|
-
/**
|
|
1637
|
-
var
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
return t < 0 ? 0 : t > 1 ? 1 : t;
|
|
1404
|
+
/** Data point under a screen pixel */
|
|
1405
|
+
var toData = (s, px, py) => s.horizontal ? {
|
|
1406
|
+
x: s.pxToX(py),
|
|
1407
|
+
y: s.pxToY(px)
|
|
1408
|
+
} : {
|
|
1409
|
+
x: s.pxToX(px),
|
|
1410
|
+
y: s.pxToY(py)
|
|
1644
1411
|
};
|
|
1412
|
+
/** Coordinate of a screen pixel along the x axis (`xToPx` space) */
|
|
1413
|
+
var alongX = (s, px, py) => s.horizontal ? py : px;
|
|
1414
|
+
/** Coordinate of a screen pixel along the value axis (`yToPx` space) */
|
|
1415
|
+
var alongY = (s, px, py) => s.horizontal ? px : py;
|
|
1645
1416
|
/**
|
|
1646
|
-
*
|
|
1647
|
-
*
|
|
1648
|
-
* tint of the surface to a bright saturated hue; diverging ramps meet in a
|
|
1649
|
-
* neutral middle
|
|
1417
|
+
* Screen rectangle between two pixels along x (`xa`, `xb`) and two along
|
|
1418
|
+
* y (`ya`, `yb`)
|
|
1650
1419
|
*/
|
|
1651
|
-
var
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
side(color),
|
|
1662
|
-
end(color)
|
|
1663
|
-
];
|
|
1664
|
-
}
|
|
1665
|
-
return dark ? [
|
|
1666
|
-
mixOklab(theme.surface, color, .14),
|
|
1667
|
-
withLightness(color, .46, .95),
|
|
1668
|
-
withLightness(color, .64, 1.2),
|
|
1669
|
-
withLightness(color, .8, 1.15)
|
|
1670
|
-
] : [
|
|
1671
|
-
mixOklab(theme.surface, color, .06),
|
|
1672
|
-
withLightness(color, .8, .6),
|
|
1673
|
-
withLightness(color, .58, 1),
|
|
1674
|
-
withLightness(color, .36, .9)
|
|
1675
|
-
];
|
|
1676
|
-
};
|
|
1677
|
-
var buildModel = (options, theme, hidden, cache, resolver = null) => {
|
|
1678
|
-
const resolve = resolver ?? theme.resolve ?? ((value, fallback) => resolveStaticColor(value, {}, fallback));
|
|
1679
|
-
const input = options.series;
|
|
1680
|
-
const locale = resolveLocale(options.locale);
|
|
1681
|
-
const tooltipOptions = typeof options.tooltip === "object" ? options.tooltip : void 0;
|
|
1682
|
-
const labels = chartLabels(locale, {
|
|
1683
|
-
...definedEntries(tooltipOptions?.labels),
|
|
1684
|
-
...definedEntries(options.labels)
|
|
1685
|
-
});
|
|
1686
|
-
const datas = input.map((s) => s.data);
|
|
1687
|
-
const xType = options.xAxis?.type ?? detectXType(datas);
|
|
1688
|
-
const categories = xType === "category" ? [...options.xAxis?.categories ?? collectCategories(datas)] : [];
|
|
1689
|
-
const categoryMap = new Map(categories.map((c, i) => [c, i]));
|
|
1690
|
-
const resolveX = xResolver(xType, (name) => categoryMap.get(name) ?? NaN);
|
|
1691
|
-
const yOpts = options.yAxis;
|
|
1692
|
-
const heatmapData = input.filter((s) => s.type === "heatmap").map((s) => s.data);
|
|
1693
|
-
const yCategories = yOpts?.type === "category" || !yOpts?.type && heatmapData.length ? [...yOpts?.categories ?? collectYCategories(heatmapData)] : [];
|
|
1694
|
-
const yType = yOpts?.type === "category" || yCategories.length ? "category" : yOpts?.type === "time" || !yOpts?.type && hasDateY(datas) ? "time" : yOpts?.type === "log" ? "log" : "linear";
|
|
1695
|
-
const yLog = yType === "log";
|
|
1696
|
-
const yCategoryMap = new Map(yCategories.map((c, i) => [c, i]));
|
|
1697
|
-
const resolveY = (v) => typeof v === "number" ? v : typeof v === "string" ? yType === "category" ? yCategoryMap.get(v) ?? NaN : yType === "time" ? Date.parse(v) : Number(v) : v instanceof Date ? v.getTime() : NaN;
|
|
1698
|
-
const resolverKey = `${xType}|${categories.join("")}|${yType}|${yCategories.join("")}`;
|
|
1699
|
-
const percent = options.stackMode === "percent";
|
|
1700
|
-
const horizontal = options.orientation === "horizontal";
|
|
1701
|
-
const usedKeys = /* @__PURE__ */ new Set();
|
|
1702
|
-
const series = input.map((s, index) => {
|
|
1703
|
-
let key = s.id ?? `#${index}`;
|
|
1704
|
-
while (usedKeys.has(key)) key += "'";
|
|
1705
|
-
usedKeys.add(key);
|
|
1706
|
-
let entry = cache.get(key);
|
|
1707
|
-
if (!entry || entry.data !== s.data || entry.resolverKey !== resolverKey) {
|
|
1708
|
-
entry = {
|
|
1709
|
-
data: s.data,
|
|
1710
|
-
resolverKey,
|
|
1711
|
-
cols: normalizeData(s.data, resolveX, resolveY)
|
|
1712
|
-
};
|
|
1713
|
-
cache.set(key, entry);
|
|
1714
|
-
}
|
|
1715
|
-
const type = s.type ?? "line";
|
|
1716
|
-
const paletteColor = theme.palette[index % theme.palette.length];
|
|
1717
|
-
const color = s.color ? resolve(s.color, paletteColor) : paletteColor;
|
|
1718
|
-
const cols = entry.cols;
|
|
1719
|
-
let pointColors = null;
|
|
1720
|
-
if ((type === "scatter" || type === "bar" || type === "boxplot") && cols.colors) {
|
|
1721
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
1722
|
-
pointColors = cols.colors.map((c) => {
|
|
1723
|
-
if (c === void 0) return void 0;
|
|
1724
|
-
let rgba = resolved.get(c);
|
|
1725
|
-
if (!rgba) resolved.set(c, rgba = resolve(c, color));
|
|
1726
|
-
return rgba;
|
|
1727
|
-
});
|
|
1728
|
-
}
|
|
1729
|
-
let colorScale = [];
|
|
1730
|
-
let valueRange = [0, 1];
|
|
1731
|
-
let cellColors = null;
|
|
1732
|
-
let colorMidpoint = null;
|
|
1733
|
-
let colorLut = null;
|
|
1734
|
-
if (type === "heatmap") {
|
|
1735
|
-
const scale = s.colorScale;
|
|
1736
|
-
const scheme = scale === "diverging" ? "diverging" : "sequential";
|
|
1737
|
-
colorScale = typeof scale === "object" && scale.length ? scale.map((c) => resolve(c, color)) : heatmapStops(scheme, color, theme, resolve(DEFAULT_DOWN, [
|
|
1738
|
-
.86,
|
|
1739
|
-
.15,
|
|
1740
|
-
.15,
|
|
1741
|
-
1
|
|
1742
|
-
]));
|
|
1743
|
-
valueRange = s.valueRange ? [s.valueRange[0], s.valueRange[1]] : [cols.valueMin, cols.valueMax];
|
|
1744
|
-
if (!Number.isFinite(valueRange[0])) valueRange = [0, 1];
|
|
1745
|
-
const [lo, hi] = valueRange;
|
|
1746
|
-
colorMidpoint = s.colorMidpoint ?? (scheme === "diverging" && typeof scale !== "object" ? lo < 0 && hi > 0 ? 0 : (lo + hi) / 2 : null);
|
|
1747
|
-
const lut = oklabRamp(colorScale);
|
|
1748
|
-
colorLut = lut;
|
|
1749
|
-
const values = cols.value ?? cols.y;
|
|
1750
|
-
const ramp = {
|
|
1751
|
-
valueRange,
|
|
1752
|
-
colorMidpoint
|
|
1753
|
-
};
|
|
1754
|
-
cellColors = new Uint32Array(cols.count);
|
|
1755
|
-
for (let i = 0; i < cols.count; i++) {
|
|
1756
|
-
const v = values[i];
|
|
1757
|
-
if (!Number.isFinite(v) || !Number.isFinite(cols.y[i])) continue;
|
|
1758
|
-
cellColors[i] = lut[Math.round(heatPosition(ramp, v) * 255)];
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
const connector = s.connector ? typeof s.connector === "object" ? s.connector : {} : null;
|
|
1762
|
-
const id = s.id ?? String(index);
|
|
1763
|
-
return {
|
|
1764
|
-
index,
|
|
1765
|
-
key,
|
|
1766
|
-
id,
|
|
1767
|
-
name: s.name ?? `${labels.series} ${index + 1}`,
|
|
1768
|
-
type,
|
|
1769
|
-
color,
|
|
1770
|
-
colorCss: toCss(color),
|
|
1771
|
-
lineWidth: s.lineWidth ?? (type === "candlestick" ? 1 : type === "boxplot" ? 1.5 : 2),
|
|
1772
|
-
pointSize: s.pointSize ?? (type === "boxplot" ? 6 : 8),
|
|
1773
|
-
opacity: s.opacity ?? (type === "area" ? .28 : type === "scatter" ? .78 : type === "boxplot" ? .22 : 1),
|
|
1774
|
-
hidden: !!s.hidden || hidden.has(id),
|
|
1775
|
-
showLabels: !!s.showLabels,
|
|
1776
|
-
labelsInside: s.showLabels === "inside" && type === "bar",
|
|
1777
|
-
curve: s.curve ?? "linear",
|
|
1778
|
-
cols,
|
|
1779
|
-
raw: cols,
|
|
1780
|
-
yAxis: s.yAxis === "right" && !horizontal ? "right" : "left",
|
|
1781
|
-
floating: type === "bar" && !!cols.y0,
|
|
1782
|
-
barOffset: 0,
|
|
1783
|
-
barHalfWidth: 0,
|
|
1784
|
-
barBase: null,
|
|
1785
|
-
barTop: null,
|
|
1786
|
-
stackBase: null,
|
|
1787
|
-
stackTop: null,
|
|
1788
|
-
pointColors,
|
|
1789
|
-
upColor: type === "candlestick" ? resolve(s.upColor ?? DEFAULT_UP, [
|
|
1790
|
-
.09,
|
|
1791
|
-
.64,
|
|
1792
|
-
.29,
|
|
1793
|
-
1
|
|
1794
|
-
]) : color,
|
|
1795
|
-
downColor: type === "candlestick" ? resolve(s.downColor ?? DEFAULT_DOWN, [
|
|
1796
|
-
.86,
|
|
1797
|
-
.15,
|
|
1798
|
-
.15,
|
|
1799
|
-
1
|
|
1800
|
-
]) : color,
|
|
1801
|
-
cellColors,
|
|
1802
|
-
cellHalf: {
|
|
1803
|
-
x: .5,
|
|
1804
|
-
y: .5
|
|
1805
|
-
},
|
|
1806
|
-
colorScale,
|
|
1807
|
-
valueRange,
|
|
1808
|
-
colorMidpoint,
|
|
1809
|
-
colorLut,
|
|
1810
|
-
tooltip: s.tooltip !== false,
|
|
1811
|
-
stroke: s.stroke !== void 0 && Number.isFinite(s.stroke) ? Math.max(0, s.stroke) : null,
|
|
1812
|
-
outlierShape: s.outlierShape ?? "hollow",
|
|
1813
|
-
outlierSize: s.outlierSize ?? s.pointSize ?? 7,
|
|
1814
|
-
connector: connector && type === "bar" ? {
|
|
1815
|
-
color: connector.color ? resolve(connector.color, theme.textMuted) : theme.textMuted,
|
|
1816
|
-
dash: [...connector.dash ?? [3, 3]],
|
|
1817
|
-
width: connector.width ?? 1
|
|
1818
|
-
} : null,
|
|
1819
|
-
radius: s.radius ?? (type === "heatmap" ? 2 : type === "boxplot" ? 0 : 3),
|
|
1820
|
-
format: s.format,
|
|
1821
|
-
valueName: s.valueName
|
|
1822
|
-
};
|
|
1823
|
-
});
|
|
1824
|
-
const visible = series.filter((s) => !s.hidden);
|
|
1825
|
-
const xLog = xType === "log";
|
|
1826
|
-
if (xLog) for (const s of series) s.cols = logXColumns(s.raw);
|
|
1827
|
-
const allScatter = series.length > 0 && series.every((s) => s.type === "scatter" || s.type === "heatmap");
|
|
1828
|
-
const slotted = visible.filter((s) => s.type === "bar" || s.type === "boxplot");
|
|
1829
|
-
const bars = visible.filter((s) => s.type === "bar");
|
|
1830
|
-
const band = bandOf(slotted, "x", xType === "category");
|
|
1831
|
-
if (slotted.length) {
|
|
1832
|
-
const slots = /* @__PURE__ */ new Map();
|
|
1833
|
-
const stackId = (s) => input[s.index].stack ?? `\u0000${s.key}`;
|
|
1834
|
-
for (const s of slotted) if (!slots.has(stackId(s))) slots.set(stackId(s), slots.size);
|
|
1835
|
-
let share = 0;
|
|
1836
|
-
for (const s of slotted) {
|
|
1837
|
-
const w = input[s.index].barWidth;
|
|
1838
|
-
if (w !== void 0 && w > share) share = Math.min(1, w);
|
|
1839
|
-
}
|
|
1840
|
-
const group = band * (share || (slots.size > 1 ? .78 : .62));
|
|
1841
|
-
const slotWidth = group / slots.size;
|
|
1842
|
-
for (const s of slotted) {
|
|
1843
|
-
const slot = slots.get(stackId(s)) ?? 0;
|
|
1844
|
-
s.barOffset = -group / 2 + slotWidth * (slot + .5);
|
|
1845
|
-
s.barHalfWidth = slotWidth / 2 * (slots.size > 1 ? .86 : 1);
|
|
1846
|
-
}
|
|
1847
|
-
const stackedBars = bars.filter((s) => !s.floating);
|
|
1848
|
-
const stacked = stackValues(stackedBars.map((s) => ({
|
|
1849
|
-
s,
|
|
1850
|
-
stack: stackId(s)
|
|
1851
|
-
})), false);
|
|
1852
|
-
const percentStacked = percent ? stackValues(stackedBars.filter((s) => input[s.index].stack !== void 0).map((s) => ({
|
|
1853
|
-
s,
|
|
1854
|
-
stack: stackId(s)
|
|
1855
|
-
})), true) : [];
|
|
1856
|
-
let p = 0;
|
|
1857
|
-
stackedBars.forEach((s, k) => {
|
|
1858
|
-
const values = percent && input[s.index].stack !== void 0 ? percentStacked[p++] : stacked[k];
|
|
1859
|
-
s.barBase = values.base;
|
|
1860
|
-
s.barTop = values.top;
|
|
1861
|
-
});
|
|
1862
|
-
for (const s of bars) {
|
|
1863
|
-
const { y, y0, count } = s.cols;
|
|
1864
|
-
if (!s.floating || !y0) continue;
|
|
1865
|
-
const base = new Float64Array(count);
|
|
1866
|
-
const top = new Float64Array(count);
|
|
1867
|
-
for (let i = 0; i < count; i++) {
|
|
1868
|
-
const valid = Number.isFinite(y[i]);
|
|
1869
|
-
base[i] = valid ? Number.isFinite(y0[i]) ? y0[i] : 0 : NaN;
|
|
1870
|
-
top[i] = valid ? y[i] : NaN;
|
|
1871
|
-
}
|
|
1872
|
-
s.barBase = base;
|
|
1873
|
-
s.barTop = top;
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
for (const s of visible) if (s.type === "area" && s.cols.y0 && input[s.index].stack === void 0) s.stackBase = s.cols.y0;
|
|
1877
|
-
const stackedLines = visible.filter((s) => (s.type === "line" || s.type === "area") && input[s.index].stack !== void 0);
|
|
1878
|
-
if (stackedLines.length) {
|
|
1879
|
-
const values = stackValues(stackedLines.map((s) => ({
|
|
1880
|
-
s,
|
|
1881
|
-
stack: input[s.index].stack ?? ""
|
|
1882
|
-
})), percent);
|
|
1883
|
-
stackedLines.forEach((s, k) => {
|
|
1884
|
-
s.stackBase = values[k].base;
|
|
1885
|
-
s.stackTop = values[k].top;
|
|
1886
|
-
});
|
|
1887
|
-
}
|
|
1888
|
-
const candles = visible.filter((s) => s.type === "candlestick");
|
|
1889
|
-
const candleBand = bandOf(candles, "x", xType === "category");
|
|
1890
|
-
for (const s of candles) s.barHalfWidth = candleBand * .3;
|
|
1891
|
-
const heatmaps = visible.filter((s) => s.type === "heatmap");
|
|
1892
|
-
if (heatmaps.length) {
|
|
1893
|
-
const cx = bandOf(heatmaps, "x", xType === "category") / 2;
|
|
1894
|
-
const cy = bandOf(heatmaps, "y", yType === "category") / 2;
|
|
1895
|
-
for (const s of heatmaps) s.cellHalf = {
|
|
1896
|
-
x: cx,
|
|
1897
|
-
y: cy
|
|
1898
|
-
};
|
|
1899
|
-
}
|
|
1900
|
-
const xOpts = options.xAxis;
|
|
1901
|
-
let xRange;
|
|
1902
|
-
if (xType === "category") xRange = [-.5, Math.max(1, categories.length) - .5];
|
|
1903
|
-
else {
|
|
1904
|
-
const values = [];
|
|
1905
|
-
for (const s of visible) {
|
|
1906
|
-
values.push(s.cols.xMin, s.cols.xMax);
|
|
1907
|
-
const pad = s.type === "bar" || s.type === "boxplot" ? band / 2 : s.type === "candlestick" ? candleBand / 2 : s.type === "heatmap" ? s.cellHalf.x : 0;
|
|
1908
|
-
if (pad) values.push(s.cols.xMin - pad, s.cols.xMax + pad);
|
|
1909
|
-
}
|
|
1910
|
-
xRange = ensureSpan(...extent(values), xType === "time" ? [Date.now() - 864e5, Date.now()] : [0, 1]);
|
|
1911
|
-
const pointsOnly = series.every((s) => s.type === "scatter");
|
|
1912
|
-
if (pointsOnly) {
|
|
1913
|
-
const pad = (xRange[1] - xRange[0]) * .04;
|
|
1914
|
-
xRange = [xRange[0] - pad, xRange[1] + pad];
|
|
1915
|
-
}
|
|
1916
|
-
if ((xOpts?.nice ?? (pointsOnly && xType === "linear")) && xType === "linear") xRange = niceDomain(xRange[0], xRange[1], xOpts?.tickCount ?? 6);
|
|
1917
|
-
if (xLog && xOpts?.nice !== false) xRange = niceLogRange(xRange);
|
|
1918
|
-
}
|
|
1919
|
-
const xBound = (v) => xLog ? Math.log10(v) : v;
|
|
1920
|
-
if (xOpts?.min !== void 0) xRange[0] = xBound(xOpts.min);
|
|
1921
|
-
if (xOpts?.max !== void 0) xRange[1] = xBound(xOpts.max);
|
|
1922
|
-
xRange = ensureSpan(xRange[0], xRange[1]);
|
|
1923
|
-
if (yLog) for (const s of series) {
|
|
1924
|
-
if (s.yAxis === "right" || s.type === "heatmap") continue;
|
|
1925
|
-
logColumns(s, LOG_MAP);
|
|
1926
|
-
}
|
|
1927
|
-
const valuesOf = (list, zero, log = false) => {
|
|
1928
|
-
const values = [];
|
|
1929
|
-
for (const s of list) if (s.type === "bar" && s.barBase && s.barTop) values.push(...extent(s.barBase), ...extent(s.barTop));
|
|
1930
|
-
else if (s.stackBase && s.stackTop) values.push(...extent(s.stackBase), ...extent(s.stackTop));
|
|
1931
|
-
else if (s.type === "heatmap") values.push(s.cols.yMin - s.cellHalf.y, s.cols.yMax + s.cellHalf.y);
|
|
1932
|
-
else values.push(s.cols.yMin, s.cols.yMax);
|
|
1933
|
-
const listBars = list.filter((s) => s.type === "bar");
|
|
1934
|
-
if (!log && (listBars.some((s) => !s.floating) || zero)) values.push(0);
|
|
1935
|
-
if (listBars.some((s) => s.showLabels && !s.labelsInside)) {
|
|
1936
|
-
const [lo, hi] = extent(values);
|
|
1937
|
-
if (hi > 0) values.push(hi + (hi - Math.min(0, lo)) * .1);
|
|
1938
|
-
if (lo < 0) values.push(lo - (Math.max(0, hi) - lo) * .1);
|
|
1939
|
-
}
|
|
1940
|
-
return values;
|
|
1941
|
-
};
|
|
1942
|
-
const leftVisible = visible.filter((s) => s.yAxis === "left");
|
|
1943
|
-
const leftSeries = leftVisible.length ? leftVisible : visible;
|
|
1944
|
-
const leftBars = leftSeries.filter((s) => s.type === "bar");
|
|
1945
|
-
let yRange;
|
|
1946
|
-
if (yType === "category") yRange = [-.5, Math.max(1, yCategories.length) - .5];
|
|
1947
|
-
else {
|
|
1948
|
-
const yValues = valuesOf(leftSeries, yOpts?.zero, yLog);
|
|
1949
|
-
const filled = leftBars.length > 0 || leftSeries.some((s) => s.type === "area");
|
|
1950
|
-
yRange = ensureSpan(...extent(yValues));
|
|
1951
|
-
if (series.every((s) => s.type === "scatter")) {
|
|
1952
|
-
const pad = (yRange[1] - yRange[0]) * .06;
|
|
1953
|
-
yRange = [yRange[0] - pad, yRange[1] + pad];
|
|
1954
|
-
} else if ((candles.length || leftSeries.some((s) => s.type === "boxplot")) && !leftBars.length) {
|
|
1955
|
-
const pad = (yRange[1] - yRange[0]) * .04;
|
|
1956
|
-
yRange = [yRange[0] - pad, yRange[1] + pad];
|
|
1957
|
-
}
|
|
1958
|
-
if ((yOpts?.nice ?? !heatmaps.length) && yType === "linear") yRange = niceDomain(yRange[0], yRange[1], yOpts?.tickCount ?? 5);
|
|
1959
|
-
if (yLog && yOpts?.nice !== false) yRange = decades(yRange);
|
|
1960
|
-
if (percent && (bars.length || stackedLines.length)) yRange = [extent(yValues)[0] < 0 ? -100 : 0, 100];
|
|
1961
|
-
if (filled && !yOpts?.zero && !yLog) {
|
|
1962
|
-
if (yRange[0] < 0 && extent(yValues)[0] >= 0) yRange[0] = 0;
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
const yBound = (v) => yLog ? Math.log10(v) : v;
|
|
1966
|
-
if (yOpts?.min !== void 0) yRange[0] = yBound(yOpts.min);
|
|
1967
|
-
if (yOpts?.max !== void 0) yRange[1] = yBound(yOpts.max);
|
|
1968
|
-
yRange = ensureSpan(yRange[0], yRange[1]);
|
|
1969
|
-
if (yLog) {
|
|
1970
|
-
for (const s of series) if (s.yAxis === "left") floorBases(s, yRange[0]);
|
|
1971
|
-
}
|
|
1972
|
-
const y2Opts = options.yAxis2;
|
|
1973
|
-
let y2 = null;
|
|
1974
|
-
let y2Map = null;
|
|
1975
|
-
const rightSeries = series.filter((s) => s.yAxis === "right");
|
|
1976
|
-
if (rightSeries.length && yType !== "category") {
|
|
1977
|
-
const y2Log = y2Opts?.type === "log";
|
|
1978
|
-
const rightVisible = rightSeries.filter((s) => !s.hidden);
|
|
1979
|
-
const [lo, hi] = ensureSpan(...extent(y2Log ? rightVisible.flatMap((s) => {
|
|
1980
|
-
const c = mapColumns(s.raw, LOG_MAP);
|
|
1981
|
-
return [c.yMin, c.yMax];
|
|
1982
|
-
}) : valuesOf(rightVisible, y2Opts?.zero)));
|
|
1983
|
-
let domain = y2Opts?.nice === false ? [lo, hi] : y2Log ? decades([lo, hi]) : alignedDomain(lo, hi, yRange, yOpts?.tickCount ?? 5);
|
|
1984
|
-
const y2Bound = (v) => y2Log ? Math.log10(v) : v;
|
|
1985
|
-
if (y2Opts?.min !== void 0) domain[0] = y2Bound(y2Opts.min);
|
|
1986
|
-
if (y2Opts?.max !== void 0) domain[1] = y2Bound(y2Opts.max);
|
|
1987
|
-
domain = ensureSpan(domain[0], domain[1]);
|
|
1988
|
-
const scale = (yRange[1] - yRange[0]) / (domain[1] - domain[0]);
|
|
1989
|
-
const map = {
|
|
1990
|
-
scale,
|
|
1991
|
-
offset: yRange[0] - domain[0] * scale,
|
|
1992
|
-
log: y2Log
|
|
1993
|
-
};
|
|
1994
|
-
y2Map = map;
|
|
1995
|
-
const zero = y2Log ? yRange[0] : mapper(map)(Math.min(Math.max(0, domain[0]), domain[1]));
|
|
1996
|
-
for (const s of rightSeries) {
|
|
1997
|
-
logColumns(s, map, zero);
|
|
1998
|
-
floorBases(s, yRange[0]);
|
|
1999
|
-
}
|
|
2000
|
-
y2 = axisModel(y2Opts, y2Log ? "log" : "linear", [], false, locale);
|
|
2001
|
-
}
|
|
2002
|
-
const timeline = yType === "time" && series.some((s) => s.type === "bar") && series.every((s) => s.type === "bar" ? s.floating : s.type === "scatter");
|
|
2003
|
-
const zoomOption = options.interaction === false ? false : options.interaction === true || options.interaction === void 0 ? true : options.interaction.zoom ?? true;
|
|
2004
|
-
const zoomAxes = zoomOption === false ? {
|
|
2005
|
-
x: false,
|
|
2006
|
-
y: false
|
|
2007
|
-
} : zoomOption === true ? {
|
|
2008
|
-
x: true,
|
|
2009
|
-
y: allScatter
|
|
1420
|
+
var screenRect = (s, xa, xb, ya, yb) => {
|
|
1421
|
+
const a = Math.min(xa, xb);
|
|
1422
|
+
const b = Math.max(xa, xb);
|
|
1423
|
+
const c = Math.min(ya, yb);
|
|
1424
|
+
const d = Math.max(ya, yb);
|
|
1425
|
+
return s.horizontal ? {
|
|
1426
|
+
x: c,
|
|
1427
|
+
y: a,
|
|
1428
|
+
width: d - c,
|
|
1429
|
+
height: b - a
|
|
2010
1430
|
} : {
|
|
2011
|
-
x:
|
|
2012
|
-
y:
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
return {
|
|
2016
|
-
series,
|
|
2017
|
-
x: axisModel(xOpts, xType, categories, false, locale),
|
|
2018
|
-
y: {
|
|
2019
|
-
...axisModel(yOpts, yType, yCategories, yType !== "category", locale),
|
|
2020
|
-
format: yFormat
|
|
2021
|
-
},
|
|
2022
|
-
y2,
|
|
2023
|
-
y2Map,
|
|
2024
|
-
horizontal,
|
|
2025
|
-
locale,
|
|
2026
|
-
labels,
|
|
2027
|
-
formatValue: valueFormatter(locale),
|
|
2028
|
-
base: {
|
|
2029
|
-
x: xRange,
|
|
2030
|
-
y: yRange
|
|
2031
|
-
},
|
|
2032
|
-
allScatter,
|
|
2033
|
-
zoom: zoomAxes,
|
|
2034
|
-
tooltipMode: tooltipOptions?.mode ?? (allScatter ? "nearest" : timeline ? "item" : "axis"),
|
|
2035
|
-
baseline: yLog ? yRange[0] : Math.min(Math.max(0, yRange[0]), yRange[1]),
|
|
2036
|
-
minSpan: {
|
|
2037
|
-
x: xType === "category" ? 1 : (xRange[1] - xRange[0]) * 1e-7,
|
|
2038
|
-
y: yType === "category" ? 1 : (yRange[1] - yRange[0]) * 1e-7
|
|
2039
|
-
},
|
|
2040
|
-
percent,
|
|
2041
|
-
stacks: input.map((s) => s.stack !== void 0 && (s.type === "bar" || s.type === "line" || s.type === "area" || !s.type) ? `${s.type === "bar" ? "bar" : "line"}:${s.stack}` : null)
|
|
1431
|
+
x: a,
|
|
1432
|
+
y: c,
|
|
1433
|
+
width: b - a,
|
|
1434
|
+
height: d - c
|
|
2042
1435
|
};
|
|
2043
1436
|
};
|
|
2044
|
-
/** Y value drawn for a point: its stacked top for stacks, the close for candles */
|
|
2045
|
-
var plottedY = (s, i) => s.barTop ? s.barTop[i] : s.stackTop ? s.stackTop[i] : s.cols.y[i];
|
|
2046
|
-
var grids = /* @__PURE__ */ new WeakMap();
|
|
2047
|
-
/** Fill opacity of bar `i`: its own `opacity`, else the one of the series */
|
|
2048
|
-
var barOpacity = (s, i) => {
|
|
2049
|
-
const own = s.cols.opacities?.[i];
|
|
2050
|
-
return own !== void 0 && Number.isFinite(own) ? own : s.opacity;
|
|
2051
|
-
};
|
|
2052
|
-
/** Spatial index of a scatter series, built on first use and kept with its data */
|
|
2053
|
-
var gridOf = (s) => {
|
|
2054
|
-
let grid = grids.get(s.cols);
|
|
2055
|
-
if (!grid) {
|
|
2056
|
-
grid = new GridIndex(s.cols.x, s.cols.y, s.cols.count);
|
|
2057
|
-
grids.set(s.cols, grid);
|
|
2058
|
-
}
|
|
2059
|
-
return grid;
|
|
2060
|
-
};
|
|
2061
|
-
//#endregion
|
|
2062
|
-
//#region ../charts/src/engine/overlay.ts
|
|
2063
|
-
/** Padding of the labels inside the bars, CSS pixels */
|
|
2064
|
-
var INSIDE_PAD$1 = 6;
|
|
2065
1437
|
/**
|
|
2066
|
-
*
|
|
2067
|
-
* `
|
|
2068
|
-
*
|
|
1438
|
+
* Inset (per side) and corner radius of heatmap cells of `width` ×
|
|
1439
|
+
* `height` CSS pixels: a hairline apart (1px, 2px on large cells), corners
|
|
1440
|
+
* of `radius` at most 20% of the cell; tiny cells touch
|
|
2069
1441
|
*/
|
|
2070
|
-
var
|
|
2071
|
-
const
|
|
2072
|
-
if (
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
};
|
|
2076
|
-
var font = (theme, size = theme.fontSize, weight = 400) => `${weight} ${size}px ${theme.fontFamily}`;
|
|
2077
|
-
var crisp = (v) => Math.round(v) + .5;
|
|
2078
|
-
/** Path of a rounded box with half-height corners */
|
|
2079
|
-
var pill = (ctx, x, y, w, h) => {
|
|
2080
|
-
const r = Math.min(h / 2, w / 2);
|
|
2081
|
-
ctx.beginPath();
|
|
2082
|
-
ctx.moveTo(x + r, y);
|
|
2083
|
-
ctx.lineTo(x + w - r, y);
|
|
2084
|
-
ctx.arcTo(x + w, y, x + w, y + r, r);
|
|
2085
|
-
ctx.lineTo(x + w, y + h - r);
|
|
2086
|
-
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
|
|
2087
|
-
ctx.lineTo(x + r, y + h);
|
|
2088
|
-
ctx.arcTo(x, y + h, x, y + h - r, r);
|
|
2089
|
-
ctx.lineTo(x, y + r);
|
|
2090
|
-
ctx.arcTo(x, y, x + r, y, r);
|
|
2091
|
-
ctx.closePath();
|
|
2092
|
-
};
|
|
2093
|
-
/** Box of a text annotation, kept inside the plot */
|
|
2094
|
-
var textBox = (a, s, width, height) => {
|
|
2095
|
-
const { plot } = s;
|
|
2096
|
-
const at = toScreen(s, a.x ?? 0, a.y ?? 0);
|
|
2097
|
-
const free = s.horizontal ? {
|
|
2098
|
-
x: a.y === void 0,
|
|
2099
|
-
y: a.x === void 0
|
|
2100
|
-
} : {
|
|
2101
|
-
x: a.x === void 0,
|
|
2102
|
-
y: a.y === void 0
|
|
1442
|
+
var heatCellStyle = (width, height, radius) => {
|
|
1443
|
+
const size = Math.min(Math.abs(width), Math.abs(height));
|
|
1444
|
+
if (!(size >= 4)) return {
|
|
1445
|
+
inset: 0,
|
|
1446
|
+
radius: 0
|
|
2103
1447
|
};
|
|
2104
|
-
const px = free.x ? plot.x + plot.width / 2 : at.x;
|
|
2105
|
-
const py = free.y ? plot.y + plot.height / 2 : at.y;
|
|
2106
|
-
const gap = 6;
|
|
2107
|
-
let x = px - width / 2;
|
|
2108
|
-
let y = py - height / 2;
|
|
2109
|
-
if (a.anchor === "top") y = py + gap;
|
|
2110
|
-
else if (a.anchor === "bottom") y = py - gap - height;
|
|
2111
|
-
else if (a.anchor === "left") x = px + gap;
|
|
2112
|
-
else if (a.anchor === "right") x = px - gap - width;
|
|
2113
|
-
x = Math.max(plot.x + gap, Math.min(x, plot.x + plot.width - gap - width));
|
|
2114
|
-
y = Math.max(plot.y + 2, Math.min(y, plot.y + plot.height - 2 - height));
|
|
2115
1448
|
return {
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
width,
|
|
2119
|
-
height
|
|
1449
|
+
inset: Math.round(Math.min(1, .5 + size / 96) * 4) / 4,
|
|
1450
|
+
radius: Math.round(Math.max(0, Math.min(radius, size * .2)) * 4) / 4
|
|
2120
1451
|
};
|
|
2121
1452
|
};
|
|
2122
|
-
/**
|
|
2123
|
-
var
|
|
2124
|
-
const {
|
|
2125
|
-
const
|
|
2126
|
-
const
|
|
2127
|
-
const
|
|
2128
|
-
|
|
1453
|
+
/** Room for the axes, then the plot area */
|
|
1454
|
+
var computePlot = (input) => {
|
|
1455
|
+
const { width, height, padding, fontSize } = input;
|
|
1456
|
+
const line = Math.ceil(fontSize * 1.4);
|
|
1457
|
+
const left = padding.left + (input.yLabelWidth > 0 ? input.yLabelWidth + 8 : 0) + (input.yTitle ? line + 6 : 0);
|
|
1458
|
+
const bottom = padding.bottom + (input.xAxisVisible ? line + 8 : 0) + (input.xTitle ? line + 4 : 0);
|
|
1459
|
+
const top = padding.top;
|
|
1460
|
+
const y2Width = input.y2LabelWidth ?? 0;
|
|
1461
|
+
const right = y2Width > 0 || input.y2Title ? Math.max(4, padding.left) + (y2Width > 0 ? y2Width + 8 : 0) + (input.y2Title ? line + 6 : 0) : padding.right;
|
|
1462
|
+
return {
|
|
1463
|
+
x: Math.round(left),
|
|
1464
|
+
y: Math.round(top),
|
|
1465
|
+
width: Math.max(10, Math.round(width - left - right)),
|
|
1466
|
+
height: Math.max(10, Math.round(height - top - bottom))
|
|
1467
|
+
};
|
|
1468
|
+
};
|
|
1469
|
+
//#endregion
|
|
1470
|
+
//#region ../charts/src/engine/labels.ts
|
|
1471
|
+
var CHART_LABELS = {
|
|
1472
|
+
en: {
|
|
1473
|
+
open: "Open",
|
|
1474
|
+
high: "High",
|
|
1475
|
+
low: "Low",
|
|
1476
|
+
close: "Close",
|
|
1477
|
+
max: "Max",
|
|
1478
|
+
q3: "Q3",
|
|
1479
|
+
median: "Median",
|
|
1480
|
+
q1: "Q1",
|
|
1481
|
+
min: "Min",
|
|
1482
|
+
outliers: "Outliers",
|
|
1483
|
+
value: "Value",
|
|
1484
|
+
x: "x",
|
|
1485
|
+
y: "y",
|
|
1486
|
+
series: "Series",
|
|
1487
|
+
chart: "Chart",
|
|
1488
|
+
chartOf: "Chart: {series}",
|
|
1489
|
+
start: "Start",
|
|
1490
|
+
end: "End",
|
|
1491
|
+
duration: "Duration",
|
|
1492
|
+
progress: "Progress",
|
|
1493
|
+
dependencies: "Depends on",
|
|
1494
|
+
outlier: "Outlier"
|
|
1495
|
+
},
|
|
1496
|
+
zh: {
|
|
1497
|
+
open: "开盘",
|
|
1498
|
+
high: "最高",
|
|
1499
|
+
low: "最低",
|
|
1500
|
+
close: "收盘",
|
|
1501
|
+
max: "最大值",
|
|
1502
|
+
q3: "上四分位",
|
|
1503
|
+
median: "中位数",
|
|
1504
|
+
q1: "下四分位",
|
|
1505
|
+
min: "最小值",
|
|
1506
|
+
outliers: "异常值",
|
|
1507
|
+
value: "数值",
|
|
1508
|
+
series: "系列",
|
|
1509
|
+
chart: "图表",
|
|
1510
|
+
chartOf: "图表:{series}",
|
|
1511
|
+
start: "开始",
|
|
1512
|
+
end: "结束",
|
|
1513
|
+
duration: "时长",
|
|
1514
|
+
progress: "进度",
|
|
1515
|
+
dependencies: "前置任务",
|
|
1516
|
+
outlier: "异常值"
|
|
1517
|
+
},
|
|
1518
|
+
ja: {
|
|
1519
|
+
open: "始値",
|
|
1520
|
+
high: "高値",
|
|
1521
|
+
low: "安値",
|
|
1522
|
+
close: "終値",
|
|
1523
|
+
max: "最大値",
|
|
1524
|
+
q3: "第3四分位",
|
|
1525
|
+
median: "中央値",
|
|
1526
|
+
q1: "第1四分位",
|
|
1527
|
+
min: "最小値",
|
|
1528
|
+
outliers: "外れ値",
|
|
1529
|
+
value: "値",
|
|
1530
|
+
series: "系列",
|
|
1531
|
+
chart: "グラフ",
|
|
1532
|
+
chartOf: "グラフ:{series}",
|
|
1533
|
+
start: "開始",
|
|
1534
|
+
end: "終了",
|
|
1535
|
+
duration: "期間",
|
|
1536
|
+
progress: "進捗",
|
|
1537
|
+
dependencies: "先行タスク",
|
|
1538
|
+
outlier: "外れ値"
|
|
1539
|
+
},
|
|
1540
|
+
fr: {
|
|
1541
|
+
open: "Ouverture",
|
|
1542
|
+
high: "Plus haut",
|
|
1543
|
+
low: "Plus bas",
|
|
1544
|
+
close: "Clôture",
|
|
1545
|
+
max: "Max",
|
|
1546
|
+
q3: "Q3",
|
|
1547
|
+
median: "Médiane",
|
|
1548
|
+
q1: "Q1",
|
|
1549
|
+
min: "Min",
|
|
1550
|
+
outliers: "Valeurs aberrantes",
|
|
1551
|
+
value: "Valeur",
|
|
1552
|
+
series: "Série",
|
|
1553
|
+
chart: "Graphique",
|
|
1554
|
+
chartOf: "Graphique : {series}",
|
|
1555
|
+
start: "Début",
|
|
1556
|
+
end: "Fin",
|
|
1557
|
+
duration: "Durée",
|
|
1558
|
+
progress: "Avancement",
|
|
1559
|
+
dependencies: "Dépend de",
|
|
1560
|
+
outlier: "Valeur aberrante"
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
/** Texts of a locale with the overrides */
|
|
1564
|
+
var chartLabels = (locale, overrides) => localeLabels(CHART_LABELS, locale, overrides);
|
|
1565
|
+
/** Separator of the list of series names in accessible names */
|
|
1566
|
+
var listSeparator = (locale) => /^(zh|ja)\b/i.test(locale) ? "、" : ", ";
|
|
1567
|
+
/** Default accessible name of a chart model: "Chart: Revenue, Costs" (unnamed series left out) */
|
|
1568
|
+
var chartAriaLabel = (model) => {
|
|
1569
|
+
const named = model.series.filter((s) => s.name);
|
|
1570
|
+
return named.length ? model.labels.chartOf.replace("{series}", named.map((s) => s.name).join(listSeparator(model.locale))) : model.labels.chart;
|
|
1571
|
+
};
|
|
1572
|
+
/**
|
|
1573
|
+
* Localized accessible description of a chart, for platforms that build their
|
|
1574
|
+
* own accessible name ("Chart: Revenue, Costs", "营收:营收、成本").
|
|
1575
|
+
* With a `title`, the title replaces the generic "Chart" word.
|
|
1576
|
+
*/
|
|
1577
|
+
function describeChart$1(input) {
|
|
1578
|
+
const locale = input.locale ?? "en";
|
|
1579
|
+
const labels = chartLabels(locale, input.labels);
|
|
1580
|
+
const series = input.series ?? [];
|
|
1581
|
+
const title = input.title?.trim();
|
|
1582
|
+
const generic = chartAriaLabel({
|
|
1583
|
+
labels,
|
|
1584
|
+
locale,
|
|
1585
|
+
series
|
|
1586
|
+
});
|
|
1587
|
+
if (!title) return generic;
|
|
1588
|
+
if (!series.length) return title;
|
|
1589
|
+
const index = labels.chartOf.indexOf(labels.chart);
|
|
1590
|
+
if (index < 0 || !labels.chart) return `${title}. ${generic}`;
|
|
1591
|
+
const names = series.map((s) => s.name).join(listSeparator(locale));
|
|
1592
|
+
const fill = (part) => part.split("{series}").join(names);
|
|
1593
|
+
return fill(labels.chartOf.slice(0, index)) + title + fill(labels.chartOf.slice(index + labels.chart.length));
|
|
1594
|
+
}
|
|
1595
|
+
//#endregion
|
|
1596
|
+
//#region ../charts/src/engine/spatial.ts
|
|
1597
|
+
var GridIndex = class {
|
|
1598
|
+
constructor(x, y, count, pointsPerCell = 4) {
|
|
1599
|
+
let xMin = Infinity;
|
|
1600
|
+
let xMax = -Infinity;
|
|
1601
|
+
let yMin = Infinity;
|
|
1602
|
+
let yMax = -Infinity;
|
|
1603
|
+
let valid = 0;
|
|
1604
|
+
for (let i = 0; i < count; i++) {
|
|
1605
|
+
const xi = x[i];
|
|
1606
|
+
const yi = y[i];
|
|
1607
|
+
if (!Number.isFinite(xi) || !Number.isFinite(yi)) continue;
|
|
1608
|
+
valid++;
|
|
1609
|
+
if (xi < xMin) xMin = xi;
|
|
1610
|
+
if (xi > xMax) xMax = xi;
|
|
1611
|
+
if (yi < yMin) yMin = yi;
|
|
1612
|
+
if (yi > yMax) yMax = yi;
|
|
1613
|
+
}
|
|
1614
|
+
const side = Math.min(1024, Math.max(1, Math.ceil(Math.sqrt(valid / pointsPerCell))));
|
|
1615
|
+
this.cols = side;
|
|
1616
|
+
this.rows = side;
|
|
1617
|
+
this.x0 = valid ? xMin : 0;
|
|
1618
|
+
this.y0 = valid ? yMin : 0;
|
|
1619
|
+
this.cellW = valid && xMax > xMin ? (xMax - xMin) / side : 1;
|
|
1620
|
+
this.cellH = valid && yMax > yMin ? (yMax - yMin) / side : 1;
|
|
1621
|
+
const cells = new Int32Array(count);
|
|
1622
|
+
const counts = new Uint32Array(side * side + 1);
|
|
1623
|
+
for (let i = 0; i < count; i++) {
|
|
1624
|
+
const xi = x[i];
|
|
1625
|
+
const yi = y[i];
|
|
1626
|
+
if (!Number.isFinite(xi) || !Number.isFinite(yi)) {
|
|
1627
|
+
cells[i] = -1;
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
const cell = this.cellOf(xi, yi);
|
|
1631
|
+
cells[i] = cell;
|
|
1632
|
+
counts[cell + 1]++;
|
|
1633
|
+
}
|
|
1634
|
+
for (let c = 1; c < counts.length; c++) counts[c] += counts[c - 1];
|
|
1635
|
+
this.starts = counts.slice();
|
|
1636
|
+
const cursor = counts;
|
|
1637
|
+
this.order = new Uint32Array(valid);
|
|
1638
|
+
for (let i = 0; i < count; i++) {
|
|
1639
|
+
const cell = cells[i];
|
|
1640
|
+
if (cell >= 0) this.order[cursor[cell]++] = i;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
column(xi) {
|
|
1644
|
+
const c = Math.floor((xi - this.x0) / this.cellW);
|
|
1645
|
+
return c < 0 ? 0 : c >= this.cols ? this.cols - 1 : c;
|
|
1646
|
+
}
|
|
1647
|
+
row(yi) {
|
|
1648
|
+
const r = Math.floor((yi - this.y0) / this.cellH);
|
|
1649
|
+
return r < 0 ? 0 : r >= this.rows ? this.rows - 1 : r;
|
|
1650
|
+
}
|
|
1651
|
+
cellOf(xi, yi) {
|
|
1652
|
+
return this.row(yi) * this.cols + this.column(xi);
|
|
1653
|
+
}
|
|
1654
|
+
/** Calls `visit` with every point that may lie inside the box */
|
|
1655
|
+
query(xMin, xMax, yMin, yMax, visit) {
|
|
1656
|
+
if (this.order.length === 0) return;
|
|
1657
|
+
const c0 = this.column(Math.min(xMin, xMax));
|
|
1658
|
+
const c1 = this.column(Math.max(xMin, xMax));
|
|
1659
|
+
const r0 = this.row(Math.min(yMin, yMax));
|
|
1660
|
+
const r1 = this.row(Math.max(yMin, yMax));
|
|
1661
|
+
for (let r = r0; r <= r1; r++) for (let c = c0; c <= c1; c++) {
|
|
1662
|
+
const cell = r * this.cols + c;
|
|
1663
|
+
for (let k = this.starts[cell]; k < this.starts[cell + 1]; k++) visit(this.order[k]);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1667
|
+
//#endregion
|
|
1668
|
+
//#region ../charts/src/engine/model.ts
|
|
1669
|
+
/** Plotted units of a log axis: the base-10 logarithm of the values */
|
|
1670
|
+
var LOG_MAP = {
|
|
1671
|
+
scale: 1,
|
|
1672
|
+
offset: 0,
|
|
1673
|
+
log: true
|
|
2129
1674
|
};
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
const
|
|
2136
|
-
let
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
if (width(`${chars.slice(0, mid).join("")}…`) <= max) lo = mid;
|
|
2141
|
-
else hi = mid - 1;
|
|
1675
|
+
var firstX = (data) => {
|
|
1676
|
+
if (typeof data === "object" && data !== null && !("length" in data)) {
|
|
1677
|
+
const xs = data.x;
|
|
1678
|
+
return xs && xs.length ? xs[0] : void 0;
|
|
1679
|
+
}
|
|
1680
|
+
const list = data;
|
|
1681
|
+
for (let i = 0; i < Math.min(list.length, 8); i++) {
|
|
1682
|
+
const item = list[i];
|
|
1683
|
+
if (Array.isArray(item)) return item[0];
|
|
1684
|
+
if (item && typeof item === "object") return item.x;
|
|
2142
1685
|
}
|
|
2143
|
-
return lo > 0 ? `${chars.slice(0, lo).join("").trimEnd()}…` : "";
|
|
2144
1686
|
};
|
|
2145
|
-
/**
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
if (!hasAreaTitle(a) || r.width <= 0 || r.height <= 0) return null;
|
|
2152
|
-
const pad = 10;
|
|
2153
|
-
const lineHeight = fontSize + 4;
|
|
2154
|
-
const max = Math.max(0, r.width - 20);
|
|
2155
|
-
const labelSize = fontSize + 1;
|
|
2156
|
-
const badgeSize = Math.max(9, fontSize - 1);
|
|
2157
|
-
const gap = 6;
|
|
2158
|
-
const badge = a.badge ? fitText(a.badge, Math.max(0, max - 10), (t) => measure(t, badgeSize, 700)) : "";
|
|
2159
|
-
const badgeWidth = badge ? measure(badge, badgeSize, 700) + 10 : 0;
|
|
2160
|
-
let detail = a.detail ?? "";
|
|
2161
|
-
let detailWidth = detail ? measure(detail, fontSize, 500) : 0;
|
|
2162
|
-
const room = () => max - (badge ? badgeWidth + gap : 0) - (detail ? detailWidth + gap : 0);
|
|
2163
|
-
if (detail && a.label && room() < 32) {
|
|
2164
|
-
detail = "";
|
|
2165
|
-
detailWidth = 0;
|
|
1687
|
+
/** Axis type when not given: dates -> time, strings -> category */
|
|
1688
|
+
var detectXType = (datas) => {
|
|
1689
|
+
for (const data of datas) {
|
|
1690
|
+
const x = firstX(data);
|
|
1691
|
+
if (x instanceof Date) return "time";
|
|
1692
|
+
if (typeof x === "string") return "category";
|
|
2166
1693
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
1694
|
+
return "linear";
|
|
1695
|
+
};
|
|
1696
|
+
var axisModel = (options, type, categories, gridDefault, locale) => ({
|
|
1697
|
+
type,
|
|
1698
|
+
reverse: !!options?.reverse,
|
|
1699
|
+
categories,
|
|
1700
|
+
title: options?.title,
|
|
1701
|
+
format: options?.format,
|
|
1702
|
+
grid: options?.grid ?? gridDefault,
|
|
1703
|
+
visible: options?.visible ?? true,
|
|
1704
|
+
tickCount: options?.tickCount,
|
|
1705
|
+
locale,
|
|
1706
|
+
timeZone: options?.timeZone ?? "local"
|
|
1707
|
+
});
|
|
1708
|
+
/** Defined entries only (`{ open: undefined }` keeps the built-in text) */
|
|
1709
|
+
var definedEntries = (value) => {
|
|
1710
|
+
const out = {};
|
|
1711
|
+
if (!value) return out;
|
|
1712
|
+
for (const key of Object.keys(value)) if (value[key] !== void 0) out[key] = value[key];
|
|
1713
|
+
return out;
|
|
1714
|
+
};
|
|
1715
|
+
var extent = (values) => {
|
|
1716
|
+
let min = Infinity;
|
|
1717
|
+
let max = -Infinity;
|
|
1718
|
+
for (let i = 0; i < values.length; i++) {
|
|
1719
|
+
const v = values[i];
|
|
1720
|
+
if (!Number.isFinite(v)) continue;
|
|
1721
|
+
if (v < min) min = v;
|
|
1722
|
+
if (v > max) max = v;
|
|
2170
1723
|
}
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
});
|
|
2183
|
-
if (label) line.push({
|
|
2184
|
-
kind: "label",
|
|
2185
|
-
text: label,
|
|
2186
|
-
x: 0,
|
|
2187
|
-
y: 0,
|
|
2188
|
-
width: measure(label, labelSize, 600),
|
|
2189
|
-
height: lineHeight,
|
|
2190
|
-
size: labelSize,
|
|
2191
|
-
weight: 600
|
|
2192
|
-
});
|
|
2193
|
-
if (detail) line.push({
|
|
2194
|
-
kind: "detail",
|
|
2195
|
-
text: detail,
|
|
2196
|
-
x: 0,
|
|
2197
|
-
y: 0,
|
|
2198
|
-
width: detailWidth,
|
|
2199
|
-
height: lineHeight,
|
|
2200
|
-
size: fontSize,
|
|
2201
|
-
weight: 500
|
|
1724
|
+
return [min, max];
|
|
1725
|
+
};
|
|
1726
|
+
/** Smallest positive gap between consecutive values (bar width on numeric axes) */
|
|
1727
|
+
var smallestStep = (columns, sorted) => {
|
|
1728
|
+
let step = Infinity;
|
|
1729
|
+
columns.forEach((values, k) => {
|
|
1730
|
+
const xs = sorted[k] ? values : Float64Array.from(values).sort();
|
|
1731
|
+
for (let i = 1; i < xs.length; i++) {
|
|
1732
|
+
const d = xs[i] - xs[i - 1];
|
|
1733
|
+
if (d > 0 && d < step) step = d;
|
|
1734
|
+
}
|
|
2202
1735
|
});
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
if (
|
|
2207
|
-
const
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
x
|
|
1736
|
+
return step;
|
|
1737
|
+
};
|
|
1738
|
+
var bandOf = (list, axis, category) => {
|
|
1739
|
+
if (category) return 1;
|
|
1740
|
+
const step = smallestStep(list.map((s) => axis === "x" ? s.cols.x : s.cols.y), list.map((s) => axis === "x" && s.cols.sorted));
|
|
1741
|
+
return Number.isFinite(step) ? step : 1;
|
|
1742
|
+
};
|
|
1743
|
+
/**
|
|
1744
|
+
* Stacks the y values of series sharing a stack id (positive and negative
|
|
1745
|
+
* values separately); `percent` scales every stack to 100 at each x
|
|
1746
|
+
*/
|
|
1747
|
+
var stackValues = (list, percent) => {
|
|
1748
|
+
const totals = /* @__PURE__ */ new Map();
|
|
1749
|
+
if (percent) for (const { s, stack } of list) {
|
|
1750
|
+
let byX = totals.get(stack);
|
|
1751
|
+
if (!byX) totals.set(stack, byX = /* @__PURE__ */ new Map());
|
|
1752
|
+
const { x, y, count } = s.cols;
|
|
1753
|
+
for (let i = 0; i < count; i++) {
|
|
1754
|
+
const v = y[i];
|
|
1755
|
+
if (!Number.isFinite(v)) continue;
|
|
1756
|
+
const t = byX.get(x[i]) ?? [0, 0];
|
|
1757
|
+
if (v >= 0) t[0] += v;
|
|
1758
|
+
else t[1] -= v;
|
|
1759
|
+
byX.set(x[i], t);
|
|
1760
|
+
}
|
|
2220
1761
|
}
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
1762
|
+
const running = /* @__PURE__ */ new Map();
|
|
1763
|
+
const members = /* @__PURE__ */ new Map();
|
|
1764
|
+
for (const { stack } of list) members.set(stack, (members.get(stack) ?? 0) + 1);
|
|
1765
|
+
return list.map(({ s, stack }) => {
|
|
1766
|
+
const { x, y, count } = s.cols;
|
|
1767
|
+
if (!percent && members.get(stack) === 1) {
|
|
1768
|
+
const base = new Float64Array(count);
|
|
1769
|
+
const top = new Float64Array(count);
|
|
1770
|
+
for (let i = 0; i < count; i++) {
|
|
1771
|
+
const v = y[i];
|
|
1772
|
+
const valid = Number.isFinite(v);
|
|
1773
|
+
base[i] = valid ? 0 : NaN;
|
|
1774
|
+
top[i] = valid ? v : NaN;
|
|
1775
|
+
}
|
|
1776
|
+
return {
|
|
1777
|
+
base,
|
|
1778
|
+
top
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
let byX = running.get(stack);
|
|
1782
|
+
if (!byX) running.set(stack, byX = /* @__PURE__ */ new Map());
|
|
1783
|
+
const sums = totals.get(stack);
|
|
1784
|
+
const base = new Float64Array(count);
|
|
1785
|
+
const top = new Float64Array(count);
|
|
1786
|
+
for (let i = 0; i < count; i++) {
|
|
1787
|
+
let v = y[i];
|
|
1788
|
+
if (!Number.isFinite(v)) {
|
|
1789
|
+
base[i] = NaN;
|
|
1790
|
+
top[i] = NaN;
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
if (sums) {
|
|
1794
|
+
const t = sums.get(x[i]);
|
|
1795
|
+
const total = t ? v >= 0 ? t[0] : t[1] : 0;
|
|
1796
|
+
v = total > 0 ? v / total * 100 : 0;
|
|
1797
|
+
}
|
|
1798
|
+
const t = byX.get(x[i]) ?? [0, 0];
|
|
1799
|
+
const b = v >= 0 ? t[0] : t[1];
|
|
1800
|
+
base[i] = b;
|
|
1801
|
+
top[i] = b + v;
|
|
1802
|
+
if (v >= 0) t[0] += v;
|
|
1803
|
+
else t[1] += v;
|
|
1804
|
+
byX.set(x[i], t);
|
|
1805
|
+
}
|
|
1806
|
+
return {
|
|
1807
|
+
base,
|
|
1808
|
+
top
|
|
1809
|
+
};
|
|
2230
1810
|
});
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
1811
|
+
};
|
|
1812
|
+
var DEFAULT_UP = "var(--success-color, #16a34a)";
|
|
1813
|
+
var DEFAULT_DOWN = "var(--danger-color, #dc2626)";
|
|
1814
|
+
/** Dates in the y values (or starts) of objects: a time value axis */
|
|
1815
|
+
var hasDateY = (datas) => {
|
|
1816
|
+
for (const data of datas) {
|
|
1817
|
+
if (typeof data !== "object" || data === null || !("length" in data)) continue;
|
|
1818
|
+
const list = data;
|
|
1819
|
+
for (let i = 0; i < Math.min(list.length, 8); i++) {
|
|
1820
|
+
const item = list[i];
|
|
1821
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
1822
|
+
const d = item;
|
|
1823
|
+
if (d.y instanceof Date || d.y0 instanceof Date) return true;
|
|
1824
|
+
}
|
|
2239
1825
|
}
|
|
2240
|
-
}
|
|
1826
|
+
}
|
|
1827
|
+
return false;
|
|
1828
|
+
};
|
|
1829
|
+
var precise = (v) => Number(v.toPrecision(12));
|
|
1830
|
+
/** Next step of the 1, 2, 5 × 10^k sequence */
|
|
1831
|
+
var nextStep = (step) => {
|
|
1832
|
+
const power = 10 ** Math.floor(Math.log10(step) + 1e-9);
|
|
1833
|
+
const f = step / power;
|
|
1834
|
+
return (f < 1.5 ? 2 : f < 3.5 ? 5 : 10) * power;
|
|
2241
1835
|
};
|
|
2242
1836
|
/**
|
|
2243
|
-
*
|
|
2244
|
-
*
|
|
1837
|
+
* Range of the right axis covering [lo, hi] with as many tick intervals as
|
|
1838
|
+
* the left range has (its ticks then line up with the left grid)
|
|
2245
1839
|
*/
|
|
2246
|
-
var
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
const bottomTicks = scales.horizontal ? yTicks : xTicks;
|
|
2258
|
-
const leftTicks = scales.horizontal ? xTicks : yTicks;
|
|
2259
|
-
const bottomPx = scales.horizontal ? scales.yToPx : scales.xToPx;
|
|
2260
|
-
const leftPx = scales.horizontal ? scales.xToPx : scales.yToPx;
|
|
2261
|
-
ctx.clearRect(0, 0, input.width, input.height);
|
|
2262
|
-
ctx.save();
|
|
2263
|
-
ctx.beginPath();
|
|
2264
|
-
ctx.rect(plot.x, plot.y, plot.width, plot.height);
|
|
2265
|
-
ctx.clip();
|
|
2266
|
-
for (const a of input.annotations) {
|
|
2267
|
-
if (a.type !== "rect") continue;
|
|
2268
|
-
const r = rectOf(a, scales);
|
|
2269
|
-
if (r.width <= 0 || r.height <= 0) continue;
|
|
2270
|
-
ctx.fillStyle = toCss(a.color, a.opacity);
|
|
2271
|
-
ctx.fillRect(r.x, r.y, r.width, r.height);
|
|
2272
|
-
}
|
|
2273
|
-
ctx.restore();
|
|
2274
|
-
ctx.lineWidth = 1;
|
|
2275
|
-
ctx.strokeStyle = toCss(theme.grid);
|
|
2276
|
-
ctx.beginPath();
|
|
2277
|
-
if (y.grid) for (const v of leftTicks.values) {
|
|
2278
|
-
const py = crisp(leftPx(v));
|
|
2279
|
-
if (py < plot.y - 1 || py > plot.y + plot.height + 1) continue;
|
|
2280
|
-
ctx.moveTo(plot.x, py);
|
|
2281
|
-
ctx.lineTo(plot.x + plot.width, py);
|
|
2282
|
-
}
|
|
2283
|
-
if (x.grid) for (const v of bottomTicks.values) {
|
|
2284
|
-
const px = crisp(bottomPx(v));
|
|
2285
|
-
if (px < plot.x - 1 || px > plot.x + plot.width + 1) continue;
|
|
2286
|
-
ctx.moveTo(px, plot.y);
|
|
2287
|
-
ctx.lineTo(px, plot.y + plot.height);
|
|
2288
|
-
}
|
|
2289
|
-
ctx.stroke();
|
|
2290
|
-
const line = Math.ceil(theme.fontSize * 1.4);
|
|
2291
|
-
ctx.font = font(theme);
|
|
2292
|
-
ctx.fillStyle = toCss(theme.textMuted);
|
|
2293
|
-
if (x.visible) {
|
|
2294
|
-
ctx.strokeStyle = toCss(theme.axis);
|
|
2295
|
-
ctx.beginPath();
|
|
2296
|
-
const base = crisp(plot.y + plot.height);
|
|
2297
|
-
ctx.moveTo(plot.x, base);
|
|
2298
|
-
ctx.lineTo(plot.x + plot.width, base);
|
|
2299
|
-
ctx.stroke();
|
|
2300
|
-
ctx.textBaseline = "top";
|
|
2301
|
-
ctx.textAlign = "center";
|
|
2302
|
-
let lastRight = -Infinity;
|
|
2303
|
-
const order = bottomTicks.values.map((v, i) => ({
|
|
2304
|
-
px: bottomPx(v),
|
|
2305
|
-
text: bottomTicks.labels[i]
|
|
2306
|
-
})).sort((a, b) => a.px - b.px);
|
|
2307
|
-
for (const { px, text } of order) {
|
|
2308
|
-
if (px < plot.x - .5 || px > plot.x + plot.width + .5) continue;
|
|
2309
|
-
const w = ctx.measureText(text).width;
|
|
2310
|
-
const left = Math.max(0, Math.min(input.width - w, px - w / 2));
|
|
2311
|
-
if (left < lastRight + 6) continue;
|
|
2312
|
-
lastRight = left + w;
|
|
2313
|
-
ctx.fillText(text, left + w / 2, plot.y + plot.height + 7);
|
|
2314
|
-
}
|
|
2315
|
-
if (x.title) {
|
|
2316
|
-
ctx.font = font(theme, theme.fontSize + 1, 500);
|
|
2317
|
-
ctx.fillStyle = toCss(theme.text, .8);
|
|
2318
|
-
ctx.fillText(x.title, plot.x + plot.width / 2, plot.y + plot.height + line + 10);
|
|
2319
|
-
}
|
|
1840
|
+
var alignedDomain = (lo, hi, left, count) => {
|
|
1841
|
+
const [a0, a1] = left;
|
|
1842
|
+
const stepL = tickStep(a0, a1, count);
|
|
1843
|
+
const n = stepL > 0 ? Math.round((a1 - a0) / stepL) : 0;
|
|
1844
|
+
[lo, hi] = ensureSpan(lo, hi);
|
|
1845
|
+
if (n < 1 || Math.abs(n * stepL - (a1 - a0)) > stepL * 1e-6) return niceDomain(lo, hi, count);
|
|
1846
|
+
let step = tickStep(lo, hi, n);
|
|
1847
|
+
for (let k = 0; k < 40 && step > 0; k++) {
|
|
1848
|
+
const start = Math.floor(lo / step + 1e-9) * step;
|
|
1849
|
+
if (start + n * step >= hi - step * 1e-9) return [precise(start), precise(start + n * step)];
|
|
1850
|
+
step = nextStep(step);
|
|
2320
1851
|
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
1852
|
+
return niceDomain(lo, hi, count);
|
|
1853
|
+
};
|
|
1854
|
+
var mapped = /* @__PURE__ */ new WeakMap();
|
|
1855
|
+
/** Columns of a right-axis series in left axis units (cached with the data) */
|
|
1856
|
+
var mapColumns = (src, map) => {
|
|
1857
|
+
const key = `${map.scale},${map.offset},${!!map.log}`;
|
|
1858
|
+
const hit = mapped.get(src);
|
|
1859
|
+
if (hit && hit.key === key) return hit.cols;
|
|
1860
|
+
const f = mapper(map);
|
|
1861
|
+
const column = (a) => a ? a.map(f) : null;
|
|
1862
|
+
const raw = src;
|
|
1863
|
+
const cols = {
|
|
1864
|
+
...raw,
|
|
1865
|
+
y: raw.y.map(f),
|
|
1866
|
+
open: column(raw.open),
|
|
1867
|
+
high: column(raw.high),
|
|
1868
|
+
low: column(raw.low),
|
|
1869
|
+
y0: column(raw.y0),
|
|
1870
|
+
q1: column(raw.q1),
|
|
1871
|
+
q3: column(raw.q3),
|
|
1872
|
+
outliers: raw.outliers ? raw.outliers.map((list) => list?.map(f)) : null
|
|
1873
|
+
};
|
|
1874
|
+
if (map.log) {
|
|
1875
|
+
const range = [Infinity, -Infinity];
|
|
1876
|
+
const add = (a) => {
|
|
1877
|
+
if (!a) return;
|
|
1878
|
+
for (let i = 0; i < a.length; i++) {
|
|
1879
|
+
const v = a[i];
|
|
1880
|
+
if (!Number.isFinite(v)) continue;
|
|
1881
|
+
if (v < range[0]) range[0] = v;
|
|
1882
|
+
if (v > range[1]) range[1] = v;
|
|
2337
1883
|
}
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
1884
|
+
};
|
|
1885
|
+
for (const c of [
|
|
1886
|
+
cols.y,
|
|
1887
|
+
cols.y0,
|
|
1888
|
+
cols.high,
|
|
1889
|
+
cols.low,
|
|
1890
|
+
cols.q1,
|
|
1891
|
+
cols.q3
|
|
1892
|
+
]) add(c);
|
|
1893
|
+
cols.outliers?.forEach((list) => add(list));
|
|
1894
|
+
cols.yMin = range[0] <= range[1] ? range[0] : NaN;
|
|
1895
|
+
cols.yMax = range[0] <= range[1] ? range[1] : NaN;
|
|
1896
|
+
} else {
|
|
1897
|
+
const lo = f(raw.yMin);
|
|
1898
|
+
const hi = f(raw.yMax);
|
|
1899
|
+
cols.yMin = Math.min(lo, hi);
|
|
1900
|
+
cols.yMax = Math.max(lo, hi);
|
|
2350
1901
|
}
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
1902
|
+
mapped.set(src, {
|
|
1903
|
+
key,
|
|
1904
|
+
cols
|
|
1905
|
+
});
|
|
1906
|
+
return cols;
|
|
1907
|
+
};
|
|
1908
|
+
/** A log range widened to whole decades (`[0.3, 2.4]` -> `[0, 3]`) */
|
|
1909
|
+
var decades = (range) => {
|
|
1910
|
+
const lo = Math.floor(range[0] + 1e-9);
|
|
1911
|
+
const hi = Math.ceil(range[1] - 1e-9);
|
|
1912
|
+
return [lo, hi > lo ? hi : lo + 1];
|
|
1913
|
+
};
|
|
1914
|
+
/** Exponent of `m × 10^k`, exact for the values of the ticks */
|
|
1915
|
+
var niceExponent = (m, k) => Math.log10(Number((m * 10 ** k).toPrecision(12)));
|
|
1916
|
+
/**
|
|
1917
|
+
* A log range widened to the closest 1, 2 or 5 × 10^k bounds (the values
|
|
1918
|
+
* 900 - 140k -> 500 - 200k), tighter than whole decades
|
|
1919
|
+
*/
|
|
1920
|
+
var niceLogRange = (range) => {
|
|
1921
|
+
const below = (e) => {
|
|
1922
|
+
const k = Math.floor(e + 1e-9);
|
|
1923
|
+
let best = k;
|
|
1924
|
+
for (const m of [2, 5]) {
|
|
1925
|
+
const v = niceExponent(m, k);
|
|
1926
|
+
if (v <= e + 1e-9) best = v;
|
|
1927
|
+
}
|
|
1928
|
+
return best;
|
|
1929
|
+
};
|
|
1930
|
+
const above = (e) => {
|
|
1931
|
+
const k = Math.floor(e - 1e-9);
|
|
1932
|
+
for (const m of [
|
|
1933
|
+
1,
|
|
1934
|
+
2,
|
|
1935
|
+
5
|
|
1936
|
+
]) {
|
|
1937
|
+
const v = niceExponent(m, k);
|
|
1938
|
+
if (v >= e - 1e-9) return v;
|
|
2373
1939
|
}
|
|
1940
|
+
return k + 1;
|
|
1941
|
+
};
|
|
1942
|
+
const lo = below(range[0]);
|
|
1943
|
+
const hi = above(range[1]);
|
|
1944
|
+
return [lo, hi > lo + 1e-9 ? hi : above(lo + 1e-6)];
|
|
1945
|
+
};
|
|
1946
|
+
/**
|
|
1947
|
+
* Plots a series through a map: its columns, bars, stacks and lower
|
|
1948
|
+
* edges (bars / areas of the right axis start at `zero`)
|
|
1949
|
+
*/
|
|
1950
|
+
var logColumns = (s, map, zero = -Infinity) => {
|
|
1951
|
+
const f = mapper(map);
|
|
1952
|
+
const column = (a) => a ? a.map(f) : null;
|
|
1953
|
+
const rawBase = s.stackBase;
|
|
1954
|
+
s.cols = mapColumns(s.cols, map);
|
|
1955
|
+
s.barBase = s.barBase && !s.floating ? s.barBase.map((v) => v === 0 ? map.log ? -Infinity : f(0) : f(v)) : column(s.barBase);
|
|
1956
|
+
s.barTop = column(s.barTop);
|
|
1957
|
+
s.stackTop = column(s.stackTop);
|
|
1958
|
+
s.stackBase = rawBase && rawBase === s.raw.y0 ? s.cols.y0 : rawBase ? column(rawBase) : s.type === "area" ? new Float64Array(s.cols.count).fill(zero) : null;
|
|
1959
|
+
};
|
|
1960
|
+
/** Value -> plotted units of a map (`-Infinity` for 0 on log axes) */
|
|
1961
|
+
var mapper = (map) => (v) => (map.log ? Math.log10(v) : v) * map.scale + map.offset;
|
|
1962
|
+
/** Value of the y axis -> plotted y (the logarithm on log axes) */
|
|
1963
|
+
var toPlotY = (model, value) => model.y.type === "log" ? Math.log10(value) : value;
|
|
1964
|
+
/** Plotted y -> value of the y axis */
|
|
1965
|
+
var fromPlotY = (model, value) => model.y.type === "log" ? 10 ** value : value;
|
|
1966
|
+
/** Value of the x axis -> plotted x (the logarithm on log axes) */
|
|
1967
|
+
var toPlotX = (model, value) => model.x.type === "log" ? Math.log10(value) : value;
|
|
1968
|
+
/** Plotted x -> value of the x axis */
|
|
1969
|
+
var fromPlotX = (model, value) => model.x.type === "log" ? 10 ** value : value;
|
|
1970
|
+
/** A view in plotted units -> the values of the axes (zoom events, `getView`) */
|
|
1971
|
+
var viewToValues = (model, view) => ({
|
|
1972
|
+
x: [fromPlotX(model, view.x[0]), fromPlotX(model, view.x[1])],
|
|
1973
|
+
y: [fromPlotY(model, view.y[0]), fromPlotY(model, view.y[1])]
|
|
1974
|
+
});
|
|
1975
|
+
/** Values of the axes -> a view in plotted units (`setView`); missing axes keep `current` */
|
|
1976
|
+
var viewFromValues = (model, next, current) => ({
|
|
1977
|
+
x: next.x ? [toPlotX(model, next.x[0]), toPlotX(model, next.x[1])] : current.x,
|
|
1978
|
+
y: next.y ? [toPlotY(model, next.y[0]), toPlotY(model, next.y[1])] : current.y
|
|
1979
|
+
});
|
|
1980
|
+
var logXCache = /* @__PURE__ */ new WeakMap();
|
|
1981
|
+
/**
|
|
1982
|
+
* Columns plotted on a log x axis: the logarithms of the x values; points
|
|
1983
|
+
* without one (x <= 0) become gaps (cached with the data)
|
|
1984
|
+
*/
|
|
1985
|
+
var logXColumns = (raw) => {
|
|
1986
|
+
const hit = logXCache.get(raw);
|
|
1987
|
+
if (hit) return hit;
|
|
1988
|
+
const x = raw.x.map((v) => v > 0 ? Math.log10(v) : NaN);
|
|
1989
|
+
let y = raw.y;
|
|
1990
|
+
let gaps = false;
|
|
1991
|
+
for (let i = 0; i < raw.count; i++) {
|
|
1992
|
+
if (Number.isFinite(x[i])) continue;
|
|
1993
|
+
if (!gaps) y = Float64Array.from(raw.y);
|
|
1994
|
+
gaps = true;
|
|
1995
|
+
y[i] = NaN;
|
|
1996
|
+
}
|
|
1997
|
+
const [xMin, xMax] = extent(x);
|
|
1998
|
+
const cols = {
|
|
1999
|
+
...raw,
|
|
2000
|
+
x,
|
|
2001
|
+
y,
|
|
2002
|
+
xMin,
|
|
2003
|
+
xMax,
|
|
2004
|
+
sorted: raw.sorted && !gaps
|
|
2005
|
+
};
|
|
2006
|
+
logXCache.set(raw, cols);
|
|
2007
|
+
return cols;
|
|
2008
|
+
};
|
|
2009
|
+
/** Replaces the bases of bars / areas on log axes (`-Infinity`) by the bottom of the axis */
|
|
2010
|
+
var floorBases = (s, floor) => {
|
|
2011
|
+
for (const a of [s.barBase, s.stackBase]) {
|
|
2012
|
+
if (!a) continue;
|
|
2013
|
+
for (let i = 0; i < a.length; i++) if (a[i] === -Infinity) a[i] = floor;
|
|
2374
2014
|
}
|
|
2375
2015
|
};
|
|
2376
|
-
|
|
2377
|
-
var
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
ctx.fillStyle = color;
|
|
2385
|
-
ctx.fillText(text, x, y);
|
|
2016
|
+
/** Position (0-1) of a heatmap value on its color ramp */
|
|
2017
|
+
var heatPosition = (s, v) => {
|
|
2018
|
+
const [lo, hi] = s.valueRange;
|
|
2019
|
+
const mid = s.colorMidpoint;
|
|
2020
|
+
let t;
|
|
2021
|
+
if (mid !== null && mid > lo && mid < hi) t = v < mid ? (v - lo) / (mid - lo) * .5 : .5 + (v - mid) / (hi - mid) * .5;
|
|
2022
|
+
else t = (v - lo) / (hi - lo || 1);
|
|
2023
|
+
return t < 0 ? 0 : t > 1 ? 1 : t;
|
|
2386
2024
|
};
|
|
2387
2025
|
/**
|
|
2388
|
-
*
|
|
2389
|
-
*
|
|
2390
|
-
*
|
|
2026
|
+
* Built-in ramps of heatmaps, perceptual (OKLab) and theme-aware: light
|
|
2027
|
+
* themes go from a near-white tint to a deep hue, dark themes from a dark
|
|
2028
|
+
* tint of the surface to a bright saturated hue; diverging ramps meet in a
|
|
2029
|
+
* neutral middle
|
|
2391
2030
|
*/
|
|
2392
|
-
var
|
|
2393
|
-
const
|
|
2394
|
-
if (
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2031
|
+
var heatmapStops = (scheme, color, theme, negative) => {
|
|
2032
|
+
const dark = luminance(theme.background) < .4;
|
|
2033
|
+
if (scheme === "diverging") {
|
|
2034
|
+
const neutral = mixOklab(theme.surface, theme.textMuted, dark ? .12 : .08);
|
|
2035
|
+
const end = (c) => dark ? withLightness(c, .7, 1.25) : withLightness(c, .5, 1.05);
|
|
2036
|
+
const side = (c) => mixOklab(neutral, end(c), .5);
|
|
2037
|
+
return [
|
|
2038
|
+
end(negative),
|
|
2039
|
+
side(negative),
|
|
2040
|
+
neutral,
|
|
2041
|
+
side(color),
|
|
2042
|
+
end(color)
|
|
2043
|
+
];
|
|
2044
|
+
}
|
|
2045
|
+
return dark ? [
|
|
2046
|
+
mixOklab(theme.surface, color, .14),
|
|
2047
|
+
withLightness(color, .46, .95),
|
|
2048
|
+
withLightness(color, .64, 1.2),
|
|
2049
|
+
withLightness(color, .8, 1.15)
|
|
2050
|
+
] : [
|
|
2051
|
+
mixOklab(theme.surface, color, .06),
|
|
2052
|
+
withLightness(color, .8, .6),
|
|
2053
|
+
withLightness(color, .58, 1),
|
|
2054
|
+
withLightness(color, .36, .9)
|
|
2055
|
+
];
|
|
2056
|
+
};
|
|
2057
|
+
var buildModel = (options, theme, hidden, cache, resolver = null) => {
|
|
2058
|
+
const resolve = resolver ?? theme.resolve ?? ((value, fallback) => resolveStaticColor(value, {}, fallback));
|
|
2059
|
+
const input = options.series;
|
|
2060
|
+
const locale = resolveLocale(options.locale);
|
|
2061
|
+
const tooltipOptions = typeof options.tooltip === "object" ? options.tooltip : void 0;
|
|
2062
|
+
const labels = chartLabels(locale, {
|
|
2063
|
+
...definedEntries(tooltipOptions?.labels),
|
|
2064
|
+
...definedEntries(options.labels)
|
|
2065
|
+
});
|
|
2066
|
+
const datas = input.map((s) => s.data);
|
|
2067
|
+
const xType = options.xAxis?.type ?? detectXType(datas);
|
|
2068
|
+
const categories = xType === "category" ? [...options.xAxis?.categories ?? collectCategories(datas)] : [];
|
|
2069
|
+
const categoryMap = new Map(categories.map((c, i) => [c, i]));
|
|
2070
|
+
const resolveX = xResolver(xType, (name) => categoryMap.get(name) ?? NaN);
|
|
2071
|
+
const yOpts = options.yAxis;
|
|
2072
|
+
const heatmapData = input.filter((s) => s.type === "heatmap").map((s) => s.data);
|
|
2073
|
+
const yCategories = yOpts?.type === "category" || !yOpts?.type && heatmapData.length ? [...yOpts?.categories ?? collectYCategories(heatmapData)] : [];
|
|
2074
|
+
const yType = yOpts?.type === "category" || yCategories.length ? "category" : yOpts?.type === "time" || !yOpts?.type && hasDateY(datas) ? "time" : yOpts?.type === "log" ? "log" : "linear";
|
|
2075
|
+
const yLog = yType === "log";
|
|
2076
|
+
const yCategoryMap = new Map(yCategories.map((c, i) => [c, i]));
|
|
2077
|
+
const resolveY = (v) => typeof v === "number" ? v : typeof v === "string" ? yType === "category" ? yCategoryMap.get(v) ?? NaN : yType === "time" ? Date.parse(v) : Number(v) : v instanceof Date ? v.getTime() : NaN;
|
|
2078
|
+
const resolverKey = `${xType}|${categories.join("")}|${yType}|${yCategories.join("")}`;
|
|
2079
|
+
const percent = options.stackMode === "percent";
|
|
2080
|
+
const horizontal = options.orientation === "horizontal";
|
|
2081
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
2082
|
+
const series = input.map((s, index) => {
|
|
2083
|
+
let key = s.id ?? `#${index}`;
|
|
2084
|
+
while (usedKeys.has(key)) key += "'";
|
|
2085
|
+
usedKeys.add(key);
|
|
2086
|
+
let entry = cache.get(key);
|
|
2087
|
+
if (!entry || entry.data !== s.data || entry.resolverKey !== resolverKey) {
|
|
2088
|
+
entry = {
|
|
2089
|
+
data: s.data,
|
|
2090
|
+
resolverKey,
|
|
2091
|
+
cols: normalizeData(s.data, resolveX, resolveY)
|
|
2092
|
+
};
|
|
2093
|
+
cache.set(key, entry);
|
|
2094
|
+
}
|
|
2095
|
+
const type = s.type ?? "line";
|
|
2096
|
+
const paletteColor = theme.palette[index % theme.palette.length];
|
|
2097
|
+
const color = s.color ? resolve(s.color, paletteColor) : paletteColor;
|
|
2098
|
+
const cols = entry.cols;
|
|
2099
|
+
let pointColors = null;
|
|
2100
|
+
if ((type === "scatter" || type === "bar" || type === "boxplot") && cols.colors) {
|
|
2101
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
2102
|
+
pointColors = cols.colors.map((c) => {
|
|
2103
|
+
if (c === void 0) return void 0;
|
|
2104
|
+
let rgba = resolved.get(c);
|
|
2105
|
+
if (!rgba) resolved.set(c, rgba = resolve(c, color));
|
|
2106
|
+
return rgba;
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
let colorScale = [];
|
|
2110
|
+
let valueRange = [0, 1];
|
|
2111
|
+
let cellColors = null;
|
|
2112
|
+
let colorMidpoint = null;
|
|
2113
|
+
let colorLut = null;
|
|
2114
|
+
if (type === "heatmap") {
|
|
2115
|
+
const scale = s.colorScale;
|
|
2116
|
+
const scheme = scale === "diverging" ? "diverging" : "sequential";
|
|
2117
|
+
colorScale = typeof scale === "object" && scale.length ? scale.map((c) => resolve(c, color)) : heatmapStops(scheme, color, theme, resolve(DEFAULT_DOWN, [
|
|
2118
|
+
.86,
|
|
2119
|
+
.15,
|
|
2120
|
+
.15,
|
|
2121
|
+
1
|
|
2122
|
+
]));
|
|
2123
|
+
valueRange = s.valueRange ? [s.valueRange[0], s.valueRange[1]] : [cols.valueMin, cols.valueMax];
|
|
2124
|
+
if (!Number.isFinite(valueRange[0])) valueRange = [0, 1];
|
|
2125
|
+
const [lo, hi] = valueRange;
|
|
2126
|
+
colorMidpoint = s.colorMidpoint ?? (scheme === "diverging" && typeof scale !== "object" ? lo < 0 && hi > 0 ? 0 : (lo + hi) / 2 : null);
|
|
2127
|
+
const lut = oklabRamp(colorScale);
|
|
2128
|
+
colorLut = lut;
|
|
2129
|
+
const values = cols.value ?? cols.y;
|
|
2130
|
+
const ramp = {
|
|
2131
|
+
valueRange,
|
|
2132
|
+
colorMidpoint
|
|
2133
|
+
};
|
|
2134
|
+
cellColors = new Uint32Array(cols.count);
|
|
2135
|
+
for (let i = 0; i < cols.count; i++) {
|
|
2136
|
+
const v = values[i];
|
|
2137
|
+
if (!Number.isFinite(v) || !Number.isFinite(cols.y[i])) continue;
|
|
2138
|
+
cellColors[i] = lut[Math.round(heatPosition(ramp, v) * 255)];
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
const connector = s.connector ? typeof s.connector === "object" ? s.connector : {} : null;
|
|
2142
|
+
const id = s.id ?? String(index);
|
|
2143
|
+
return {
|
|
2144
|
+
index,
|
|
2145
|
+
key,
|
|
2146
|
+
id,
|
|
2147
|
+
name: s.name ?? `${labels.series} ${index + 1}`,
|
|
2148
|
+
type,
|
|
2149
|
+
color,
|
|
2150
|
+
colorCss: toCss(color),
|
|
2151
|
+
lineWidth: s.lineWidth ?? (type === "candlestick" ? 1 : type === "boxplot" ? 1.5 : 2),
|
|
2152
|
+
pointSize: s.pointSize ?? (type === "boxplot" ? 6 : 8),
|
|
2153
|
+
opacity: s.opacity ?? (type === "area" ? .28 : type === "scatter" ? .78 : type === "boxplot" ? .22 : 1),
|
|
2154
|
+
hidden: !!s.hidden || hidden.has(id),
|
|
2155
|
+
showLabels: !!s.showLabels,
|
|
2156
|
+
labelsInside: s.showLabels === "inside" && type === "bar",
|
|
2157
|
+
curve: s.curve ?? "linear",
|
|
2158
|
+
cols,
|
|
2159
|
+
raw: cols,
|
|
2160
|
+
yAxis: s.yAxis === "right" && !horizontal ? "right" : "left",
|
|
2161
|
+
floating: type === "bar" && !!cols.y0,
|
|
2162
|
+
barOffset: 0,
|
|
2163
|
+
barHalfWidth: 0,
|
|
2164
|
+
barBase: null,
|
|
2165
|
+
barTop: null,
|
|
2166
|
+
stackBase: null,
|
|
2167
|
+
stackTop: null,
|
|
2168
|
+
pointColors,
|
|
2169
|
+
upColor: type === "candlestick" ? resolve(s.upColor ?? DEFAULT_UP, [
|
|
2170
|
+
.09,
|
|
2171
|
+
.64,
|
|
2172
|
+
.29,
|
|
2173
|
+
1
|
|
2174
|
+
]) : color,
|
|
2175
|
+
downColor: type === "candlestick" ? resolve(s.downColor ?? DEFAULT_DOWN, [
|
|
2176
|
+
.86,
|
|
2177
|
+
.15,
|
|
2178
|
+
.15,
|
|
2179
|
+
1
|
|
2180
|
+
]) : color,
|
|
2181
|
+
cellColors,
|
|
2182
|
+
cellHalf: {
|
|
2183
|
+
x: .5,
|
|
2184
|
+
y: .5
|
|
2185
|
+
},
|
|
2186
|
+
colorScale,
|
|
2187
|
+
valueRange,
|
|
2188
|
+
colorMidpoint,
|
|
2189
|
+
colorLut,
|
|
2190
|
+
tooltip: s.tooltip !== false,
|
|
2191
|
+
stroke: s.stroke !== void 0 && Number.isFinite(s.stroke) ? Math.max(0, s.stroke) : null,
|
|
2192
|
+
outlierShape: s.outlierShape ?? "hollow",
|
|
2193
|
+
outlierSize: s.outlierSize ?? s.pointSize ?? 7,
|
|
2194
|
+
connector: connector && type === "bar" ? {
|
|
2195
|
+
color: connector.color ? resolve(connector.color, theme.textMuted) : theme.textMuted,
|
|
2196
|
+
dash: [...connector.dash ?? [3, 3]],
|
|
2197
|
+
width: connector.width ?? 1
|
|
2198
|
+
} : null,
|
|
2199
|
+
radius: s.radius ?? (type === "heatmap" ? 2 : type === "boxplot" ? 0 : 3),
|
|
2200
|
+
format: s.format,
|
|
2201
|
+
valueName: s.valueName
|
|
2202
|
+
};
|
|
2203
|
+
});
|
|
2204
|
+
const visible = series.filter((s) => !s.hidden);
|
|
2205
|
+
const xLog = xType === "log";
|
|
2206
|
+
if (xLog) for (const s of series) s.cols = logXColumns(s.raw);
|
|
2207
|
+
const allScatter = series.length > 0 && series.every((s) => s.type === "scatter" || s.type === "heatmap");
|
|
2208
|
+
const slotted = visible.filter((s) => s.type === "bar" || s.type === "boxplot");
|
|
2209
|
+
const bars = visible.filter((s) => s.type === "bar");
|
|
2210
|
+
const band = bandOf(slotted, "x", xType === "category");
|
|
2211
|
+
if (slotted.length) {
|
|
2212
|
+
const slots = /* @__PURE__ */ new Map();
|
|
2213
|
+
const stackId = (s) => input[s.index].stack ?? `\u0000${s.key}`;
|
|
2214
|
+
for (const s of slotted) if (!slots.has(stackId(s))) slots.set(stackId(s), slots.size);
|
|
2215
|
+
let share = 0;
|
|
2216
|
+
for (const s of slotted) {
|
|
2217
|
+
const w = input[s.index].barWidth;
|
|
2218
|
+
if (w !== void 0 && w > share) share = Math.min(1, w);
|
|
2414
2219
|
}
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
if (label.placement === "inside") {
|
|
2422
|
-
const fit = insideBox(label, plot, measure, height);
|
|
2423
|
-
if (fit) placed.push(fit);
|
|
2424
|
-
continue;
|
|
2220
|
+
const group = band * (share || (slots.size > 1 ? .78 : .62));
|
|
2221
|
+
const slotWidth = group / slots.size;
|
|
2222
|
+
for (const s of slotted) {
|
|
2223
|
+
const slot = slots.get(stackId(s)) ?? 0;
|
|
2224
|
+
s.barOffset = -group / 2 + slotWidth * (slot + .5);
|
|
2225
|
+
s.barHalfWidth = slotWidth / 2 * (slots.size > 1 ? .86 : 1);
|
|
2425
2226
|
}
|
|
2426
|
-
const
|
|
2427
|
-
const
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
const right = {
|
|
2441
|
-
x: label.x + gap,
|
|
2442
|
-
y: label.y - height / 2,
|
|
2443
|
-
width: w,
|
|
2444
|
-
height
|
|
2445
|
-
};
|
|
2446
|
-
const left = {
|
|
2447
|
-
x: label.x - gap - w,
|
|
2448
|
-
y: label.y - height / 2,
|
|
2449
|
-
width: w,
|
|
2450
|
-
height
|
|
2451
|
-
};
|
|
2452
|
-
const box = (label.placement === "above" ? [above] : label.placement === "below" ? [below] : label.placement === "right" ? [right, left] : label.placement === "left" ? [left, right] : [
|
|
2453
|
-
{
|
|
2454
|
-
x: label.x + gap,
|
|
2455
|
-
y: label.y - height / 2,
|
|
2456
|
-
width: w,
|
|
2457
|
-
height
|
|
2458
|
-
},
|
|
2459
|
-
{
|
|
2460
|
-
x: label.x - gap - w,
|
|
2461
|
-
y: label.y - height / 2,
|
|
2462
|
-
width: w,
|
|
2463
|
-
height
|
|
2464
|
-
},
|
|
2465
|
-
above,
|
|
2466
|
-
below
|
|
2467
|
-
]).find((c) => inside$1(c, plot) && !occupied.some((o) => overlaps(o, c)));
|
|
2468
|
-
if (!box) continue;
|
|
2469
|
-
occupied.push(box);
|
|
2470
|
-
placed.push({
|
|
2471
|
-
label,
|
|
2472
|
-
box
|
|
2227
|
+
const stackedBars = bars.filter((s) => !s.floating);
|
|
2228
|
+
const stacked = stackValues(stackedBars.map((s) => ({
|
|
2229
|
+
s,
|
|
2230
|
+
stack: stackId(s)
|
|
2231
|
+
})), false);
|
|
2232
|
+
const percentStacked = percent ? stackValues(stackedBars.filter((s) => input[s.index].stack !== void 0).map((s) => ({
|
|
2233
|
+
s,
|
|
2234
|
+
stack: stackId(s)
|
|
2235
|
+
})), true) : [];
|
|
2236
|
+
let p = 0;
|
|
2237
|
+
stackedBars.forEach((s, k) => {
|
|
2238
|
+
const values = percent && input[s.index].stack !== void 0 ? percentStacked[p++] : stacked[k];
|
|
2239
|
+
s.barBase = values.base;
|
|
2240
|
+
s.barTop = values.top;
|
|
2473
2241
|
});
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
ctx.lineJoin = "round";
|
|
2487
|
-
ctx.setLineDash([]);
|
|
2488
|
-
ctx.beginPath();
|
|
2489
|
-
ctx.moveTo(first.x, first.y);
|
|
2490
|
-
for (let i = 1; i < pts.length; i++) {
|
|
2491
|
-
const p = pts[i];
|
|
2492
|
-
const next = pts[i + 1];
|
|
2493
|
-
const prev = pts[i - 1];
|
|
2494
|
-
if (next && typeof ctx.arcTo === "function") {
|
|
2495
|
-
const r = Math.min(4, Math.hypot(p.x - prev.x, p.y - prev.y) / 2, Math.hypot(next.x - p.x, next.y - p.y) / 2);
|
|
2496
|
-
ctx.arcTo(p.x, p.y, next.x, next.y, r);
|
|
2497
|
-
} else ctx.lineTo(p.x, p.y);
|
|
2498
|
-
}
|
|
2499
|
-
ctx.stroke();
|
|
2500
|
-
const [tip, b0, b1] = a.head;
|
|
2501
|
-
if (tip && b0 && b1) {
|
|
2502
|
-
ctx.beginPath();
|
|
2503
|
-
ctx.moveTo(tip.x, tip.y);
|
|
2504
|
-
ctx.lineTo(b0.x, b0.y);
|
|
2505
|
-
ctx.lineTo(b1.x, b1.y);
|
|
2506
|
-
ctx.closePath();
|
|
2507
|
-
ctx.fill();
|
|
2242
|
+
for (const s of bars) {
|
|
2243
|
+
const { y, y0, count } = s.cols;
|
|
2244
|
+
if (!s.floating || !y0) continue;
|
|
2245
|
+
const base = new Float64Array(count);
|
|
2246
|
+
const top = new Float64Array(count);
|
|
2247
|
+
for (let i = 0; i < count; i++) {
|
|
2248
|
+
const valid = Number.isFinite(y[i]);
|
|
2249
|
+
base[i] = valid ? Number.isFinite(y0[i]) ? y0[i] : 0 : NaN;
|
|
2250
|
+
top[i] = valid ? y[i] : NaN;
|
|
2251
|
+
}
|
|
2252
|
+
s.barBase = base;
|
|
2253
|
+
s.barTop = top;
|
|
2508
2254
|
}
|
|
2509
2255
|
}
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
ctx.lineWidth = c.width;
|
|
2522
|
-
ctx.setLineDash([...c.dash]);
|
|
2523
|
-
ctx.beginPath();
|
|
2524
|
-
const along = c.y0 === c.y1;
|
|
2525
|
-
const x0 = along ? c.x0 : crisp(c.x0);
|
|
2526
|
-
const y0 = along ? crisp(c.y0) : c.y0;
|
|
2527
|
-
ctx.moveTo(x0, y0);
|
|
2528
|
-
ctx.lineTo(along ? c.x1 : x0, along ? y0 : c.y1);
|
|
2529
|
-
ctx.stroke();
|
|
2256
|
+
for (const s of visible) if (s.type === "area" && s.cols.y0 && input[s.index].stack === void 0) s.stackBase = s.cols.y0;
|
|
2257
|
+
const stackedLines = visible.filter((s) => (s.type === "line" || s.type === "area") && input[s.index].stack !== void 0);
|
|
2258
|
+
if (stackedLines.length) {
|
|
2259
|
+
const values = stackValues(stackedLines.map((s) => ({
|
|
2260
|
+
s,
|
|
2261
|
+
stack: input[s.index].stack ?? ""
|
|
2262
|
+
})), percent);
|
|
2263
|
+
stackedLines.forEach((s, k) => {
|
|
2264
|
+
s.stackBase = values[k].base;
|
|
2265
|
+
s.stackTop = values[k].top;
|
|
2266
|
+
});
|
|
2530
2267
|
}
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
const
|
|
2534
|
-
const
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
const
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
ctx.beginPath();
|
|
2543
|
-
const where = lineOf(a, scales);
|
|
2544
|
-
if (!where.vertical) {
|
|
2545
|
-
const py = crisp(where.at);
|
|
2546
|
-
ctx.moveTo(plot.x, py);
|
|
2547
|
-
ctx.lineTo(plot.x + plot.width, py);
|
|
2548
|
-
ctx.stroke();
|
|
2549
|
-
if (a.label) {
|
|
2550
|
-
ctx.setLineDash([]);
|
|
2551
|
-
ctx.font = font(theme, theme.fontSize, 500);
|
|
2552
|
-
ctx.textAlign = "right";
|
|
2553
|
-
ctx.textBaseline = "bottom";
|
|
2554
|
-
haloText(ctx, a.label, plot.x + plot.width - 6, py - 3, color, halo);
|
|
2555
|
-
}
|
|
2556
|
-
} else {
|
|
2557
|
-
const px = crisp(where.at);
|
|
2558
|
-
ctx.moveTo(px, plot.y);
|
|
2559
|
-
ctx.lineTo(px, plot.y + plot.height);
|
|
2560
|
-
ctx.stroke();
|
|
2561
|
-
if (a.label) {
|
|
2562
|
-
ctx.setLineDash([]);
|
|
2563
|
-
ctx.font = font(theme, theme.fontSize, 500);
|
|
2564
|
-
ctx.textAlign = "left";
|
|
2565
|
-
ctx.textBaseline = "top";
|
|
2566
|
-
haloText(ctx, a.label, px + 5, plot.y + 5, color, halo);
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2268
|
+
const candles = visible.filter((s) => s.type === "candlestick");
|
|
2269
|
+
const candleBand = bandOf(candles, "x", xType === "category");
|
|
2270
|
+
for (const s of candles) s.barHalfWidth = candleBand * .3;
|
|
2271
|
+
const heatmaps = visible.filter((s) => s.type === "heatmap");
|
|
2272
|
+
if (heatmaps.length) {
|
|
2273
|
+
const cx = bandOf(heatmaps, "x", xType === "category") / 2;
|
|
2274
|
+
const cy = bandOf(heatmaps, "y", yType === "category") / 2;
|
|
2275
|
+
for (const s of heatmaps) s.cellHalf = {
|
|
2276
|
+
x: cx,
|
|
2277
|
+
y: cy
|
|
2278
|
+
};
|
|
2569
2279
|
}
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
const muted = toCss(theme.textMuted);
|
|
2580
|
-
ctx.textAlign = "left";
|
|
2581
|
-
ctx.textBaseline = "middle";
|
|
2582
|
-
for (const p of title.parts) {
|
|
2583
|
-
const cy = p.y + p.height / 2;
|
|
2584
|
-
if (p.kind === "badge") {
|
|
2585
|
-
const h = p.height - 2;
|
|
2586
|
-
pill(ctx, p.x, cy - h / 2, p.width, h);
|
|
2587
|
-
ctx.fillStyle = toCss(a.labelColor, .16);
|
|
2588
|
-
ctx.fill();
|
|
2589
|
-
ctx.font = font(theme, p.size, p.weight);
|
|
2590
|
-
ctx.fillStyle = color;
|
|
2591
|
-
ctx.fillText(p.text, p.x + 5, cy);
|
|
2592
|
-
continue;
|
|
2593
|
-
}
|
|
2594
|
-
ctx.font = font(theme, p.size, p.weight);
|
|
2595
|
-
ctx.fillStyle = p.kind === "label" ? color : muted;
|
|
2596
|
-
ctx.fillText(p.text, p.x, cy);
|
|
2280
|
+
const xOpts = options.xAxis;
|
|
2281
|
+
let xRange;
|
|
2282
|
+
if (xType === "category") xRange = [-.5, Math.max(1, categories.length) - .5];
|
|
2283
|
+
else {
|
|
2284
|
+
const values = [];
|
|
2285
|
+
for (const s of visible) {
|
|
2286
|
+
values.push(s.cols.xMin, s.cols.xMax);
|
|
2287
|
+
const pad = s.type === "bar" || s.type === "boxplot" ? band / 2 : s.type === "candlestick" ? candleBand / 2 : s.type === "heatmap" ? s.cellHalf.x : 0;
|
|
2288
|
+
if (pad) values.push(s.cols.xMin - pad, s.cols.xMax + pad);
|
|
2597
2289
|
}
|
|
2598
|
-
|
|
2290
|
+
xRange = ensureSpan(...extent(values), xType === "time" ? [Date.now() - 864e5, Date.now()] : [0, 1]);
|
|
2291
|
+
const pointsOnly = series.every((s) => s.type === "scatter");
|
|
2292
|
+
if (pointsOnly) {
|
|
2293
|
+
const pad = (xRange[1] - xRange[0]) * .04;
|
|
2294
|
+
xRange = [xRange[0] - pad, xRange[1] + pad];
|
|
2295
|
+
}
|
|
2296
|
+
if ((xOpts?.nice ?? (pointsOnly && xType === "linear")) && xType === "linear") xRange = niceDomain(xRange[0], xRange[1], xOpts?.tickCount ?? 6);
|
|
2297
|
+
if (xLog && xOpts?.nice !== false) xRange = niceLogRange(xRange);
|
|
2599
2298
|
}
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
occupied.push(box);
|
|
2299
|
+
const xBound = (v) => xLog ? Math.log10(v) : v;
|
|
2300
|
+
if (xOpts?.min !== void 0) xRange[0] = xBound(xOpts.min);
|
|
2301
|
+
if (xOpts?.max !== void 0) xRange[1] = xBound(xOpts.max);
|
|
2302
|
+
xRange = ensureSpan(xRange[0], xRange[1]);
|
|
2303
|
+
if (yLog) for (const s of series) {
|
|
2304
|
+
if (s.yAxis === "right" || s.type === "heatmap") continue;
|
|
2305
|
+
logColumns(s, LOG_MAP);
|
|
2608
2306
|
}
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2307
|
+
const valuesOf = (list, zero, log = false) => {
|
|
2308
|
+
const values = [];
|
|
2309
|
+
for (const s of list) if (s.type === "bar" && s.barBase && s.barTop) values.push(...extent(s.barBase), ...extent(s.barTop));
|
|
2310
|
+
else if (s.stackBase && s.stackTop) values.push(...extent(s.stackBase), ...extent(s.stackTop));
|
|
2311
|
+
else if (s.type === "heatmap") values.push(s.cols.yMin - s.cellHalf.y, s.cols.yMax + s.cellHalf.y);
|
|
2312
|
+
else values.push(s.cols.yMin, s.cols.yMax);
|
|
2313
|
+
const listBars = list.filter((s) => s.type === "bar");
|
|
2314
|
+
if (!log && (listBars.some((s) => !s.floating) || zero)) values.push(0);
|
|
2315
|
+
if (listBars.some((s) => s.showLabels && !s.labelsInside)) {
|
|
2316
|
+
const [lo, hi] = extent(values);
|
|
2317
|
+
if (hi > 0) values.push(hi + (hi - Math.min(0, lo)) * .1);
|
|
2318
|
+
if (lo < 0) values.push(lo - (Math.max(0, hi) - lo) * .1);
|
|
2319
|
+
}
|
|
2320
|
+
return values;
|
|
2321
|
+
};
|
|
2322
|
+
const leftVisible = visible.filter((s) => s.yAxis === "left");
|
|
2323
|
+
const leftSeries = leftVisible.length ? leftVisible : visible;
|
|
2324
|
+
const leftBars = leftSeries.filter((s) => s.type === "bar");
|
|
2325
|
+
let yRange;
|
|
2326
|
+
if (yType === "category") yRange = [-.5, Math.max(1, yCategories.length) - .5];
|
|
2327
|
+
else {
|
|
2328
|
+
const yValues = valuesOf(leftSeries, yOpts?.zero, yLog);
|
|
2329
|
+
const filled = leftBars.length > 0 || leftSeries.some((s) => s.type === "area");
|
|
2330
|
+
yRange = ensureSpan(...extent(yValues));
|
|
2331
|
+
if (series.every((s) => s.type === "scatter")) {
|
|
2332
|
+
const pad = (yRange[1] - yRange[0]) * .06;
|
|
2333
|
+
yRange = [yRange[0] - pad, yRange[1] + pad];
|
|
2334
|
+
} else if ((candles.length || leftSeries.some((s) => s.type === "boxplot")) && !leftBars.length) {
|
|
2335
|
+
const pad = (yRange[1] - yRange[0]) * .04;
|
|
2336
|
+
yRange = [yRange[0] - pad, yRange[1] + pad];
|
|
2337
|
+
}
|
|
2338
|
+
if ((yOpts?.nice ?? !heatmaps.length) && yType === "linear") yRange = niceDomain(yRange[0], yRange[1], yOpts?.tickCount ?? 5);
|
|
2339
|
+
if (yLog && yOpts?.nice !== false) yRange = decades(yRange);
|
|
2340
|
+
if (percent && (bars.length || stackedLines.length)) yRange = [extent(yValues)[0] < 0 ? -100 : 0, 100];
|
|
2341
|
+
if (filled && !yOpts?.zero && !yLog) {
|
|
2342
|
+
if (yRange[0] < 0 && extent(yValues)[0] >= 0) yRange[0] = 0;
|
|
2621
2343
|
}
|
|
2622
2344
|
}
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
var HOUR = 60 * MINUTE;
|
|
2630
|
-
var DAY = 24 * HOUR;
|
|
2631
|
-
var MONTH = 30 * DAY;
|
|
2632
|
-
var YEAR = 365 * DAY;
|
|
2633
|
-
var INTERVALS = [
|
|
2634
|
-
{
|
|
2635
|
-
unit: "second",
|
|
2636
|
-
step: 1,
|
|
2637
|
-
ms: SECOND
|
|
2638
|
-
},
|
|
2639
|
-
{
|
|
2640
|
-
unit: "second",
|
|
2641
|
-
step: 5,
|
|
2642
|
-
ms: 5 * SECOND
|
|
2643
|
-
},
|
|
2644
|
-
{
|
|
2645
|
-
unit: "second",
|
|
2646
|
-
step: 15,
|
|
2647
|
-
ms: 15 * SECOND
|
|
2648
|
-
},
|
|
2649
|
-
{
|
|
2650
|
-
unit: "second",
|
|
2651
|
-
step: 30,
|
|
2652
|
-
ms: 30 * SECOND
|
|
2653
|
-
},
|
|
2654
|
-
{
|
|
2655
|
-
unit: "minute",
|
|
2656
|
-
step: 1,
|
|
2657
|
-
ms: MINUTE
|
|
2658
|
-
},
|
|
2659
|
-
{
|
|
2660
|
-
unit: "minute",
|
|
2661
|
-
step: 5,
|
|
2662
|
-
ms: 5 * MINUTE
|
|
2663
|
-
},
|
|
2664
|
-
{
|
|
2665
|
-
unit: "minute",
|
|
2666
|
-
step: 15,
|
|
2667
|
-
ms: 15 * MINUTE
|
|
2668
|
-
},
|
|
2669
|
-
{
|
|
2670
|
-
unit: "minute",
|
|
2671
|
-
step: 30,
|
|
2672
|
-
ms: 30 * MINUTE
|
|
2673
|
-
},
|
|
2674
|
-
{
|
|
2675
|
-
unit: "hour",
|
|
2676
|
-
step: 1,
|
|
2677
|
-
ms: HOUR
|
|
2678
|
-
},
|
|
2679
|
-
{
|
|
2680
|
-
unit: "hour",
|
|
2681
|
-
step: 2,
|
|
2682
|
-
ms: 2 * HOUR
|
|
2683
|
-
},
|
|
2684
|
-
{
|
|
2685
|
-
unit: "hour",
|
|
2686
|
-
step: 3,
|
|
2687
|
-
ms: 3 * HOUR
|
|
2688
|
-
},
|
|
2689
|
-
{
|
|
2690
|
-
unit: "hour",
|
|
2691
|
-
step: 6,
|
|
2692
|
-
ms: 6 * HOUR
|
|
2693
|
-
},
|
|
2694
|
-
{
|
|
2695
|
-
unit: "hour",
|
|
2696
|
-
step: 12,
|
|
2697
|
-
ms: 12 * HOUR
|
|
2698
|
-
},
|
|
2699
|
-
{
|
|
2700
|
-
unit: "day",
|
|
2701
|
-
step: 1,
|
|
2702
|
-
ms: DAY
|
|
2703
|
-
},
|
|
2704
|
-
{
|
|
2705
|
-
unit: "day",
|
|
2706
|
-
step: 2,
|
|
2707
|
-
ms: 2 * DAY
|
|
2708
|
-
},
|
|
2709
|
-
{
|
|
2710
|
-
unit: "day",
|
|
2711
|
-
step: 7,
|
|
2712
|
-
ms: 7 * DAY
|
|
2713
|
-
},
|
|
2714
|
-
{
|
|
2715
|
-
unit: "month",
|
|
2716
|
-
step: 1,
|
|
2717
|
-
ms: MONTH
|
|
2718
|
-
},
|
|
2719
|
-
{
|
|
2720
|
-
unit: "month",
|
|
2721
|
-
step: 3,
|
|
2722
|
-
ms: 3 * MONTH
|
|
2723
|
-
},
|
|
2724
|
-
{
|
|
2725
|
-
unit: "month",
|
|
2726
|
-
step: 6,
|
|
2727
|
-
ms: 6 * MONTH
|
|
2345
|
+
const yBound = (v) => yLog ? Math.log10(v) : v;
|
|
2346
|
+
if (yOpts?.min !== void 0) yRange[0] = yBound(yOpts.min);
|
|
2347
|
+
if (yOpts?.max !== void 0) yRange[1] = yBound(yOpts.max);
|
|
2348
|
+
yRange = ensureSpan(yRange[0], yRange[1]);
|
|
2349
|
+
if (yLog) {
|
|
2350
|
+
for (const s of series) if (s.yAxis === "left") floorBases(s, yRange[0]);
|
|
2728
2351
|
}
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
const
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2352
|
+
const y2Opts = options.yAxis2;
|
|
2353
|
+
let y2 = null;
|
|
2354
|
+
let y2Map = null;
|
|
2355
|
+
const rightSeries = series.filter((s) => s.yAxis === "right");
|
|
2356
|
+
if (rightSeries.length && yType !== "category") {
|
|
2357
|
+
const y2Log = y2Opts?.type === "log";
|
|
2358
|
+
const rightVisible = rightSeries.filter((s) => !s.hidden);
|
|
2359
|
+
const [lo, hi] = ensureSpan(...extent(y2Log ? rightVisible.flatMap((s) => {
|
|
2360
|
+
const c = mapColumns(s.raw, LOG_MAP);
|
|
2361
|
+
return [c.yMin, c.yMax];
|
|
2362
|
+
}) : valuesOf(rightVisible, y2Opts?.zero)));
|
|
2363
|
+
let domain = y2Opts?.nice === false ? [lo, hi] : y2Log ? decades([lo, hi]) : alignedDomain(lo, hi, yRange, yOpts?.tickCount ?? 5);
|
|
2364
|
+
const y2Bound = (v) => y2Log ? Math.log10(v) : v;
|
|
2365
|
+
if (y2Opts?.min !== void 0) domain[0] = y2Bound(y2Opts.min);
|
|
2366
|
+
if (y2Opts?.max !== void 0) domain[1] = y2Bound(y2Opts.max);
|
|
2367
|
+
domain = ensureSpan(domain[0], domain[1]);
|
|
2368
|
+
const scale = (yRange[1] - yRange[0]) / (domain[1] - domain[0]);
|
|
2369
|
+
const map = {
|
|
2370
|
+
scale,
|
|
2371
|
+
offset: yRange[0] - domain[0] * scale,
|
|
2372
|
+
log: y2Log
|
|
2373
|
+
};
|
|
2374
|
+
y2Map = map;
|
|
2375
|
+
const zero = y2Log ? yRange[0] : mapper(map)(Math.min(Math.max(0, domain[0]), domain[1]));
|
|
2376
|
+
for (const s of rightSeries) {
|
|
2377
|
+
logColumns(s, map, zero);
|
|
2378
|
+
floorBases(s, yRange[0]);
|
|
2379
|
+
}
|
|
2380
|
+
y2 = axisModel(y2Opts, y2Log ? "log" : "linear", [], false, locale);
|
|
2381
|
+
}
|
|
2382
|
+
const timeline = yType === "time" && series.some((s) => s.type === "bar") && series.every((s) => s.type === "bar" ? s.floating : s.type === "scatter");
|
|
2383
|
+
const zoomOption = options.interaction === false ? false : options.interaction === true || options.interaction === void 0 ? true : options.interaction.zoom ?? true;
|
|
2384
|
+
const zoomAxes = zoomOption === false ? {
|
|
2385
|
+
x: false,
|
|
2386
|
+
y: false
|
|
2387
|
+
} : zoomOption === true ? {
|
|
2388
|
+
x: true,
|
|
2389
|
+
y: allScatter
|
|
2742
2390
|
} : {
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2391
|
+
x: zoomOption.includes("x"),
|
|
2392
|
+
y: zoomOption.includes("y")
|
|
2393
|
+
};
|
|
2394
|
+
const yFormat = yOpts?.format ?? (percent && (bars.length || stackedLines.length) ? (v) => formatPercent(v, locale) : void 0);
|
|
2395
|
+
return {
|
|
2396
|
+
series,
|
|
2397
|
+
x: axisModel(xOpts, xType, categories, false, locale),
|
|
2398
|
+
y: {
|
|
2399
|
+
...axisModel(yOpts, yType, yCategories, yType !== "category", locale),
|
|
2400
|
+
format: yFormat
|
|
2401
|
+
},
|
|
2402
|
+
y2,
|
|
2403
|
+
y2Map,
|
|
2404
|
+
horizontal,
|
|
2405
|
+
locale,
|
|
2406
|
+
labels,
|
|
2407
|
+
formatValue: valueFormatter(locale),
|
|
2408
|
+
base: {
|
|
2409
|
+
x: xRange,
|
|
2410
|
+
y: yRange
|
|
2411
|
+
},
|
|
2412
|
+
allScatter,
|
|
2413
|
+
zoom: zoomAxes,
|
|
2414
|
+
tooltipMode: tooltipOptions?.mode ?? (allScatter ? "nearest" : timeline ? "item" : "axis"),
|
|
2415
|
+
baseline: yLog ? yRange[0] : Math.min(Math.max(0, yRange[0]), yRange[1]),
|
|
2416
|
+
minSpan: {
|
|
2417
|
+
x: xType === "category" ? 1 : (xRange[1] - xRange[0]) * 1e-7,
|
|
2418
|
+
y: yType === "category" ? 1 : (yRange[1] - yRange[0]) * 1e-7
|
|
2419
|
+
},
|
|
2420
|
+
percent,
|
|
2421
|
+
stacks: input.map((s) => s.stack !== void 0 && (s.type === "bar" || s.type === "line" || s.type === "area" || !s.type) ? `${s.type === "bar" ? "bar" : "line"}:${s.stack}` : null)
|
|
2751
2422
|
};
|
|
2752
2423
|
};
|
|
2753
|
-
/**
|
|
2754
|
-
var
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2424
|
+
/** Y value drawn for a point: its stacked top for stacks, the close for candles */
|
|
2425
|
+
var plottedY = (s, i) => s.barTop ? s.barTop[i] : s.stackTop ? s.stackTop[i] : s.cols.y[i];
|
|
2426
|
+
var grids = /* @__PURE__ */ new WeakMap();
|
|
2427
|
+
/** Fill opacity of bar `i`: its own `opacity`, else the one of the series */
|
|
2428
|
+
var barOpacity = (s, i) => {
|
|
2429
|
+
const own = s.cols.opacities?.[i];
|
|
2430
|
+
return own !== void 0 && Number.isFinite(own) ? own : s.opacity;
|
|
2431
|
+
};
|
|
2432
|
+
/** Spatial index of a scatter series, built on first use and kept with its data */
|
|
2433
|
+
var gridOf = (s) => {
|
|
2434
|
+
let grid = grids.get(s.cols);
|
|
2435
|
+
if (!grid) {
|
|
2436
|
+
grid = new GridIndex(s.cols.x, s.cols.y, s.cols.count);
|
|
2437
|
+
grids.set(s.cols, grid);
|
|
2763
2438
|
}
|
|
2764
|
-
|
|
2765
|
-
if (year >= 0 && year < 100) d.setFullYear(d.getFullYear() - 1900);
|
|
2766
|
-
return d.getTime();
|
|
2439
|
+
return grid;
|
|
2767
2440
|
};
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2441
|
+
//#endregion
|
|
2442
|
+
//#region ../charts/src/engine/overlay.ts
|
|
2443
|
+
/** Padding of the labels inside the bars, CSS pixels */
|
|
2444
|
+
var INSIDE_PAD$1 = 6;
|
|
2445
|
+
/**
|
|
2446
|
+
* Color of a text written on a bar of color `fill` (alpha included) over
|
|
2447
|
+
* `background`: white or near-black, whichever contrasts more; `undefined`
|
|
2448
|
+
* for translucent bars (the theme text color with a halo reads better)
|
|
2449
|
+
*/
|
|
2450
|
+
var insideLabelColor = (fill, background) => {
|
|
2451
|
+
const alpha = fill[3];
|
|
2452
|
+
if (alpha < .6) return void 0;
|
|
2453
|
+
const l = relativeLuminance$1(mix(background, fill, alpha));
|
|
2454
|
+
return 1.05 / (l + .05) >= (l + .05) / .062 ? "#ffffff" : "rgba(17, 24, 39, 0.92)";
|
|
2455
|
+
};
|
|
2456
|
+
var font = (theme, size = theme.fontSize, weight = 400) => `${weight} ${size}px ${theme.fontFamily}`;
|
|
2457
|
+
var crisp = (v) => Math.round(v) + .5;
|
|
2458
|
+
/** Path of a rounded box with half-height corners */
|
|
2459
|
+
var pill = (ctx, x, y, w, h) => {
|
|
2460
|
+
const r = Math.min(h / 2, w / 2);
|
|
2461
|
+
ctx.beginPath();
|
|
2462
|
+
ctx.moveTo(x + r, y);
|
|
2463
|
+
ctx.lineTo(x + w - r, y);
|
|
2464
|
+
ctx.arcTo(x + w, y, x + w, y + r, r);
|
|
2465
|
+
ctx.lineTo(x + w, y + h - r);
|
|
2466
|
+
ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
|
|
2467
|
+
ctx.lineTo(x + r, y + h);
|
|
2468
|
+
ctx.arcTo(x, y + h, x, y + h - r, r);
|
|
2469
|
+
ctx.lineTo(x, y + r);
|
|
2470
|
+
ctx.arcTo(x, y, x + r, y, r);
|
|
2471
|
+
ctx.closePath();
|
|
2472
|
+
};
|
|
2473
|
+
/** Box of a text annotation, kept inside the plot */
|
|
2474
|
+
var textBox$1 = (a, s, width, height) => {
|
|
2475
|
+
const { plot } = s;
|
|
2476
|
+
const at = toScreen(s, a.x ?? 0, a.y ?? 0);
|
|
2477
|
+
const free = s.horizontal ? {
|
|
2478
|
+
x: a.y === void 0,
|
|
2479
|
+
y: a.x === void 0
|
|
2480
|
+
} : {
|
|
2481
|
+
x: a.x === void 0,
|
|
2482
|
+
y: a.y === void 0
|
|
2774
2483
|
};
|
|
2775
|
-
|
|
2776
|
-
const
|
|
2484
|
+
const px = free.x ? plot.x + plot.width / 2 : at.x;
|
|
2485
|
+
const py = free.y ? plot.y + plot.height / 2 : at.y;
|
|
2486
|
+
const gap = 6;
|
|
2487
|
+
let x = px - width / 2;
|
|
2488
|
+
let y = py - height / 2;
|
|
2489
|
+
if (a.anchor === "top") y = py + gap;
|
|
2490
|
+
else if (a.anchor === "bottom") y = py - gap - height;
|
|
2491
|
+
else if (a.anchor === "left") x = px + gap;
|
|
2492
|
+
else if (a.anchor === "right") x = px - gap - width;
|
|
2493
|
+
x = Math.max(plot.x + gap, Math.min(x, plot.x + plot.width - gap - width));
|
|
2494
|
+
y = Math.max(plot.y + 2, Math.min(y, plot.y + plot.height - 2 - height));
|
|
2777
2495
|
return {
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2496
|
+
x,
|
|
2497
|
+
y,
|
|
2498
|
+
width,
|
|
2499
|
+
height
|
|
2781
2500
|
};
|
|
2782
2501
|
};
|
|
2783
|
-
/**
|
|
2784
|
-
var
|
|
2785
|
-
const
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2502
|
+
/** Screen box of an area annotation, clipped to the plot */
|
|
2503
|
+
var rectOf = (a, s) => {
|
|
2504
|
+
const { plot } = s;
|
|
2505
|
+
const [xLo, xHi] = s.horizontal ? [plot.y, plot.y + plot.height] : [plot.x, plot.x + plot.width];
|
|
2506
|
+
const [yLo, yHi] = s.horizontal ? [plot.x, plot.x + plot.width] : [plot.y, plot.y + plot.height];
|
|
2507
|
+
const along = (px, lo, hi) => px < lo ? lo : px > hi ? hi : px;
|
|
2508
|
+
return screenRect(s, along(s.xToPx(a.x0 ?? -Infinity), xLo, xHi), along(s.xToPx(a.x1 ?? Infinity), xLo, xHi), along(s.yToPx(a.y0 ?? -Infinity), yLo, yHi), along(s.yToPx(a.y1 ?? Infinity), yLo, yHi));
|
|
2509
|
+
};
|
|
2510
|
+
/** Whether an area annotation has a title block */
|
|
2511
|
+
var hasAreaTitle = (a) => !!(a.label || a.badge || a.detail || a.description);
|
|
2512
|
+
/** Text cut with an ellipsis to fit `max` pixels ("" when nothing fits) */
|
|
2513
|
+
var fitText = (text, max, width) => {
|
|
2514
|
+
if (width(text) <= max) return text;
|
|
2515
|
+
const chars = Array.from(text);
|
|
2516
|
+
let lo = 0;
|
|
2517
|
+
let hi = chars.length;
|
|
2518
|
+
while (lo < hi) {
|
|
2519
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
2520
|
+
if (width(`${chars.slice(0, mid).join("")}…`) <= max) lo = mid;
|
|
2521
|
+
else hi = mid - 1;
|
|
2794
2522
|
}
|
|
2523
|
+
return lo > 0 ? `${chars.slice(0, lo).join("").trimEnd()}…` : "";
|
|
2795
2524
|
};
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2525
|
+
/**
|
|
2526
|
+
* Layout of the title block of an area: `[badge] label detail` on the first
|
|
2527
|
+
* line, the description under it; the block sits in the `labelPosition`
|
|
2528
|
+
* corner, 10px from the edges, and the texts are cut to the area width
|
|
2529
|
+
*/
|
|
2530
|
+
var areaTitleLayout = (a, r, fontSize, measure) => {
|
|
2531
|
+
if (!hasAreaTitle(a) || r.width <= 0 || r.height <= 0) return null;
|
|
2532
|
+
const pad = 10;
|
|
2533
|
+
const lineHeight = fontSize + 4;
|
|
2534
|
+
const max = Math.max(0, r.width - 20);
|
|
2535
|
+
const labelSize = fontSize + 1;
|
|
2536
|
+
const badgeSize = Math.max(9, fontSize - 1);
|
|
2537
|
+
const gap = 6;
|
|
2538
|
+
const badge = a.badge ? fitText(a.badge, Math.max(0, max - 10), (t) => measure(t, badgeSize, 700)) : "";
|
|
2539
|
+
const badgeWidth = badge ? measure(badge, badgeSize, 700) + 10 : 0;
|
|
2540
|
+
let detail = a.detail ?? "";
|
|
2541
|
+
let detailWidth = detail ? measure(detail, fontSize, 500) : 0;
|
|
2542
|
+
const room = () => max - (badge ? badgeWidth + gap : 0) - (detail ? detailWidth + gap : 0);
|
|
2543
|
+
if (detail && a.label && room() < 32) {
|
|
2544
|
+
detail = "";
|
|
2545
|
+
detailWidth = 0;
|
|
2546
|
+
}
|
|
2547
|
+
if (detail && room() < 0) {
|
|
2548
|
+
detail = fitText(detail, Math.max(0, max - (badge ? badgeWidth + gap : 0)), (t) => measure(t, fontSize, 500));
|
|
2549
|
+
detailWidth = detail ? measure(detail, fontSize, 500) : 0;
|
|
2550
|
+
}
|
|
2551
|
+
const label = a.label ? fitText(a.label, Math.max(0, room()), (t) => measure(t, labelSize, 600)) : "";
|
|
2552
|
+
const line = [];
|
|
2553
|
+
if (badge) line.push({
|
|
2554
|
+
kind: "badge",
|
|
2555
|
+
text: badge,
|
|
2556
|
+
x: 0,
|
|
2557
|
+
y: 0,
|
|
2558
|
+
width: badgeWidth,
|
|
2559
|
+
height: lineHeight,
|
|
2560
|
+
size: badgeSize,
|
|
2561
|
+
weight: 700
|
|
2562
|
+
});
|
|
2563
|
+
if (label) line.push({
|
|
2564
|
+
kind: "label",
|
|
2565
|
+
text: label,
|
|
2566
|
+
x: 0,
|
|
2567
|
+
y: 0,
|
|
2568
|
+
width: measure(label, labelSize, 600),
|
|
2569
|
+
height: lineHeight,
|
|
2570
|
+
size: labelSize,
|
|
2571
|
+
weight: 600
|
|
2572
|
+
});
|
|
2573
|
+
if (detail) line.push({
|
|
2574
|
+
kind: "detail",
|
|
2575
|
+
text: detail,
|
|
2576
|
+
x: 0,
|
|
2577
|
+
y: 0,
|
|
2578
|
+
width: detailWidth,
|
|
2579
|
+
height: lineHeight,
|
|
2580
|
+
size: fontSize,
|
|
2581
|
+
weight: 500
|
|
2582
|
+
});
|
|
2583
|
+
const description = a.description ? fitText(a.description, max, (t) => measure(t, fontSize, 400)) : "";
|
|
2584
|
+
const lineWidth = line.reduce((sum, p) => sum + p.width, 0) + Math.max(0, line.length - 1) * gap;
|
|
2585
|
+
const descriptionWidth = description ? measure(description, fontSize, 400) : 0;
|
|
2586
|
+
if (!line.length && !description) return null;
|
|
2587
|
+
const height = (line.length ? lineHeight : 0) + (description ? lineHeight + (line.length ? 2 : 0) : 0);
|
|
2588
|
+
const pos = a.labelPosition;
|
|
2589
|
+
const top = pos.startsWith("top") ? r.y + pad : pos.startsWith("bottom") ? r.y + r.height - pad - height : r.y + (r.height - height) / 2;
|
|
2590
|
+
const startOf = (width) => pos.endsWith("left") ? r.x + pad : pos.endsWith("right") ? r.x + r.width - pad - width : r.x + (r.width - width) / 2;
|
|
2591
|
+
const parts = [];
|
|
2592
|
+
let x = startOf(lineWidth);
|
|
2593
|
+
for (const p of line) {
|
|
2594
|
+
parts.push({
|
|
2595
|
+
...p,
|
|
2596
|
+
x,
|
|
2597
|
+
y: top
|
|
2598
|
+
});
|
|
2599
|
+
x += p.width + gap;
|
|
2806
2600
|
}
|
|
2601
|
+
if (description) parts.push({
|
|
2602
|
+
kind: "description",
|
|
2603
|
+
text: description,
|
|
2604
|
+
x: startOf(descriptionWidth),
|
|
2605
|
+
y: top + (line.length ? lineHeight + 2 : 0),
|
|
2606
|
+
width: descriptionWidth,
|
|
2607
|
+
height: lineHeight,
|
|
2608
|
+
size: fontSize,
|
|
2609
|
+
weight: 400
|
|
2610
|
+
});
|
|
2611
|
+
const width = Math.max(lineWidth, descriptionWidth);
|
|
2612
|
+
return {
|
|
2613
|
+
parts,
|
|
2614
|
+
box: {
|
|
2615
|
+
x: startOf(width),
|
|
2616
|
+
y: top,
|
|
2617
|
+
width,
|
|
2618
|
+
height
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2807
2621
|
};
|
|
2808
2622
|
/**
|
|
2809
|
-
*
|
|
2810
|
-
*
|
|
2811
|
-
* @param timeZone calendar of the steps and labels
|
|
2623
|
+
* Where a reference line goes: `vertical` lines run at screen x `at`,
|
|
2624
|
+
* horizontal ones at screen y `at`
|
|
2812
2625
|
*/
|
|
2813
|
-
var
|
|
2814
|
-
if (min > max) [min, max] = [max, min];
|
|
2815
|
-
const interval = chooseInterval(max - min, count);
|
|
2816
|
-
if (interval.unit === "millisecond") return {
|
|
2817
|
-
values: linearTicks(min, max, count),
|
|
2818
|
-
unit: "millisecond",
|
|
2819
|
-
format: (v) => formatTime(v, "millisecond", locale, timeZone)
|
|
2820
|
-
};
|
|
2821
|
-
const utc = timeZone === "utc";
|
|
2822
|
-
const values = [];
|
|
2823
|
-
let t = floorTo(min, interval, utc);
|
|
2824
|
-
while (t < min) t = advance(t, interval, utc);
|
|
2825
|
-
while (t <= max && values.length < 500) {
|
|
2826
|
-
values.push(t);
|
|
2827
|
-
t = advance(t, interval, utc);
|
|
2828
|
-
}
|
|
2829
|
-
const { unit } = interval;
|
|
2626
|
+
var lineOf = (a, s) => {
|
|
2830
2627
|
return {
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
format: (v) => formatTick(v, unit, locale, timeZone)
|
|
2628
|
+
vertical: a.axis === "x" !== s.horizontal,
|
|
2629
|
+
at: a.axis === "x" ? s.xToPx(a.value) : s.yToPx(a.value)
|
|
2834
2630
|
};
|
|
2835
2631
|
};
|
|
2836
|
-
var
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
/** Cached formatter of a locale and time zone */
|
|
2857
|
-
var intl = (locale, timeZone, options) => dateFormat(locale, timeZone === "utc" ? {
|
|
2858
|
-
...options,
|
|
2859
|
-
timeZone: "UTC"
|
|
2860
|
-
} : options);
|
|
2861
|
-
/** Short tick label; day boundaries show the date, year boundaries the year */
|
|
2862
|
-
var formatTick = (value, unit, locale, timeZone) => {
|
|
2863
|
-
const p = partsOf(value, timeZone === "utc");
|
|
2864
|
-
const midnight = p.hours === 0 && p.minutes === 0 && p.seconds === 0;
|
|
2865
|
-
const newYear = p.month === 0 && p.date === 1;
|
|
2866
|
-
if (locale) {
|
|
2867
|
-
const f = (options) => intl(locale, timeZone, options).format(value);
|
|
2868
|
-
switch (unit) {
|
|
2869
|
-
case "second": return f(HMS);
|
|
2870
|
-
case "minute":
|
|
2871
|
-
case "hour": return f(midnight ? MONTH_DAY : HM);
|
|
2872
|
-
case "day": return f(newYear ? { year: "numeric" } : MONTH_DAY);
|
|
2873
|
-
case "month": return f(p.month === 0 ? { year: "numeric" } : { month: "short" });
|
|
2874
|
-
default: return f({ year: "numeric" });
|
|
2875
|
-
}
|
|
2632
|
+
var drawBackground = (ctx, input) => {
|
|
2633
|
+
const { scales, theme, xTicks, yTicks } = input;
|
|
2634
|
+
const { plot } = scales;
|
|
2635
|
+
const x = scales.horizontal ? input.y : input.x;
|
|
2636
|
+
const y = scales.horizontal ? input.x : input.y;
|
|
2637
|
+
const bottomTicks = scales.horizontal ? yTicks : xTicks;
|
|
2638
|
+
const leftTicks = scales.horizontal ? xTicks : yTicks;
|
|
2639
|
+
const bottomPx = scales.horizontal ? scales.yToPx : scales.xToPx;
|
|
2640
|
+
const leftPx = scales.horizontal ? scales.xToPx : scales.yToPx;
|
|
2641
|
+
ctx.clearRect(0, 0, input.width, input.height);
|
|
2642
|
+
ctx.save();
|
|
2643
|
+
ctx.beginPath();
|
|
2644
|
+
ctx.rect(plot.x, plot.y, plot.width, plot.height);
|
|
2645
|
+
ctx.clip();
|
|
2646
|
+
for (const a of input.annotations) {
|
|
2647
|
+
if (a.type !== "rect") continue;
|
|
2648
|
+
const r = rectOf(a, scales);
|
|
2649
|
+
if (r.width <= 0 || r.height <= 0) continue;
|
|
2650
|
+
ctx.fillStyle = toCss(a.color, a.opacity);
|
|
2651
|
+
ctx.fillRect(r.x, r.y, r.width, r.height);
|
|
2876
2652
|
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2653
|
+
ctx.restore();
|
|
2654
|
+
ctx.lineWidth = 1;
|
|
2655
|
+
ctx.strokeStyle = toCss(theme.grid);
|
|
2656
|
+
ctx.beginPath();
|
|
2657
|
+
if (y.grid) for (const v of leftTicks.values) {
|
|
2658
|
+
const py = crisp(leftPx(v));
|
|
2659
|
+
if (py < plot.y - 1 || py > plot.y + plot.height + 1) continue;
|
|
2660
|
+
ctx.moveTo(plot.x, py);
|
|
2661
|
+
ctx.lineTo(plot.x + plot.width, py);
|
|
2884
2662
|
}
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
const
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2663
|
+
if (x.grid) for (const v of bottomTicks.values) {
|
|
2664
|
+
const px = crisp(bottomPx(v));
|
|
2665
|
+
if (px < plot.x - 1 || px > plot.x + plot.width + 1) continue;
|
|
2666
|
+
ctx.moveTo(px, plot.y);
|
|
2667
|
+
ctx.lineTo(px, plot.y + plot.height);
|
|
2668
|
+
}
|
|
2669
|
+
ctx.stroke();
|
|
2670
|
+
const line = Math.ceil(theme.fontSize * 1.4);
|
|
2671
|
+
ctx.font = font(theme);
|
|
2672
|
+
ctx.fillStyle = toCss(theme.textMuted);
|
|
2673
|
+
if (x.visible) {
|
|
2674
|
+
ctx.strokeStyle = toCss(theme.axis);
|
|
2675
|
+
ctx.beginPath();
|
|
2676
|
+
const base = crisp(plot.y + plot.height);
|
|
2677
|
+
ctx.moveTo(plot.x, base);
|
|
2678
|
+
ctx.lineTo(plot.x + plot.width, base);
|
|
2679
|
+
ctx.stroke();
|
|
2680
|
+
ctx.textBaseline = "top";
|
|
2681
|
+
ctx.textAlign = "center";
|
|
2682
|
+
let lastRight = -Infinity;
|
|
2683
|
+
const order = bottomTicks.values.map((v, i) => ({
|
|
2684
|
+
px: bottomPx(v),
|
|
2685
|
+
text: bottomTicks.labels[i]
|
|
2686
|
+
})).sort((a, b) => a.px - b.px);
|
|
2687
|
+
for (const { px, text } of order) {
|
|
2688
|
+
if (px < plot.x - .5 || px > plot.x + plot.width + .5) continue;
|
|
2689
|
+
const w = ctx.measureText(text).width;
|
|
2690
|
+
const left = Math.max(0, Math.min(input.width - w, px - w / 2));
|
|
2691
|
+
if (left < lastRight + 6) continue;
|
|
2692
|
+
lastRight = left + w;
|
|
2693
|
+
ctx.fillText(text, left + w / 2, plot.y + plot.height + 7);
|
|
2694
|
+
}
|
|
2695
|
+
if (x.title) {
|
|
2696
|
+
ctx.font = font(theme, theme.fontSize + 1, 500);
|
|
2697
|
+
ctx.fillStyle = toCss(theme.text, .8);
|
|
2698
|
+
ctx.fillText(x.title, plot.x + plot.width / 2, plot.y + plot.height + line + 10);
|
|
2913
2699
|
}
|
|
2914
2700
|
}
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2701
|
+
if (y.visible) {
|
|
2702
|
+
ctx.font = font(theme);
|
|
2703
|
+
ctx.fillStyle = toCss(theme.textMuted);
|
|
2704
|
+
ctx.textAlign = "right";
|
|
2705
|
+
ctx.textBaseline = "middle";
|
|
2706
|
+
let lastBottom = -Infinity;
|
|
2707
|
+
const rows = leftTicks.values.map((v, i) => ({
|
|
2708
|
+
py: leftPx(v),
|
|
2709
|
+
text: leftTicks.labels[i]
|
|
2710
|
+
})).sort((a, b) => a.py - b.py);
|
|
2711
|
+
const rowHeight = theme.fontSize + 2;
|
|
2712
|
+
for (const { py, text } of rows) {
|
|
2713
|
+
if (py < plot.y - .5 || py > plot.y + plot.height + .5) continue;
|
|
2714
|
+
if (scales.horizontal) {
|
|
2715
|
+
if (py - rowHeight / 2 < lastBottom) continue;
|
|
2716
|
+
lastBottom = py + rowHeight / 2;
|
|
2717
|
+
}
|
|
2718
|
+
ctx.fillText(text, plot.x - 8, py);
|
|
2719
|
+
}
|
|
2720
|
+
if (y.title) {
|
|
2721
|
+
ctx.save();
|
|
2722
|
+
ctx.font = font(theme, theme.fontSize + 1, 500);
|
|
2723
|
+
ctx.fillStyle = toCss(theme.text, .8);
|
|
2724
|
+
ctx.translate(line / 2 + 4, plot.y + plot.height / 2);
|
|
2725
|
+
ctx.rotate(-Math.PI / 2);
|
|
2726
|
+
ctx.textAlign = "center";
|
|
2727
|
+
ctx.fillText(y.title, 0, 0);
|
|
2728
|
+
ctx.restore();
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
const y2 = scales.horizontal ? null : input.y2;
|
|
2732
|
+
const y2Ticks = input.y2Ticks;
|
|
2733
|
+
if (y2 && y2Ticks && y2.visible) {
|
|
2734
|
+
const right = plot.x + plot.width;
|
|
2735
|
+
ctx.font = font(theme);
|
|
2736
|
+
ctx.fillStyle = toCss(theme.textMuted);
|
|
2737
|
+
ctx.textAlign = "left";
|
|
2738
|
+
ctx.textBaseline = "middle";
|
|
2739
|
+
y2Ticks.values.forEach((v, i) => {
|
|
2740
|
+
const py = scales.yToPx(v);
|
|
2741
|
+
if (py < plot.y - .5 || py > plot.y + plot.height + .5) return;
|
|
2742
|
+
ctx.fillText(y2Ticks.labels[i], right + 8, py);
|
|
2743
|
+
});
|
|
2744
|
+
if (y2.title) {
|
|
2745
|
+
ctx.save();
|
|
2746
|
+
ctx.font = font(theme, theme.fontSize + 1, 500);
|
|
2747
|
+
ctx.fillStyle = toCss(theme.text, .8);
|
|
2748
|
+
ctx.translate(input.width - line / 2 - 4, plot.y + plot.height / 2);
|
|
2749
|
+
ctx.rotate(Math.PI / 2);
|
|
2750
|
+
ctx.textAlign = "center";
|
|
2751
|
+
ctx.fillText(y2.title, 0, 0);
|
|
2752
|
+
ctx.restore();
|
|
2753
|
+
}
|
|
2921
2754
|
}
|
|
2922
2755
|
};
|
|
2923
|
-
|
|
2924
|
-
|
|
2756
|
+
var overlaps = (a, b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
|
2757
|
+
var inside$2 = (r, plot) => r.x >= plot.x && r.y >= plot.y && r.x + r.width <= plot.x + plot.width && r.y + r.height <= plot.y + plot.height;
|
|
2758
|
+
/** Text with a halo in the background color (readable over data) */
|
|
2759
|
+
var haloText = (ctx, text, x, y, color, halo) => {
|
|
2760
|
+
ctx.lineWidth = 3;
|
|
2761
|
+
ctx.lineJoin = "round";
|
|
2762
|
+
ctx.strokeStyle = halo;
|
|
2763
|
+
ctx.strokeText(text, x, y);
|
|
2764
|
+
ctx.fillStyle = color;
|
|
2765
|
+
ctx.fillText(text, x, y);
|
|
2766
|
+
};
|
|
2925
2767
|
/**
|
|
2926
|
-
*
|
|
2927
|
-
*
|
|
2768
|
+
* Box of a label inside its bar (the visible part of it): the text cut with
|
|
2769
|
+
* an ellipsis to fit, at the start of horizontal bars and centered in
|
|
2770
|
+
* vertical ones; `null` when the bar is too small
|
|
2928
2771
|
*/
|
|
2929
|
-
var
|
|
2930
|
-
const
|
|
2931
|
-
if (
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2772
|
+
var insideBox = (label, plot, measure, height) => {
|
|
2773
|
+
const bar = label.bar;
|
|
2774
|
+
if (!bar) return null;
|
|
2775
|
+
const x0 = Math.max(bar.x, plot.x);
|
|
2776
|
+
const x1 = Math.min(bar.x + bar.width, plot.x + plot.width);
|
|
2777
|
+
const y0 = Math.max(bar.y, plot.y);
|
|
2778
|
+
const y1 = Math.min(bar.y + bar.height, plot.y + plot.height);
|
|
2779
|
+
if (y1 - y0 < height - 2) return null;
|
|
2780
|
+
const text = fitText(label.text, x1 - x0 - 12, measure);
|
|
2781
|
+
if (!text) return null;
|
|
2782
|
+
const w = measure(text);
|
|
2783
|
+
const x = label.align === "start" ? x0 + INSIDE_PAD$1 : (x0 + x1) / 2 - w / 2;
|
|
2784
|
+
return {
|
|
2785
|
+
label: text === label.text ? label : {
|
|
2786
|
+
...label,
|
|
2787
|
+
text
|
|
2788
|
+
},
|
|
2789
|
+
box: {
|
|
2790
|
+
x,
|
|
2791
|
+
y: (y0 + y1) / 2 - height / 2,
|
|
2792
|
+
width: w,
|
|
2793
|
+
height
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
};
|
|
2797
|
+
/** Greedy placement: right, left, above, below the dot; skipped when no slot is free */
|
|
2798
|
+
var placeLabels = (labels, plot, measure, height, occupied = []) => {
|
|
2799
|
+
const placed = [];
|
|
2800
|
+
for (const label of labels) {
|
|
2801
|
+
if (label.placement === "inside") {
|
|
2802
|
+
const fit = insideBox(label, plot, measure, height);
|
|
2803
|
+
if (fit) placed.push(fit);
|
|
2804
|
+
continue;
|
|
2805
|
+
}
|
|
2806
|
+
const w = measure(label.text);
|
|
2807
|
+
const gap = label.r + 4;
|
|
2808
|
+
const above = {
|
|
2809
|
+
x: label.x - w / 2,
|
|
2810
|
+
y: label.y - gap - height,
|
|
2811
|
+
width: w,
|
|
2812
|
+
height
|
|
2942
2813
|
};
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2814
|
+
const below = {
|
|
2815
|
+
x: label.x - w / 2,
|
|
2816
|
+
y: label.y + gap,
|
|
2817
|
+
width: w,
|
|
2818
|
+
height
|
|
2947
2819
|
};
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2820
|
+
const right = {
|
|
2821
|
+
x: label.x + gap,
|
|
2822
|
+
y: label.y - height / 2,
|
|
2823
|
+
width: w,
|
|
2824
|
+
height
|
|
2825
|
+
};
|
|
2826
|
+
const left = {
|
|
2827
|
+
x: label.x - gap - w,
|
|
2828
|
+
y: label.y - height / 2,
|
|
2829
|
+
width: w,
|
|
2830
|
+
height
|
|
2958
2831
|
};
|
|
2832
|
+
const box = (label.placement === "above" ? [above] : label.placement === "below" ? [below] : label.placement === "right" ? [right, left] : label.placement === "left" ? [left, right] : [
|
|
2833
|
+
{
|
|
2834
|
+
x: label.x + gap,
|
|
2835
|
+
y: label.y - height / 2,
|
|
2836
|
+
width: w,
|
|
2837
|
+
height
|
|
2838
|
+
},
|
|
2839
|
+
{
|
|
2840
|
+
x: label.x - gap - w,
|
|
2841
|
+
y: label.y - height / 2,
|
|
2842
|
+
width: w,
|
|
2843
|
+
height
|
|
2844
|
+
},
|
|
2845
|
+
above,
|
|
2846
|
+
below
|
|
2847
|
+
]).find((c) => inside$2(c, plot) && !occupied.some((o) => overlaps(o, c)));
|
|
2848
|
+
if (!box) continue;
|
|
2849
|
+
occupied.push(box);
|
|
2850
|
+
placed.push({
|
|
2851
|
+
label,
|
|
2852
|
+
box
|
|
2853
|
+
});
|
|
2959
2854
|
}
|
|
2960
|
-
|
|
2961
|
-
const step = tickStep(min, max, count);
|
|
2962
|
-
if (axis.type === "log") return logTicks(axis, min, max, count);
|
|
2963
|
-
const tick = axis.format ?? tickFormatter(step, Math.max(Math.abs(min), Math.abs(max)), axis.locale);
|
|
2964
|
-
return {
|
|
2965
|
-
values,
|
|
2966
|
-
labels: values.map(tick),
|
|
2967
|
-
formatValue: axis.format ?? ((value) => formatValue(value, axis.locale))
|
|
2968
|
-
};
|
|
2855
|
+
return placed;
|
|
2969
2856
|
};
|
|
2970
|
-
/**
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2857
|
+
/** Elbow arrows: polylines with rounded corners and filled heads */
|
|
2858
|
+
var drawArrows = (ctx, arrows) => {
|
|
2859
|
+
for (const a of arrows) {
|
|
2860
|
+
const pts = a.points;
|
|
2861
|
+
const first = pts[0];
|
|
2862
|
+
if (!first) continue;
|
|
2863
|
+
ctx.strokeStyle = a.color;
|
|
2864
|
+
ctx.fillStyle = a.color;
|
|
2865
|
+
ctx.lineWidth = a.width;
|
|
2866
|
+
ctx.lineJoin = "round";
|
|
2867
|
+
ctx.setLineDash([]);
|
|
2868
|
+
ctx.beginPath();
|
|
2869
|
+
ctx.moveTo(first.x, first.y);
|
|
2870
|
+
for (let i = 1; i < pts.length; i++) {
|
|
2871
|
+
const p = pts[i];
|
|
2872
|
+
const next = pts[i + 1];
|
|
2873
|
+
const prev = pts[i - 1];
|
|
2874
|
+
if (next && typeof ctx.arcTo === "function") {
|
|
2875
|
+
const r = Math.min(4, Math.hypot(p.x - prev.x, p.y - prev.y) / 2, Math.hypot(next.x - p.x, next.y - p.y) / 2);
|
|
2876
|
+
ctx.arcTo(p.x, p.y, next.x, next.y, r);
|
|
2877
|
+
} else ctx.lineTo(p.x, p.y);
|
|
2878
|
+
}
|
|
2879
|
+
ctx.stroke();
|
|
2880
|
+
const [tip, b0, b1] = a.head;
|
|
2881
|
+
if (tip && b0 && b1) {
|
|
2882
|
+
ctx.beginPath();
|
|
2883
|
+
ctx.moveTo(tip.x, tip.y);
|
|
2884
|
+
ctx.lineTo(b0.x, b0.y);
|
|
2885
|
+
ctx.lineTo(b1.x, b1.y);
|
|
2886
|
+
ctx.closePath();
|
|
2887
|
+
ctx.fill();
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
};
|
|
2891
|
+
var drawForeground = (ctx, input) => {
|
|
2892
|
+
const { scales, theme } = input;
|
|
2893
|
+
const { plot } = scales;
|
|
2894
|
+
if (input.clear !== false) ctx.clearRect(0, 0, input.width, input.height);
|
|
2895
|
+
ctx.save();
|
|
2896
|
+
ctx.beginPath();
|
|
2897
|
+
ctx.rect(plot.x, plot.y, plot.width, plot.height);
|
|
2898
|
+
ctx.clip();
|
|
2899
|
+
for (const c of input.connectors ?? []) {
|
|
2900
|
+
ctx.strokeStyle = c.color;
|
|
2901
|
+
ctx.lineWidth = c.width;
|
|
2902
|
+
ctx.setLineDash([...c.dash]);
|
|
2903
|
+
ctx.beginPath();
|
|
2904
|
+
const along = c.y0 === c.y1;
|
|
2905
|
+
const x0 = along ? c.x0 : crisp(c.x0);
|
|
2906
|
+
const y0 = along ? crisp(c.y0) : c.y0;
|
|
2907
|
+
ctx.moveTo(x0, y0);
|
|
2908
|
+
ctx.lineTo(along ? c.x1 : x0, along ? y0 : c.y1);
|
|
2909
|
+
ctx.stroke();
|
|
2910
|
+
}
|
|
2911
|
+
ctx.setLineDash([]);
|
|
2912
|
+
if (input.arrows?.length) drawArrows(ctx, input.arrows);
|
|
2913
|
+
const halo = toCss(theme.background, .9);
|
|
2914
|
+
const occupied = [];
|
|
2915
|
+
const labelHeight = theme.fontSize + 4;
|
|
2916
|
+
for (const a of input.annotations) {
|
|
2917
|
+
if (a.type !== "line") continue;
|
|
2918
|
+
const color = toCss(a.color);
|
|
2919
|
+
ctx.strokeStyle = color;
|
|
2920
|
+
ctx.lineWidth = a.width;
|
|
2921
|
+
ctx.setLineDash(a.dash);
|
|
2922
|
+
ctx.beginPath();
|
|
2923
|
+
const where = lineOf(a, scales);
|
|
2924
|
+
if (!where.vertical) {
|
|
2925
|
+
const py = crisp(where.at);
|
|
2926
|
+
ctx.moveTo(plot.x, py);
|
|
2927
|
+
ctx.lineTo(plot.x + plot.width, py);
|
|
2928
|
+
ctx.stroke();
|
|
2929
|
+
if (a.label) {
|
|
2930
|
+
ctx.setLineDash([]);
|
|
2931
|
+
ctx.font = font(theme, theme.fontSize, 500);
|
|
2932
|
+
ctx.textAlign = "right";
|
|
2933
|
+
ctx.textBaseline = "bottom";
|
|
2934
|
+
haloText(ctx, a.label, plot.x + plot.width - 6, py - 3, color, halo);
|
|
2935
|
+
}
|
|
2936
|
+
} else {
|
|
2937
|
+
const px = crisp(where.at);
|
|
2938
|
+
ctx.moveTo(px, plot.y);
|
|
2939
|
+
ctx.lineTo(px, plot.y + plot.height);
|
|
2940
|
+
ctx.stroke();
|
|
2941
|
+
if (a.label) {
|
|
2942
|
+
ctx.setLineDash([]);
|
|
2943
|
+
ctx.font = font(theme, theme.fontSize, 500);
|
|
2944
|
+
ctx.textAlign = "left";
|
|
2945
|
+
ctx.textBaseline = "top";
|
|
2946
|
+
haloText(ctx, a.label, px + 5, plot.y + 5, color, halo);
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
ctx.setLineDash([]);
|
|
2951
|
+
for (const a of input.annotations) {
|
|
2952
|
+
if (a.type !== "rect" || !hasAreaTitle(a)) continue;
|
|
2953
|
+
const title = areaTitleLayout(a, rectOf(a, scales), theme.fontSize, (text, size, weight) => {
|
|
2954
|
+
ctx.font = font(theme, size, weight);
|
|
2955
|
+
return ctx.measureText(text).width;
|
|
2956
|
+
});
|
|
2957
|
+
if (!title) continue;
|
|
2958
|
+
const color = toCss(a.labelColor);
|
|
2959
|
+
const muted = toCss(theme.textMuted);
|
|
2960
|
+
ctx.textAlign = "left";
|
|
2961
|
+
ctx.textBaseline = "middle";
|
|
2962
|
+
for (const p of title.parts) {
|
|
2963
|
+
const cy = p.y + p.height / 2;
|
|
2964
|
+
if (p.kind === "badge") {
|
|
2965
|
+
const h = p.height - 2;
|
|
2966
|
+
pill(ctx, p.x, cy - h / 2, p.width, h);
|
|
2967
|
+
ctx.fillStyle = toCss(a.labelColor, .16);
|
|
2968
|
+
ctx.fill();
|
|
2969
|
+
ctx.font = font(theme, p.size, p.weight);
|
|
2970
|
+
ctx.fillStyle = color;
|
|
2971
|
+
ctx.fillText(p.text, p.x + 5, cy);
|
|
2972
|
+
continue;
|
|
2973
|
+
}
|
|
2974
|
+
ctx.font = font(theme, p.size, p.weight);
|
|
2975
|
+
ctx.fillStyle = p.kind === "label" ? color : muted;
|
|
2976
|
+
ctx.fillText(p.text, p.x, cy);
|
|
2984
2977
|
}
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
const
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2978
|
+
occupied.push(title.box);
|
|
2979
|
+
}
|
|
2980
|
+
for (const a of input.annotations) {
|
|
2981
|
+
if (a.type !== "text" || !a.text) continue;
|
|
2982
|
+
ctx.font = font(theme, theme.fontSize, a.weight);
|
|
2983
|
+
const box = textBox$1(a, scales, ctx.measureText(a.text).width, labelHeight);
|
|
2984
|
+
ctx.textAlign = "left";
|
|
2985
|
+
ctx.textBaseline = "middle";
|
|
2986
|
+
haloText(ctx, a.text, box.x, box.y + box.height / 2, toCss(a.color), halo);
|
|
2987
|
+
occupied.push(box);
|
|
2988
|
+
}
|
|
2989
|
+
if (input.pointLabels.length) {
|
|
2990
|
+
ctx.font = font(theme);
|
|
2991
|
+
ctx.textAlign = "left";
|
|
2992
|
+
ctx.textBaseline = "middle";
|
|
2993
|
+
const textColor = toCss(theme.text, .85);
|
|
2994
|
+
const placed = placeLabels(input.pointLabels, plot, (t) => ctx.measureText(t).width, labelHeight - 2, occupied);
|
|
2995
|
+
for (const { label, box } of placed) {
|
|
2996
|
+
const y = box.y + box.height / 2;
|
|
2997
|
+
if (label.color) {
|
|
2998
|
+
ctx.fillStyle = label.color;
|
|
2999
|
+
ctx.fillText(label.text, box.x, y);
|
|
3000
|
+
} else haloText(ctx, label.text, box.x, y, textColor, halo);
|
|
2996
3001
|
}
|
|
2997
3002
|
}
|
|
2998
|
-
|
|
2999
|
-
return {
|
|
3000
|
-
values,
|
|
3001
|
-
labels: raw.map(label),
|
|
3002
|
-
formatValue: axis.format ?? ((value) => formatValue(value, axis.locale))
|
|
3003
|
-
};
|
|
3003
|
+
ctx.restore();
|
|
3004
3004
|
};
|
|
3005
3005
|
var DEFAULT_PADDING = {
|
|
3006
3006
|
top: 12,
|
|
@@ -3718,6 +3718,7 @@ var hoverMarks = (model, sc, event) => {
|
|
|
3718
3718
|
const half = Math.max(s.barHalfWidth / Math.abs(sc.xPerPx), 1);
|
|
3719
3719
|
bandLo = Math.min(bandLo, c - half);
|
|
3720
3720
|
bandHi = Math.max(bandHi, c + half);
|
|
3721
|
+
if (s.type === "bar") marks.push(...liftedBars(model, sc, s, it.index));
|
|
3721
3722
|
continue;
|
|
3722
3723
|
}
|
|
3723
3724
|
if (s.type === "heatmap") {
|
|
@@ -3753,6 +3754,8 @@ var hoverMarks = (model, sc, event) => {
|
|
|
3753
3754
|
y: cell.y + style.inset,
|
|
3754
3755
|
width: Math.max(1, cell.width - style.inset * 2),
|
|
3755
3756
|
height: Math.max(1, cell.height - style.inset * 2),
|
|
3757
|
+
seriesIndex: s.index,
|
|
3758
|
+
index: it.index,
|
|
3756
3759
|
color: it.color,
|
|
3757
3760
|
ring: contrastRing(fill),
|
|
3758
3761
|
radius: style.radius
|
|
@@ -3797,6 +3800,127 @@ var hoverMarks = (model, sc, event) => {
|
|
|
3797
3800
|
};
|
|
3798
3801
|
/** Ring of a hovered cell: light on dark fills, dark on light ones */
|
|
3799
3802
|
var contrastRing = (fill) => luminance(fill) > .45 ? "rgba(15, 23, 42, 0.78)" : "rgba(255, 255, 255, 0.9)";
|
|
3803
|
+
/** Bars below this opacity are translucent: ringed in their solid color */
|
|
3804
|
+
var TRANSLUCENT = .6;
|
|
3805
|
+
/**
|
|
3806
|
+
* Corner radius of the bars of a series, like the data layer: stacked
|
|
3807
|
+
* segments stay square, floating bars (ranges, Gantt) keep their corners
|
|
3808
|
+
*/
|
|
3809
|
+
var barRadius = (model, s) => !s.floating && model.series.some((o) => o !== s && !o.hidden && o.type === "bar" && !o.floating && o.barOffset === s.barOffset) ? 0 : s.radius;
|
|
3810
|
+
/**
|
|
3811
|
+
* Room (data units) between a bar and the edge of its slot when it shares
|
|
3812
|
+
* its category with other slots (grouped bars); `null` when it is alone
|
|
3813
|
+
*/
|
|
3814
|
+
var slotRoom = (model, s) => {
|
|
3815
|
+
let slot = Infinity;
|
|
3816
|
+
for (const o of model.series) {
|
|
3817
|
+
if (o.hidden || o.type !== "bar" && o.type !== "boxplot") continue;
|
|
3818
|
+
const d = Math.abs(o.barOffset - s.barOffset);
|
|
3819
|
+
if (d > 0 && d < slot) slot = d;
|
|
3820
|
+
}
|
|
3821
|
+
return Number.isFinite(slot) ? Math.max(0, slot / 2 - s.barHalfWidth) : null;
|
|
3822
|
+
};
|
|
3823
|
+
/** Pixels the thickness of a hovered bar grows by on each side */
|
|
3824
|
+
var barGrow = (model, sc, s, thickness) => {
|
|
3825
|
+
let grow = Math.min(5, Math.max(1.5, thickness * .08));
|
|
3826
|
+
const room = slotRoom(model, s);
|
|
3827
|
+
if (room !== null) grow = Math.min(grow, room / Math.abs(sc.xPerPx) * .5);
|
|
3828
|
+
return grow >= .75 ? Math.round(grow * 4) / 4 : 0;
|
|
3829
|
+
};
|
|
3830
|
+
/** Bar `i` of a series in pixels of the stage, `null` if not drawn */
|
|
3831
|
+
var barGeometry = (model, sc, s, i) => {
|
|
3832
|
+
const base = s.barBase?.[i];
|
|
3833
|
+
const top = s.barTop?.[i];
|
|
3834
|
+
const x = s.cols.x[i];
|
|
3835
|
+
if (base === void 0 || top === void 0) return null;
|
|
3836
|
+
if (Number.isNaN(base) || Number.isNaN(top) || !Number.isFinite(x)) return null;
|
|
3837
|
+
const { plot } = sc;
|
|
3838
|
+
const c = sc.xToPx(x + s.barOffset);
|
|
3839
|
+
const half = Math.max(s.barHalfWidth / Math.abs(sc.xPerPx), .5);
|
|
3840
|
+
const lo = sc.horizontal ? plot.x : plot.y;
|
|
3841
|
+
const hi = lo + (sc.horizontal ? plot.width : plot.height);
|
|
3842
|
+
const clamp = (v) => Math.min(hi + 16, Math.max(lo - 16, Number.isNaN(v) ? lo : v));
|
|
3843
|
+
const a = clamp(sc.yToPx(base));
|
|
3844
|
+
const b = clamp(sc.yToPx(top));
|
|
3845
|
+
if (Math.abs(a - b) < .5) return null;
|
|
3846
|
+
const rect = screenRect(sc, c - half, c + half, a, b);
|
|
3847
|
+
if (rect.x > plot.x + plot.width || rect.x + rect.width < plot.x || rect.y > plot.y + plot.height || rect.y + rect.height < plot.y) return null;
|
|
3848
|
+
return {
|
|
3849
|
+
...rect,
|
|
3850
|
+
radius: Math.min(barRadius(model, s), rect.width / 2, rect.height / 2),
|
|
3851
|
+
base: sc.horizontal ? a < b ? "left" : "right" : a > b ? "bottom" : "top"
|
|
3852
|
+
};
|
|
3853
|
+
};
|
|
3854
|
+
/**
|
|
3855
|
+
* Lifted marks of hovered bar `i`, then the bars drawn over it by series
|
|
3856
|
+
* without a tooltip in its slot (the progress of a Gantt task), which the
|
|
3857
|
+
* lifted bar would hide otherwise
|
|
3858
|
+
*/
|
|
3859
|
+
var liftedBars = (model, sc, s, i) => {
|
|
3860
|
+
const shape = barGeometry(model, sc, s, i);
|
|
3861
|
+
if (!shape) return [];
|
|
3862
|
+
const grow = barGrow(model, sc, s, shape.base === "top" || shape.base === "bottom" ? shape.width : shape.height);
|
|
3863
|
+
const fill = s.pointColors?.[i] ?? s.color;
|
|
3864
|
+
const alpha = fill[3] * barOpacity(s, i);
|
|
3865
|
+
const translucent = alpha < TRANSLUCENT;
|
|
3866
|
+
const marks = [{
|
|
3867
|
+
kind: "bar",
|
|
3868
|
+
...shape,
|
|
3869
|
+
seriesIndex: s.index,
|
|
3870
|
+
index: i,
|
|
3871
|
+
color: toCss(fill, barOpacity(s, i)),
|
|
3872
|
+
backdrop: alpha < .98,
|
|
3873
|
+
ring: translucent ? toCss([
|
|
3874
|
+
fill[0],
|
|
3875
|
+
fill[1],
|
|
3876
|
+
fill[2],
|
|
3877
|
+
1
|
|
3878
|
+
]) : contrastRing(fill),
|
|
3879
|
+
grow
|
|
3880
|
+
}];
|
|
3881
|
+
const lo = Math.min(s.barBase?.[i] ?? NaN, s.barTop?.[i] ?? NaN);
|
|
3882
|
+
const hi = Math.max(s.barBase?.[i] ?? NaN, s.barTop?.[i] ?? NaN);
|
|
3883
|
+
if (!s.floating || !(hi >= lo)) return marks;
|
|
3884
|
+
const eps = (hi - lo) * 1e-9;
|
|
3885
|
+
for (const o of model.series) {
|
|
3886
|
+
if (o === s || o.hidden || o.tooltip || o.type !== "bar" || !o.floating || o.barOffset !== s.barOffset || !o.barBase || !o.barTop) continue;
|
|
3887
|
+
for (let j = 0; j < o.cols.count; j++) {
|
|
3888
|
+
if (o.cols.x[j] !== s.cols.x[i]) continue;
|
|
3889
|
+
const a = Math.min(o.barBase[j], o.barTop[j]);
|
|
3890
|
+
const b = Math.max(o.barBase[j], o.barTop[j]);
|
|
3891
|
+
if (!(a >= lo - eps && b <= hi + eps)) continue;
|
|
3892
|
+
const part = barGeometry(model, sc, o, j);
|
|
3893
|
+
if (!part) continue;
|
|
3894
|
+
const color = o.pointColors?.[j] ?? o.color;
|
|
3895
|
+
marks.push({
|
|
3896
|
+
kind: "bar",
|
|
3897
|
+
...part,
|
|
3898
|
+
seriesIndex: o.index,
|
|
3899
|
+
index: j,
|
|
3900
|
+
color: toCss(color, barOpacity(o, j)),
|
|
3901
|
+
backdrop: false,
|
|
3902
|
+
ring: null,
|
|
3903
|
+
grow
|
|
3904
|
+
});
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
return marks;
|
|
3908
|
+
};
|
|
3909
|
+
/**
|
|
3910
|
+
* Rectangle of a lifted bar: thicker by `grow` on both sides across its
|
|
3911
|
+
* thickness, the same length
|
|
3912
|
+
*/
|
|
3913
|
+
var liftedBarRect = (mark) => mark.base === "top" || mark.base === "bottom" ? {
|
|
3914
|
+
x: mark.x - mark.grow,
|
|
3915
|
+
y: mark.y,
|
|
3916
|
+
width: mark.width + mark.grow * 2,
|
|
3917
|
+
height: mark.height
|
|
3918
|
+
} : {
|
|
3919
|
+
x: mark.x,
|
|
3920
|
+
y: mark.y - mark.grow,
|
|
3921
|
+
width: mark.width,
|
|
3922
|
+
height: mark.height + mark.grow * 2
|
|
3923
|
+
};
|
|
3800
3924
|
/** Plotted value of the hovered outlier of a box */
|
|
3801
3925
|
var plottedOutlier = (s, it) => {
|
|
3802
3926
|
const raw = s.raw.outliers?.[it.index];
|
|
@@ -3912,7 +4036,7 @@ var blockedLanes = (boxes) => {
|
|
|
3912
4036
|
}
|
|
3913
4037
|
return out;
|
|
3914
4038
|
};
|
|
3915
|
-
var inside = (lanes, v) => lanes.some(([lo, hi]) => v > lo + 1e-6 && v < hi - 1e-6);
|
|
4039
|
+
var inside$1 = (lanes, v) => lanes.some(([lo, hi]) => v > lo + 1e-6 && v < hi - 1e-6);
|
|
3916
4040
|
/** First free lane from `from` towards `to` (direction `dir`), or null */
|
|
3917
4041
|
var freeLane = (lanes, from, to, dir) => {
|
|
3918
4042
|
const candidates = [from];
|
|
@@ -3920,7 +4044,7 @@ var freeLane = (lanes, from, to, dir) => {
|
|
|
3920
4044
|
let best = null;
|
|
3921
4045
|
for (const v of candidates) {
|
|
3922
4046
|
const along = dir * (v - from);
|
|
3923
|
-
if (along < 0 || dir * (to - v) < 0 || inside(lanes, v)) continue;
|
|
4047
|
+
if (along < 0 || dir * (to - v) < 0 || inside$1(lanes, v)) continue;
|
|
3924
4048
|
if (best === null || along < dir * (best - from)) best = v;
|
|
3925
4049
|
}
|
|
3926
4050
|
return best;
|
|
@@ -4126,6 +4250,277 @@ var crosshairAt = (model, sc, event, crosshair) => {
|
|
|
4126
4250
|
return c >= lo && c <= hi ? c : null;
|
|
4127
4251
|
};
|
|
4128
4252
|
//#endregion
|
|
4253
|
+
//#region ../charts/src/engine/tooltip.ts
|
|
4254
|
+
var ORDER$1 = {
|
|
4255
|
+
side: [
|
|
4256
|
+
"right",
|
|
4257
|
+
"left",
|
|
4258
|
+
"bottom",
|
|
4259
|
+
"top"
|
|
4260
|
+
],
|
|
4261
|
+
vertical: [
|
|
4262
|
+
"top",
|
|
4263
|
+
"bottom",
|
|
4264
|
+
"right",
|
|
4265
|
+
"left"
|
|
4266
|
+
],
|
|
4267
|
+
right: [
|
|
4268
|
+
"right",
|
|
4269
|
+
"left",
|
|
4270
|
+
"bottom",
|
|
4271
|
+
"top"
|
|
4272
|
+
],
|
|
4273
|
+
left: [
|
|
4274
|
+
"left",
|
|
4275
|
+
"right",
|
|
4276
|
+
"bottom",
|
|
4277
|
+
"top"
|
|
4278
|
+
],
|
|
4279
|
+
top: [
|
|
4280
|
+
"top",
|
|
4281
|
+
"bottom",
|
|
4282
|
+
"right",
|
|
4283
|
+
"left"
|
|
4284
|
+
],
|
|
4285
|
+
bottom: [
|
|
4286
|
+
"bottom",
|
|
4287
|
+
"top",
|
|
4288
|
+
"right",
|
|
4289
|
+
"left"
|
|
4290
|
+
]
|
|
4291
|
+
};
|
|
4292
|
+
var inset$1 = (r, m) => ({
|
|
4293
|
+
x: r.x + m,
|
|
4294
|
+
y: r.y + m,
|
|
4295
|
+
width: Math.max(0, r.width - m * 2),
|
|
4296
|
+
height: Math.max(0, r.height - m * 2)
|
|
4297
|
+
});
|
|
4298
|
+
var intersection = (a, b) => {
|
|
4299
|
+
const x = Math.max(a.x, b.x);
|
|
4300
|
+
const y = Math.max(a.y, b.y);
|
|
4301
|
+
return {
|
|
4302
|
+
x,
|
|
4303
|
+
y,
|
|
4304
|
+
width: Math.max(0, Math.min(a.x + a.width, b.x + b.width) - x),
|
|
4305
|
+
height: Math.max(0, Math.min(a.y + a.height, b.y + b.height) - y)
|
|
4306
|
+
};
|
|
4307
|
+
};
|
|
4308
|
+
/** Smallest rectangle holding both */
|
|
4309
|
+
var unionRect = (a, b) => {
|
|
4310
|
+
if (!a) return {
|
|
4311
|
+
x: b.x,
|
|
4312
|
+
y: b.y,
|
|
4313
|
+
width: b.width,
|
|
4314
|
+
height: b.height
|
|
4315
|
+
};
|
|
4316
|
+
const x = Math.min(a.x, b.x);
|
|
4317
|
+
const y = Math.min(a.y, b.y);
|
|
4318
|
+
return {
|
|
4319
|
+
x,
|
|
4320
|
+
y,
|
|
4321
|
+
width: Math.max(a.x + a.width, b.x + b.width) - x,
|
|
4322
|
+
height: Math.max(a.y + a.height, b.y + b.height) - y
|
|
4323
|
+
};
|
|
4324
|
+
};
|
|
4325
|
+
/** Area covered by both rectangles */
|
|
4326
|
+
var overlapArea$1 = (a, b) => {
|
|
4327
|
+
const r = intersection(a, b);
|
|
4328
|
+
return r.width * r.height;
|
|
4329
|
+
};
|
|
4330
|
+
/** Start of a `len` long span at `v`, moved into [lo, hi] (at `lo` when longer) */
|
|
4331
|
+
var clampSpan = (v, len, lo, hi) => Math.max(lo, Math.min(v, hi - len));
|
|
4332
|
+
var inside = (area, x, y, w, h) => x >= area.x - .5 && y >= area.y - .5 && x + w <= area.x + area.width + .5 && y + h <= area.y + area.height + .5;
|
|
4333
|
+
/**
|
|
4334
|
+
* Top-left corner of a tooltip that covers neither the hovered marks
|
|
4335
|
+
* (`avoid`) nor the pointer. Candidates sit beside the avoided box, in the
|
|
4336
|
+
* order of `prefer`, centered on the pointer across that side and slid
|
|
4337
|
+
* into the area. The first one inside the stage wins; else, with `bounds`,
|
|
4338
|
+
* the candidate inside the bounds that covers the least of the stage (a
|
|
4339
|
+
* tooltip above a tiny sparkline rather than over it); else the clamped
|
|
4340
|
+
* spot covering the marks the least.
|
|
4341
|
+
*/
|
|
4342
|
+
var placeTooltip = (input) => {
|
|
4343
|
+
const { anchor, prefer = "side", gap = 12, margin = 4 } = input;
|
|
4344
|
+
const { width: w, height: h } = input.size;
|
|
4345
|
+
const pointer = {
|
|
4346
|
+
x: anchor.x,
|
|
4347
|
+
y: anchor.y,
|
|
4348
|
+
width: 0,
|
|
4349
|
+
height: 0
|
|
4350
|
+
};
|
|
4351
|
+
const avoid = unionRect(input.avoid ?? null, pointer);
|
|
4352
|
+
const stageRect = {
|
|
4353
|
+
x: 0,
|
|
4354
|
+
y: 0,
|
|
4355
|
+
width: input.stage.width,
|
|
4356
|
+
height: input.stage.height
|
|
4357
|
+
};
|
|
4358
|
+
const bounds = input.bounds && input.bounds.width > 0 && input.bounds.height > 0 ? inset$1(input.bounds, margin) : null;
|
|
4359
|
+
const stageArea = inset$1(stageRect, margin);
|
|
4360
|
+
const visible = bounds ? intersection(stageArea, bounds) : stageArea;
|
|
4361
|
+
const inner = visible.width > 0 && visible.height > 0 ? visible : stageArea;
|
|
4362
|
+
const order = ORDER$1[prefer];
|
|
4363
|
+
const beside = (side, areas) => {
|
|
4364
|
+
let x;
|
|
4365
|
+
let y;
|
|
4366
|
+
if (side === "right" || side === "left") {
|
|
4367
|
+
x = side === "right" ? avoid.x + avoid.width + gap : avoid.x - gap - w;
|
|
4368
|
+
y = anchor.y - h / 2;
|
|
4369
|
+
for (const r of areas) y = clampSpan(y, h, r.y, r.y + r.height);
|
|
4370
|
+
} else {
|
|
4371
|
+
y = side === "bottom" ? avoid.y + avoid.height + gap : avoid.y - gap - h;
|
|
4372
|
+
x = anchor.x - w / 2;
|
|
4373
|
+
for (const r of areas) x = clampSpan(x, w, r.x, r.x + r.width);
|
|
4374
|
+
}
|
|
4375
|
+
return {
|
|
4376
|
+
x,
|
|
4377
|
+
y
|
|
4378
|
+
};
|
|
4379
|
+
};
|
|
4380
|
+
for (const side of order) {
|
|
4381
|
+
const p = beside(side, [inner]);
|
|
4382
|
+
if (inside(inner, p.x, p.y, w, h)) return {
|
|
4383
|
+
...p,
|
|
4384
|
+
fit: "stage"
|
|
4385
|
+
};
|
|
4386
|
+
}
|
|
4387
|
+
if (bounds) {
|
|
4388
|
+
let best = null;
|
|
4389
|
+
let bestCover = Infinity;
|
|
4390
|
+
for (const side of order) {
|
|
4391
|
+
const p = beside(side, [inner, bounds]);
|
|
4392
|
+
if (!inside(bounds, p.x, p.y, w, h)) continue;
|
|
4393
|
+
const cover = overlapArea$1({
|
|
4394
|
+
x: p.x,
|
|
4395
|
+
y: p.y,
|
|
4396
|
+
width: w,
|
|
4397
|
+
height: h
|
|
4398
|
+
}, stageRect);
|
|
4399
|
+
if (cover < bestCover - .5) {
|
|
4400
|
+
best = p;
|
|
4401
|
+
bestCover = cover;
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
if (best) return {
|
|
4405
|
+
...best,
|
|
4406
|
+
fit: "bounds"
|
|
4407
|
+
};
|
|
4408
|
+
}
|
|
4409
|
+
let best = {
|
|
4410
|
+
x: inner.x,
|
|
4411
|
+
y: inner.y
|
|
4412
|
+
};
|
|
4413
|
+
let bestCover = Infinity;
|
|
4414
|
+
for (const area of bounds ? [inner, bounds] : [inner]) {
|
|
4415
|
+
const spots = [
|
|
4416
|
+
...order.map((side) => beside(side, [area])),
|
|
4417
|
+
{
|
|
4418
|
+
x: area.x,
|
|
4419
|
+
y: area.y
|
|
4420
|
+
},
|
|
4421
|
+
{
|
|
4422
|
+
x: area.x + area.width - w,
|
|
4423
|
+
y: area.y
|
|
4424
|
+
},
|
|
4425
|
+
{
|
|
4426
|
+
x: area.x,
|
|
4427
|
+
y: area.y + area.height - h
|
|
4428
|
+
},
|
|
4429
|
+
{
|
|
4430
|
+
x: area.x + area.width - w,
|
|
4431
|
+
y: area.y + area.height - h
|
|
4432
|
+
}
|
|
4433
|
+
];
|
|
4434
|
+
for (const s of spots) {
|
|
4435
|
+
const x = clampSpan(s.x, w, area.x, area.x + area.width);
|
|
4436
|
+
const y = clampSpan(s.y, h, area.y, area.y + area.height);
|
|
4437
|
+
const cover = overlapArea$1({
|
|
4438
|
+
x,
|
|
4439
|
+
y,
|
|
4440
|
+
width: w,
|
|
4441
|
+
height: h
|
|
4442
|
+
}, avoid);
|
|
4443
|
+
if (cover < bestCover - .5) {
|
|
4444
|
+
best = {
|
|
4445
|
+
x,
|
|
4446
|
+
y
|
|
4447
|
+
};
|
|
4448
|
+
bestCover = cover;
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
return {
|
|
4453
|
+
...best,
|
|
4454
|
+
fit: "none"
|
|
4455
|
+
};
|
|
4456
|
+
};
|
|
4457
|
+
/** Opacity of the fill of canvas-drawn tooltips (a canvas cannot blur what is below) */
|
|
4458
|
+
var CANVAS_TOOLTIP_ALPHA = .86;
|
|
4459
|
+
//#endregion
|
|
4460
|
+
//#region ../charts/src/engine/hover-rect.ts
|
|
4461
|
+
/** Room around the marks (grown / lifted marks, halos) */
|
|
4462
|
+
var PAD = 3;
|
|
4463
|
+
/**
|
|
4464
|
+
* Box (stage pixels) of the hovered marks: bars from their base to their
|
|
4465
|
+
* end, candles and boxes from high to low, dots with their halo, the
|
|
4466
|
+
* hovered cell; clipped to the plot. `null` when nothing is drawn.
|
|
4467
|
+
*/
|
|
4468
|
+
var hoverRect = (model, sc, event, marks = hoverMarks(model, sc, event)) => {
|
|
4469
|
+
let box = null;
|
|
4470
|
+
for (const mark of marks) {
|
|
4471
|
+
if (mark.kind === "band" || mark.kind === "guide") continue;
|
|
4472
|
+
if (mark.kind === "dot") {
|
|
4473
|
+
const r = mark.size / 2 + 4;
|
|
4474
|
+
box = unionRect(box, {
|
|
4475
|
+
x: mark.x - r,
|
|
4476
|
+
y: mark.y - r,
|
|
4477
|
+
width: r * 2,
|
|
4478
|
+
height: r * 2
|
|
4479
|
+
});
|
|
4480
|
+
} else if ("width" in mark && "height" in mark) box = unionRect(box, mark);
|
|
4481
|
+
}
|
|
4482
|
+
for (const it of event.items) {
|
|
4483
|
+
const s = model.series[it.seriesIndex];
|
|
4484
|
+
if (!s || it.outlier !== void 0) continue;
|
|
4485
|
+
if (s.type !== "bar" && s.type !== "candlestick" && s.type !== "boxplot") continue;
|
|
4486
|
+
const i = it.index;
|
|
4487
|
+
const x = (s.cols.x[i] ?? it.x) + (s.type === "candlestick" ? 0 : s.barOffset);
|
|
4488
|
+
const c = sc.xToPx(x);
|
|
4489
|
+
const half = Math.max(s.barHalfWidth / Math.abs(sc.xPerPx), s.type === "bar" ? 1 : 3);
|
|
4490
|
+
const { high, low, y } = s.cols;
|
|
4491
|
+
const a = s.type === "bar" ? s.barBase?.[i] ?? model.baseline : high ? high[i] : y[i];
|
|
4492
|
+
const b = s.type === "bar" ? s.barTop?.[i] ?? y[i] : low ? low[i] : y[i];
|
|
4493
|
+
const pa = sc.yToPx(a);
|
|
4494
|
+
const pb = sc.yToPx(b);
|
|
4495
|
+
if (!Number.isFinite(c) || !Number.isFinite(pa) || !Number.isFinite(pb)) continue;
|
|
4496
|
+
const lo = Math.min(pa, pb);
|
|
4497
|
+
const len = Math.abs(pb - pa);
|
|
4498
|
+
box = unionRect(box, sc.horizontal ? {
|
|
4499
|
+
x: lo,
|
|
4500
|
+
y: c - half,
|
|
4501
|
+
width: len,
|
|
4502
|
+
height: half * 2
|
|
4503
|
+
} : {
|
|
4504
|
+
x: c - half,
|
|
4505
|
+
y: lo,
|
|
4506
|
+
width: half * 2,
|
|
4507
|
+
height: len
|
|
4508
|
+
});
|
|
4509
|
+
}
|
|
4510
|
+
if (!box) return null;
|
|
4511
|
+
const { plot } = sc;
|
|
4512
|
+
const x = Math.max(plot.x - PAD, box.x - PAD);
|
|
4513
|
+
const y = Math.max(plot.y - PAD, box.y - PAD);
|
|
4514
|
+
const right = Math.min(plot.x + plot.width + PAD, box.x + box.width + PAD);
|
|
4515
|
+
const bottom = Math.min(plot.y + plot.height + PAD, box.y + box.height + PAD);
|
|
4516
|
+
return right > x && bottom > y ? {
|
|
4517
|
+
x,
|
|
4518
|
+
y,
|
|
4519
|
+
width: right - x,
|
|
4520
|
+
height: bottom - y
|
|
4521
|
+
} : null;
|
|
4522
|
+
};
|
|
4523
|
+
//#endregion
|
|
4129
4524
|
//#region ../charts/src/engine/theme.ts
|
|
4130
4525
|
/** Used when no CSS variable is defined (e.g. without the Minerva stylesheet) */
|
|
4131
4526
|
var DEFAULT_PALETTE = [
|
|
@@ -4376,7 +4771,7 @@ var createChartController = (config) => {
|
|
|
4376
4771
|
height: legendHeight
|
|
4377
4772
|
};
|
|
4378
4773
|
};
|
|
4379
|
-
const tooltipLayout = (event, anchorX, anchorY) => {
|
|
4774
|
+
const tooltipLayout = (event, anchorX, anchorY, sc) => {
|
|
4380
4775
|
const tooltip = options.tooltip;
|
|
4381
4776
|
if (tooltip === false) return null;
|
|
4382
4777
|
const custom = typeof tooltip === "object" ? tooltip.render : void 0;
|
|
@@ -4409,10 +4804,22 @@ var createChartController = (config) => {
|
|
|
4409
4804
|
}
|
|
4410
4805
|
const boxW = Math.ceil(Math.max(w, 96) + 16 + 4);
|
|
4411
4806
|
const boxH = lines * lineHeight + 16;
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4807
|
+
const { x: left, y: top } = placeTooltip({
|
|
4808
|
+
anchor: {
|
|
4809
|
+
x: anchorX,
|
|
4810
|
+
y: anchorY
|
|
4811
|
+
},
|
|
4812
|
+
avoid: hoverRect(model, sc, event),
|
|
4813
|
+
size: {
|
|
4814
|
+
width: boxW,
|
|
4815
|
+
height: boxH
|
|
4816
|
+
},
|
|
4817
|
+
stage: {
|
|
4818
|
+
width,
|
|
4819
|
+
height
|
|
4820
|
+
},
|
|
4821
|
+
prefer: sc.horizontal ? "vertical" : "side"
|
|
4822
|
+
});
|
|
4416
4823
|
return {
|
|
4417
4824
|
content,
|
|
4418
4825
|
text,
|
|
@@ -4446,7 +4853,7 @@ var createChartController = (config) => {
|
|
|
4446
4853
|
const labelHeight = theme.fontSize + 4;
|
|
4447
4854
|
for (const a of annotations) {
|
|
4448
4855
|
if (a.type === "text" && a.text) {
|
|
4449
|
-
occupied.push(textBox(a, layout.scales, measure(a.text, theme.fontSize, a.weight), labelHeight));
|
|
4856
|
+
occupied.push(textBox$1(a, layout.scales, measure(a.text, theme.fontSize, a.weight), labelHeight));
|
|
4450
4857
|
continue;
|
|
4451
4858
|
}
|
|
4452
4859
|
if (a.type !== "rect") continue;
|
|
@@ -4478,7 +4885,7 @@ var createChartController = (config) => {
|
|
|
4478
4885
|
marks: hoverMarks(model, layout.scales, hover.event),
|
|
4479
4886
|
crosshairX: horizontal ? null : guide,
|
|
4480
4887
|
crosshairY: horizontal ? guide : null,
|
|
4481
|
-
tooltip: gesture?.kind === "pan" || gesture?.kind === "point" ? null : tooltipLayout(hover.event, hover.x, hover.y)
|
|
4888
|
+
tooltip: gesture?.kind === "pan" || gesture?.kind === "point" ? null : tooltipLayout(hover.event, hover.x, hover.y, layout.scales)
|
|
4482
4889
|
};
|
|
4483
4890
|
const colors = legend?.colors;
|
|
4484
4891
|
const it = hover.event.items[0];
|
|
@@ -6212,6 +6619,80 @@ var drawColorLegend = (ctx, ramp, scene) => {
|
|
|
6212
6619
|
ctx.stroke();
|
|
6213
6620
|
}
|
|
6214
6621
|
};
|
|
6622
|
+
/** Scale of a hovered heatmap cell (like the DOM host) */
|
|
6623
|
+
var CELL_LIFT = 1.16;
|
|
6624
|
+
/** A rounded rectangle grown by `d` on every side (shrunk when negative) */
|
|
6625
|
+
var grownRect = (ctx, r, radius, d) => {
|
|
6626
|
+
const w = Math.max(0, r.width + d * 2);
|
|
6627
|
+
const h = Math.max(0, r.height + d * 2);
|
|
6628
|
+
roundedRect(ctx, r.x - d, r.y - d, w, h, Math.max(0, Math.min(radius + d, w / 2, h / 2)));
|
|
6629
|
+
};
|
|
6630
|
+
/**
|
|
6631
|
+
* A lifted shape: the lift shadow under a halo of the background, an
|
|
6632
|
+
* optional accent line around the halo, the fill, a ring inside the edge
|
|
6633
|
+
*/
|
|
6634
|
+
var drawLifted = (ctx, r, radius, fill, backdrop, ring, halo, accent, theme) => {
|
|
6635
|
+
const background = toCss(theme.background);
|
|
6636
|
+
if (ring) {
|
|
6637
|
+
ctx.save();
|
|
6638
|
+
ctx.shadowColor = "rgba(15, 23, 42, 0.4)";
|
|
6639
|
+
ctx.shadowBlur = 14;
|
|
6640
|
+
ctx.shadowOffsetY = 5;
|
|
6641
|
+
grownRect(ctx, r, radius, halo + (accent?.width ?? 0));
|
|
6642
|
+
ctx.fillStyle = accent ? accent.color : background;
|
|
6643
|
+
ctx.fill();
|
|
6644
|
+
ctx.restore();
|
|
6645
|
+
if (accent) {
|
|
6646
|
+
grownRect(ctx, r, radius, halo);
|
|
6647
|
+
ctx.fillStyle = background;
|
|
6648
|
+
ctx.fill();
|
|
6649
|
+
}
|
|
6650
|
+
} else if (backdrop) {
|
|
6651
|
+
grownRect(ctx, r, radius, 0);
|
|
6652
|
+
ctx.fillStyle = background;
|
|
6653
|
+
ctx.fill();
|
|
6654
|
+
}
|
|
6655
|
+
grownRect(ctx, r, radius, 0);
|
|
6656
|
+
ctx.fillStyle = fill;
|
|
6657
|
+
ctx.fill();
|
|
6658
|
+
if (ring && ring.width > 0) {
|
|
6659
|
+
grownRect(ctx, r, radius, -ring.width / 2);
|
|
6660
|
+
ctx.lineWidth = ring.width;
|
|
6661
|
+
ctx.strokeStyle = ring.color;
|
|
6662
|
+
ctx.stroke();
|
|
6663
|
+
}
|
|
6664
|
+
};
|
|
6665
|
+
/** Hovered bars (thicker, clipped to the plot) and heatmap cells, lifted */
|
|
6666
|
+
var drawLiftedMarks = (ctx, marks, plot, theme) => {
|
|
6667
|
+
for (const mark of marks) if (mark.kind === "bar") {
|
|
6668
|
+
const r = liftedBarRect(mark);
|
|
6669
|
+
ctx.save();
|
|
6670
|
+
ctx.beginPath();
|
|
6671
|
+
ctx.rect(plot.x, plot.y, plot.width, plot.height);
|
|
6672
|
+
ctx.clip();
|
|
6673
|
+
const ring = mark.ring === null ? null : {
|
|
6674
|
+
color: mark.ring,
|
|
6675
|
+
width: Math.min(mark.backdrop ? 2 : 1.5, Math.max(0, (Math.min(r.width, r.height) - 2) / 4))
|
|
6676
|
+
};
|
|
6677
|
+
drawLifted(ctx, r, mark.radius, mark.color, mark.backdrop, ring, 1.5, null, theme);
|
|
6678
|
+
ctx.restore();
|
|
6679
|
+
} else if (mark.kind === "cell") {
|
|
6680
|
+
const w = mark.width * CELL_LIFT;
|
|
6681
|
+
const h = mark.height * CELL_LIFT;
|
|
6682
|
+
drawLifted(ctx, {
|
|
6683
|
+
x: mark.x + (mark.width - w) / 2,
|
|
6684
|
+
y: mark.y + (mark.height - h) / 2,
|
|
6685
|
+
width: w,
|
|
6686
|
+
height: h
|
|
6687
|
+
}, Math.max(2, mark.radius) * CELL_LIFT, mark.color, false, {
|
|
6688
|
+
color: mark.ring,
|
|
6689
|
+
width: Math.min(2, Math.min(w, h) / 5)
|
|
6690
|
+
}, 2, {
|
|
6691
|
+
color: toCss(theme.primary, .6),
|
|
6692
|
+
width: 1.5
|
|
6693
|
+
}, theme);
|
|
6694
|
+
}
|
|
6695
|
+
};
|
|
6215
6696
|
/** Draws a scene (legend, axes, data, labels, hover) on a 2D context */
|
|
6216
6697
|
var drawScene = (ctx, scene, items, origin, pixelRatio, painter = createCanvas2dPainter(ctx)) => {
|
|
6217
6698
|
const { width, height, theme, model, view, layout } = scene;
|
|
@@ -6258,10 +6739,11 @@ var drawScene = (ctx, scene, items, origin, pixelRatio, painter = createCanvas2d
|
|
|
6258
6739
|
}
|
|
6259
6740
|
ctx.globalAlpha = 1;
|
|
6260
6741
|
}
|
|
6742
|
+
const lifted = scene.hover?.marks.some((m) => m.kind === "bar") ?? false;
|
|
6261
6743
|
for (const mark of scene.hover?.marks ?? []) {
|
|
6262
6744
|
if (mark.kind !== "band") continue;
|
|
6263
6745
|
roundedRect(ctx, mark.x, mark.y, mark.width, mark.height, 6);
|
|
6264
|
-
ctx.fillStyle = toCss(theme.text, .06);
|
|
6746
|
+
ctx.fillStyle = toCss(theme.text, lifted ? .04 : .06);
|
|
6265
6747
|
ctx.fill();
|
|
6266
6748
|
}
|
|
6267
6749
|
if (scales.horizontal) drawPrimitives(ctx, scenePrimitives(scene), plot);
|
|
@@ -6292,6 +6774,11 @@ var drawScene = (ctx, scene, items, origin, pixelRatio, painter = createCanvas2d
|
|
|
6292
6774
|
ctx.fillStyle = toCss(theme.text, .06);
|
|
6293
6775
|
ctx.fillRect(mark.x, mark.y, mark.width, mark.height);
|
|
6294
6776
|
}
|
|
6777
|
+
if (lifted) {
|
|
6778
|
+
ctx.fillStyle = toCss(theme.background, .42);
|
|
6779
|
+
ctx.fillRect(plot.x, plot.y, plot.width, plot.height);
|
|
6780
|
+
}
|
|
6781
|
+
if (scene.hover) drawLiftedMarks(ctx, scene.hover.marks, plot, theme);
|
|
6295
6782
|
drawForeground(ctx, {
|
|
6296
6783
|
width,
|
|
6297
6784
|
height,
|
|
@@ -6353,27 +6840,7 @@ var drawScene = (ctx, scene, items, origin, pixelRatio, painter = createCanvas2d
|
|
|
6353
6840
|
ctx.restore();
|
|
6354
6841
|
}
|
|
6355
6842
|
for (const mark of hover.marks) {
|
|
6356
|
-
if (mark.kind === "band" || mark.kind === "guide") continue;
|
|
6357
|
-
if (mark.kind === "cell") {
|
|
6358
|
-
const grow = Math.min(3, mark.width * .06, mark.height * .06);
|
|
6359
|
-
const r = Math.max(2, mark.radius);
|
|
6360
|
-
ctx.save();
|
|
6361
|
-
ctx.shadowColor = "rgba(15, 23, 42, 0.4)";
|
|
6362
|
-
ctx.shadowBlur = 12;
|
|
6363
|
-
ctx.shadowOffsetY = 4;
|
|
6364
|
-
roundedRect(ctx, mark.x - grow, mark.y - grow, mark.width + grow * 2, mark.height + grow * 2, r);
|
|
6365
|
-
ctx.fillStyle = mark.color;
|
|
6366
|
-
ctx.fill();
|
|
6367
|
-
ctx.restore();
|
|
6368
|
-
const inset = Math.min(2.75, mark.width / 5, mark.height / 5);
|
|
6369
|
-
if (mark.width > 8 && mark.height > 8) {
|
|
6370
|
-
roundedRect(ctx, mark.x + inset, mark.y + inset, mark.width - inset * 2, mark.height - inset * 2, Math.max(1, r - inset / 2));
|
|
6371
|
-
ctx.lineWidth = 1.5;
|
|
6372
|
-
ctx.strokeStyle = mark.ring;
|
|
6373
|
-
ctx.stroke();
|
|
6374
|
-
}
|
|
6375
|
-
continue;
|
|
6376
|
-
}
|
|
6843
|
+
if (mark.kind === "band" || mark.kind === "guide" || mark.kind === "bar" || mark.kind === "cell") continue;
|
|
6377
6844
|
ctx.beginPath();
|
|
6378
6845
|
ctx.arc(mark.x, mark.y, mark.size / 2 + 4, 0, Math.PI * 2);
|
|
6379
6846
|
ctx.fillStyle = mark.halo;
|
|
@@ -6407,7 +6874,7 @@ var drawScene = (ctx, scene, items, origin, pixelRatio, painter = createCanvas2d
|
|
|
6407
6874
|
ctx.shadowBlur = 16;
|
|
6408
6875
|
ctx.shadowOffsetY = 6;
|
|
6409
6876
|
roundedRect(ctx, box.x, box.y, box.width, box.height, 8);
|
|
6410
|
-
ctx.fillStyle = toCss(theme.background);
|
|
6877
|
+
ctx.fillStyle = toCss(theme.background, CANVAS_TOOLTIP_ALPHA);
|
|
6411
6878
|
ctx.fill();
|
|
6412
6879
|
ctx.restore();
|
|
6413
6880
|
roundedRect(ctx, box.x + .5, box.y + .5, box.width - 1, box.height - 1, 8);
|
|
@@ -8191,7 +8658,7 @@ var drawShapeTooltip = (ctx, theme, box) => {
|
|
|
8191
8658
|
ctx.shadowBlur = 16;
|
|
8192
8659
|
ctx.shadowOffsetY = 6;
|
|
8193
8660
|
roundRectPath(ctx, box.x, box.y, box.width, box.height, 8);
|
|
8194
|
-
ctx.fillStyle = toCss(theme.background);
|
|
8661
|
+
ctx.fillStyle = toCss(theme.background, CANVAS_TOOLTIP_ALPHA);
|
|
8195
8662
|
ctx.fill();
|
|
8196
8663
|
ctx.restore();
|
|
8197
8664
|
roundRectPath(ctx, box.x + .5, box.y + .5, box.width - 1, box.height - 1, 8);
|
|
@@ -13019,40 +13486,163 @@ var layoutShapeLegend = (entries, width, position, chartHeight, theme, measure)
|
|
|
13019
13486
|
var TIP_MARGIN = 6;
|
|
13020
13487
|
/** Distance between a tooltip and its anchor */
|
|
13021
13488
|
var TIP_GAP = 14;
|
|
13489
|
+
/** Half size of the box around the pointer a tooltip keeps off */
|
|
13490
|
+
var TIP_POINTER = 6;
|
|
13022
13491
|
var overlapArea = (a, b) => Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x)) * Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
|
|
13492
|
+
/** Box of a circle arc (stroke half width included) */
|
|
13493
|
+
var arcBox = (cx, cy, r0, r1, a0, a1) => {
|
|
13494
|
+
const xs = [];
|
|
13495
|
+
const ys = [];
|
|
13496
|
+
const add = (a) => {
|
|
13497
|
+
for (const r of [r0, r1]) {
|
|
13498
|
+
xs.push(cx + r * Math.cos(a));
|
|
13499
|
+
ys.push(cy + r * Math.sin(a));
|
|
13500
|
+
}
|
|
13501
|
+
};
|
|
13502
|
+
add(a0);
|
|
13503
|
+
add(a1);
|
|
13504
|
+
const start = Math.ceil(a0 / (Math.PI / 2));
|
|
13505
|
+
for (let k = start; k * (Math.PI / 2) <= a1; k++) add(k * (Math.PI / 2));
|
|
13506
|
+
const x = Math.min(...xs);
|
|
13507
|
+
const y = Math.min(...ys);
|
|
13508
|
+
return {
|
|
13509
|
+
x,
|
|
13510
|
+
y,
|
|
13511
|
+
width: Math.max(...xs) - x,
|
|
13512
|
+
height: Math.max(...ys) - y
|
|
13513
|
+
};
|
|
13514
|
+
};
|
|
13515
|
+
/** Box of the marks of an item (slice, tile, arc, polygon, flow, ...) */
|
|
13516
|
+
var itemBox = (scene, index) => {
|
|
13517
|
+
let box = null;
|
|
13518
|
+
const add = (b) => {
|
|
13519
|
+
box = unionRect(box, b);
|
|
13520
|
+
};
|
|
13521
|
+
for (const node of scene.nodes) {
|
|
13522
|
+
if (node.hit !== index) continue;
|
|
13523
|
+
switch (node.kind) {
|
|
13524
|
+
case "rect":
|
|
13525
|
+
add(node);
|
|
13526
|
+
break;
|
|
13527
|
+
case "sector":
|
|
13528
|
+
add(arcBox(node.cx, node.cy, node.r0, node.r1, node.a0, node.a1));
|
|
13529
|
+
break;
|
|
13530
|
+
case "arc":
|
|
13531
|
+
add(arcBox(node.cx, node.cy, node.r - node.width / 2, node.r + node.width / 2, node.a0, node.a1));
|
|
13532
|
+
break;
|
|
13533
|
+
case "polygon": {
|
|
13534
|
+
const p = node.points;
|
|
13535
|
+
let x0 = Infinity;
|
|
13536
|
+
let y0 = Infinity;
|
|
13537
|
+
let x1 = -Infinity;
|
|
13538
|
+
let y1 = -Infinity;
|
|
13539
|
+
for (let i = 0; i + 1 < p.length; i += 2) {
|
|
13540
|
+
x0 = Math.min(x0, p[i]);
|
|
13541
|
+
x1 = Math.max(x1, p[i]);
|
|
13542
|
+
y0 = Math.min(y0, p[i + 1]);
|
|
13543
|
+
y1 = Math.max(y1, p[i + 1]);
|
|
13544
|
+
}
|
|
13545
|
+
if (x1 >= x0) add({
|
|
13546
|
+
x: x0,
|
|
13547
|
+
y: y0,
|
|
13548
|
+
width: x1 - x0,
|
|
13549
|
+
height: y1 - y0
|
|
13550
|
+
});
|
|
13551
|
+
break;
|
|
13552
|
+
}
|
|
13553
|
+
case "circle":
|
|
13554
|
+
add({
|
|
13555
|
+
x: node.cx - node.r,
|
|
13556
|
+
y: node.cy - node.r,
|
|
13557
|
+
width: node.r * 2,
|
|
13558
|
+
height: node.r * 2
|
|
13559
|
+
});
|
|
13560
|
+
break;
|
|
13561
|
+
case "link": {
|
|
13562
|
+
const half = node.width / 2;
|
|
13563
|
+
const y0 = Math.min(node.y0, node.y1) - half;
|
|
13564
|
+
add({
|
|
13565
|
+
x: Math.min(node.x0, node.x1),
|
|
13566
|
+
y: y0,
|
|
13567
|
+
width: Math.abs(node.x1 - node.x0),
|
|
13568
|
+
height: Math.abs(node.y1 - node.y0) + node.width
|
|
13569
|
+
});
|
|
13570
|
+
break;
|
|
13571
|
+
}
|
|
13572
|
+
}
|
|
13573
|
+
}
|
|
13574
|
+
return box;
|
|
13575
|
+
};
|
|
13576
|
+
/** Approximate box of a text node (unrotated) */
|
|
13577
|
+
var textBox = (node) => {
|
|
13578
|
+
const size = Number(/(\d+(?:\.\d+)?)px/.exec(node.font)?.[1] ?? 12);
|
|
13579
|
+
const width = estimateText(node.text, node.font);
|
|
13580
|
+
return {
|
|
13581
|
+
x: node.align === "center" ? node.x - width / 2 : node.align === "right" ? node.x - width : node.x,
|
|
13582
|
+
y: node.baseline === "top" ? node.y : node.baseline === "middle" ? node.y - size / 2 : node.y - size,
|
|
13583
|
+
width,
|
|
13584
|
+
height: size
|
|
13585
|
+
};
|
|
13586
|
+
};
|
|
13023
13587
|
/**
|
|
13024
13588
|
* Top-left corner of a `w` x `h` tooltip anchored at `anchor`, inside a
|
|
13025
|
-
* `width` x `height` drawing
|
|
13026
|
-
*
|
|
13027
|
-
*
|
|
13589
|
+
* `width` x `height` drawing (or overflowing it into `bounds`, see
|
|
13590
|
+
* `placeTooltip`, when the drawing has no room). Candidates around the
|
|
13591
|
+
* pointer and around the hovered item are scored: never over the pointer
|
|
13592
|
+
* (nor the hovered radar vertex), then covering as little as possible of the
|
|
13593
|
+
* hovered item and of the texts, close to the pointer; radar tooltips lean
|
|
13594
|
+
* outward.
|
|
13028
13595
|
*/
|
|
13029
|
-
var placeShapeTooltip = (scene, hover, anchor, w, h, width, height) => {
|
|
13596
|
+
var placeShapeTooltip = (scene, hover, anchor, w, h, width, height, bounds = null) => {
|
|
13030
13597
|
const m = TIP_MARGIN;
|
|
13598
|
+
const { x: ax, y: ay } = anchor;
|
|
13599
|
+
const t = TIP_POINTER;
|
|
13600
|
+
const fallback = () => placeTooltip({
|
|
13601
|
+
anchor,
|
|
13602
|
+
size: {
|
|
13603
|
+
width: w,
|
|
13604
|
+
height: h
|
|
13605
|
+
},
|
|
13606
|
+
stage: {
|
|
13607
|
+
width,
|
|
13608
|
+
height
|
|
13609
|
+
},
|
|
13610
|
+
margin: m,
|
|
13611
|
+
avoid: {
|
|
13612
|
+
x: ax - t,
|
|
13613
|
+
y: ay - t,
|
|
13614
|
+
width: 12,
|
|
13615
|
+
height: 12
|
|
13616
|
+
},
|
|
13617
|
+
bounds,
|
|
13618
|
+
gap: 10
|
|
13619
|
+
});
|
|
13620
|
+
if (!scene || !hover || w + 12 > width || h + 12 > height) return fallback();
|
|
13031
13621
|
const fit = (x, y) => ({
|
|
13032
13622
|
x: Math.max(m, Math.min(x, width - w - m)),
|
|
13033
13623
|
y: Math.max(m, Math.min(y, height - h - m))
|
|
13034
13624
|
});
|
|
13035
|
-
const
|
|
13036
|
-
const radar = scene?.radar;
|
|
13037
|
-
if (!radar || !scene || !hover) {
|
|
13038
|
-
let x = ax + 16;
|
|
13039
|
-
if (x + w > width - m) x = ax - 16 - w;
|
|
13040
|
-
return fit(x, ay - h / 2);
|
|
13041
|
-
}
|
|
13042
|
-
const avoid = [[ax, ay]];
|
|
13625
|
+
const keep = [[ax, ay]];
|
|
13043
13626
|
const item = scene.items.find((it) => it.index === hover.index);
|
|
13044
13627
|
const vertex = item?.indicators && hover.indicator !== void 0 ? item.indicators[hover.indicator] : void 0;
|
|
13045
|
-
if (vertex)
|
|
13046
|
-
const
|
|
13047
|
-
|
|
13048
|
-
|
|
13049
|
-
const
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13628
|
+
if (vertex) keep.push([vertex.x, vertex.y]);
|
|
13629
|
+
const target = itemBox(scene, hover.index);
|
|
13630
|
+
const texts = [...scene.radar?.labels ?? []];
|
|
13631
|
+
for (const node of scene.nodes) if (node.kind === "text" && !node.rotate && node.indicator === void 0) texts.push(textBox(node));
|
|
13632
|
+
const radar = scene.radar;
|
|
13633
|
+
let dx = 0;
|
|
13634
|
+
let dy = 0;
|
|
13635
|
+
if (radar) {
|
|
13636
|
+
dx = ax - radar.cx;
|
|
13637
|
+
dy = ay - radar.cy;
|
|
13638
|
+
const len = Math.hypot(dx, dy);
|
|
13639
|
+
if (len > 1) {
|
|
13640
|
+
dx /= len;
|
|
13641
|
+
dy /= len;
|
|
13642
|
+
} else {
|
|
13643
|
+
dx = 1;
|
|
13644
|
+
dy = 0;
|
|
13645
|
+
}
|
|
13056
13646
|
}
|
|
13057
13647
|
const g = TIP_GAP;
|
|
13058
13648
|
const candidates = [
|
|
@@ -13065,7 +13655,13 @@ var placeShapeTooltip = (scene, hover, anchor, w, h, width, height) => {
|
|
|
13065
13655
|
[ax - g - w, ay - g - h],
|
|
13066
13656
|
[ax - g - w, ay + g]
|
|
13067
13657
|
];
|
|
13068
|
-
|
|
13658
|
+
if (target) {
|
|
13659
|
+
const gap = 10;
|
|
13660
|
+
candidates.push([target.x + target.width + gap, ay - h / 2], [target.x - gap - w, ay - h / 2], [ax - w / 2, target.y - gap - h], [ax - w / 2, target.y + target.height + gap]);
|
|
13661
|
+
}
|
|
13662
|
+
candidates.push([m, ay - h / 2], [width - w - m, ay - h / 2], [ax - w / 2, m], [ax - w / 2, height - h - m]);
|
|
13663
|
+
const area = w * h;
|
|
13664
|
+
let best = null;
|
|
13069
13665
|
let bestScore = Infinity;
|
|
13070
13666
|
for (const [cx0, cy0] of candidates) {
|
|
13071
13667
|
const p = fit(cx0, cy0);
|
|
@@ -13076,20 +13672,25 @@ var placeShapeTooltip = (scene, hover, anchor, w, h, width, height) => {
|
|
|
13076
13672
|
height: h
|
|
13077
13673
|
};
|
|
13078
13674
|
let score = 0;
|
|
13079
|
-
for (const [px, py] of
|
|
13080
|
-
|
|
13675
|
+
for (const [px, py] of keep) if (px > box.x - 8 && px < box.x + w + 8 && py > box.y - 8 && py < box.y + h + 8) score += 1e6;
|
|
13676
|
+
if (target) score += overlapArea(box, target) / area * 400;
|
|
13677
|
+
for (const text of texts) score += overlapArea(box, text) * 4;
|
|
13081
13678
|
const ox = p.x + w / 2 - ax;
|
|
13082
13679
|
const oy = p.y + h / 2 - ay;
|
|
13083
13680
|
const dist = Math.hypot(ox, oy) || 1;
|
|
13084
|
-
score += dist * .
|
|
13085
|
-
score -= (ox * dx + oy * dy) / dist * 40;
|
|
13681
|
+
score += dist * .35;
|
|
13682
|
+
if (radar) score -= (ox * dx + oy * dy) / dist * 40;
|
|
13086
13683
|
score += Math.abs(p.x - cx0) + Math.abs(p.y - cy0);
|
|
13087
13684
|
if (score < bestScore) {
|
|
13088
13685
|
bestScore = score;
|
|
13089
13686
|
best = p;
|
|
13090
13687
|
}
|
|
13091
13688
|
}
|
|
13092
|
-
return
|
|
13689
|
+
if (!best || bestScore >= 1e6) return fallback();
|
|
13690
|
+
return {
|
|
13691
|
+
...best,
|
|
13692
|
+
fit: "stage"
|
|
13693
|
+
};
|
|
13093
13694
|
};
|
|
13094
13695
|
/** Box of a tooltip drawn on a canvas, next to the anchor and inside the drawing */
|
|
13095
13696
|
var layoutShapeTooltip = (value, anchorX, anchorY, width, height, theme, measure, scene = null, hover = null) => {
|
|
@@ -13706,6 +14307,12 @@ Object.defineProperty(exports, "histogramSeries", {
|
|
|
13706
14307
|
return histogramSeries;
|
|
13707
14308
|
}
|
|
13708
14309
|
});
|
|
14310
|
+
Object.defineProperty(exports, "liftedBarRect", {
|
|
14311
|
+
enumerable: true,
|
|
14312
|
+
get: function() {
|
|
14313
|
+
return liftedBarRect;
|
|
14314
|
+
}
|
|
14315
|
+
});
|
|
13709
14316
|
Object.defineProperty(exports, "lineOf", {
|
|
13710
14317
|
enumerable: true,
|
|
13711
14318
|
get: function() {
|
|
@@ -13793,7 +14400,7 @@ Object.defineProperty(exports, "sparklineOptions", {
|
|
|
13793
14400
|
Object.defineProperty(exports, "textBox", {
|
|
13794
14401
|
enumerable: true,
|
|
13795
14402
|
get: function() {
|
|
13796
|
-
return textBox;
|
|
14403
|
+
return textBox$1;
|
|
13797
14404
|
}
|
|
13798
14405
|
});
|
|
13799
14406
|
Object.defineProperty(exports, "toCss", {
|