@mstone6969/graph 0.1.0 → 0.2.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/README.md +111 -14
- package/dist/ExcelWriter-3ZSYUH4T.cjs +154 -0
- package/dist/GraphType.js +24 -38
- package/dist/PdfWriter-6HJFYMCO.cjs +205 -0
- package/dist/catalog-YWX6FHRM.cjs +8 -0
- package/dist/catalog.js +5 -1
- package/dist/charts/bars/index.js +2 -12
- package/dist/charts/comparison/EffectForestPlot.js +1 -1
- package/dist/charts/comparison/index.js +1 -8
- package/dist/charts/demography/Nomogram.js +3 -3
- package/dist/charts/density/HexbinChart.js +6 -4
- package/dist/charts/diagrams/ArchitectureCanvas.js +68 -0
- package/dist/charts/diagrams/ArchitectureDiagram.js +331 -0
- package/dist/charts/diagrams/ArchitectureLayout.js +106 -0
- package/dist/charts/diagrams/ArchitectureLinkLabels.js +78 -0
- package/dist/charts/diagrams/ArchitectureModel.js +108 -0
- package/dist/charts/diagrams/ArchitectureRouter.js +348 -0
- package/dist/charts/diagrams/ArchitectureSamples.js +312 -0
- package/dist/charts/diagrams/ArchitectureShapes.js +130 -0
- package/dist/charts/diagrams/ArchitectureText.js +25 -0
- package/dist/charts/diagrams/DiagramText.js +38 -0
- package/dist/charts/diagrams/EntityRelationshipDiagram.js +465 -0
- package/dist/charts/diagrams/ErCrowsFoot.js +33 -0
- package/dist/charts/diagrams/ErSamples.js +205 -0
- package/dist/charts/diagrams/ErSchema.js +122 -0
- package/dist/charts/diagrams/LogicCircuit.js +154 -0
- package/dist/charts/diagrams/LogicCircuitLayout.js +600 -0
- package/dist/charts/diagrams/LogicCircuitSamples.js +66 -0
- package/dist/charts/diagrams/LogicGateDiagram.js +216 -0
- package/dist/charts/diagrams/LogicGateSymbol.js +113 -0
- package/dist/charts/diagrams/NetworkTopologyDiagram.js +247 -0
- package/dist/charts/diagrams/SequenceDiagram.js +351 -0
- package/dist/charts/diagrams/SequenceLayout.js +422 -0
- package/dist/charts/diagrams/SequenceModel.js +221 -0
- package/dist/charts/diagrams/TopologyLayout.js +143 -0
- package/dist/charts/diagrams/architectureEnums.js +50 -0
- package/dist/charts/diagrams/index.js +26 -0
- package/dist/charts/diagrams/logicEnums.js +42 -0
- package/dist/charts/diagrams/sequenceEnums.js +40 -0
- package/dist/charts/distribution/HistogramChart.js +25 -3
- package/dist/charts/distribution/index.js +1 -3
- package/dist/charts/earthscience/HovmollerDiagram.js +477 -0
- package/dist/charts/earthscience/HovmollerEnums.js +42 -0
- package/dist/charts/earthscience/HovmollerField.js +180 -0
- package/dist/charts/earthscience/HovmollerSample.js +40 -0
- package/dist/charts/earthscience/HovmollerSpeed.js +71 -0
- package/dist/charts/earthscience/index.js +10 -2
- package/dist/charts/engineering/ConstellationDiagram.js +26 -6
- package/dist/charts/engineering/EyeDiagram.js +2 -1
- package/dist/charts/engineering/PoleZeroPlot.js +32 -8
- package/dist/charts/financial/RelativeStrengthRotation.js +434 -0
- package/dist/charts/financial/RelativeStrengthRotationMath.js +71 -0
- package/dist/charts/financial/RelativeStrengthRotationSamples.js +27 -0
- package/dist/charts/financial/index.js +5 -3
- package/dist/charts/flow/index.js +1 -5
- package/dist/charts/frameworks/AnsoffMatrix.js +523 -0
- package/dist/charts/frameworks/FrameworkText.js +98 -0
- package/dist/charts/frameworks/NpsGauge.js +279 -0
- package/dist/charts/frameworks/NpsSurvey.js +58 -0
- package/dist/charts/frameworks/PerceptualMap.js +419 -0
- package/dist/charts/frameworks/PerceptualProjection.js +142 -0
- package/dist/charts/frameworks/PositioningSamples.js +191 -0
- package/dist/charts/frameworks/RaciMatrix.js +458 -0
- package/dist/charts/frameworks/RaciPlan.js +81 -0
- package/dist/charts/frameworks/RaciSamples.js +76 -0
- package/dist/charts/frameworks/SwotMatrix.js +358 -0
- package/dist/charts/frameworks/SwotSamples.js +85 -0
- package/dist/charts/frameworks/VanWestendorpChart.js +187 -0
- package/dist/charts/frameworks/VanWestendorpSurvey.js +197 -0
- package/dist/charts/frameworks/index.js +26 -0
- package/dist/charts/frameworks/raciEnums.js +30 -0
- package/dist/charts/frameworks/swotEnums.js +34 -0
- package/dist/charts/genomics/CircosPlot.js +1 -1
- package/dist/charts/geoflows/GeoFlowChart.js +2 -2
- package/dist/charts/geoflows/GeoFlowSample.js +2 -1
- package/dist/charts/geomaps/FloorPlanMap.js +324 -0
- package/dist/charts/geomaps/FloorPlanModel.js +179 -0
- package/dist/charts/geomaps/FloorPlanSamples.js +416 -0
- package/dist/charts/geomaps/GeoLand.js +2 -2
- package/dist/charts/geomaps/GeoSamples.js +2 -1
- package/dist/charts/geomaps/GraticuleMap.js +2 -2
- package/dist/charts/geomaps/RoomLabelFit.js +200 -0
- package/dist/charts/geomaps/TissotMap.js +2 -2
- package/dist/charts/geomaps/index.js +8 -4
- package/dist/charts/hierarchy/SunburstChart.js +17 -10
- package/dist/charts/hierarchy/index.js +2 -11
- package/dist/charts/indicators/BulletChart.js +8 -2
- package/dist/charts/indicators/SpeedometerChart.js +1 -1
- package/dist/charts/indicators/ThermometerChart.js +1 -1
- package/dist/charts/lifescience/LoessFit.js +113 -0
- package/dist/charts/lifescience/MaPlot.js +337 -0
- package/dist/charts/lifescience/MaPlotSamples.js +35 -0
- package/dist/charts/lifescience/OncoGlyph.js +137 -0
- package/dist/charts/lifescience/OncoPrint.js +373 -0
- package/dist/charts/lifescience/OncoPrintMatrix.js +107 -0
- package/dist/charts/lifescience/OncoPrintSamples.js +142 -0
- package/dist/charts/lifescience/PedigreeChart.js +306 -0
- package/dist/charts/lifescience/PedigreeLayout.js +261 -0
- package/dist/charts/lifescience/PedigreeRows.js +70 -0
- package/dist/charts/lifescience/PedigreeSamples.js +161 -0
- package/dist/charts/lifescience/PedigreeSymbol.js +47 -0
- package/dist/charts/lifescience/RamachandranPlot.js +364 -0
- package/dist/charts/lifescience/RamachandranRegions.js +2712 -0
- package/dist/charts/lifescience/RamachandranSamples.js +235 -0
- package/dist/charts/lifescience/index.js +25 -0
- package/dist/charts/lifescience/pedigreeEnums.js +21 -0
- package/dist/charts/lifescience/ramachandranEnums.js +34 -0
- package/dist/charts/linesareas/DualAxisChart.js +1 -1
- package/dist/charts/linesareas/LineEnums.js +1 -15
- package/dist/charts/linesareas/MultiLineChart.js +1 -1
- package/dist/charts/linesareas/TrendChart.js +15 -5
- package/dist/charts/linesareas/index.js +2 -6
- package/dist/charts/massive/MassiveChart.js +3 -2
- package/dist/charts/mathematical/FourierSeriesChart.js +7 -1
- package/dist/charts/mathematical/index.js +2 -6
- package/dist/charts/matrix/GridPlot.js +27 -2
- package/dist/charts/matrix/TilegramChart.js +2 -2
- package/dist/charts/mlcharts/AttentionGrid.js +79 -0
- package/dist/charts/mlcharts/AttentionHeatmap.js +329 -0
- package/dist/charts/mlcharts/AttentionModel.js +132 -0
- package/dist/charts/mlcharts/AttentionSamples.js +82 -0
- package/dist/charts/mlcharts/CellNumber.js +8 -0
- package/dist/charts/mlcharts/CurveDiagnosticSamples.js +95 -0
- package/dist/charts/mlcharts/CurvePickMark.js +31 -0
- package/dist/charts/mlcharts/DecisionBoundaryChart.js +316 -0
- package/dist/charts/mlcharts/DecisionSamples.js +60 -0
- package/dist/charts/mlcharts/DecisionSurface.js +130 -0
- package/dist/charts/mlcharts/DistinctTicks.js +13 -0
- package/dist/charts/mlcharts/ElbowMath.js +153 -0
- package/dist/charts/mlcharts/ElbowPlot.js +273 -0
- package/dist/charts/mlcharts/ExplainabilitySamples.js +70 -0
- package/dist/charts/mlcharts/FeatureImportanceChart.js +150 -0
- package/dist/charts/mlcharts/ImportanceRanking.js +98 -0
- package/dist/charts/mlcharts/LearningCurveChart.js +276 -0
- package/dist/charts/mlcharts/LearningCurveMath.js +92 -0
- package/dist/charts/mlcharts/PartialDependence.js +92 -0
- package/dist/charts/mlcharts/PartialDependencePlot.js +225 -0
- package/dist/charts/mlcharts/WideLabel.js +28 -0
- package/dist/charts/mlcharts/curveDiagnosticEnums.js +37 -0
- package/dist/charts/mlcharts/index.js +32 -0
- package/dist/charts/proportion/index.js +5 -9
- package/dist/charts/scatter/CategoryScatterBase.js +1 -4
- package/dist/charts/scatter/ConnectedScatter.js +7 -4
- package/dist/charts/scatter/MarginalScatter.js +13 -8
- package/dist/charts/scatter/index.js +4 -17
- package/dist/charts/scientific/SpectrogramChart.js +9 -3
- package/dist/charts/scientific/index.js +2 -2
- package/dist/charts/signals/index.js +3 -13
- package/dist/charts/statistics/LorenzCurve.js +7 -4
- package/dist/charts/threebars/Candlestick3DChart.js +18 -2
- package/dist/charts/threeglobe/GlobeChart.js +2 -2
- package/dist/charts/threeglobe/GlobeSamples.js +2 -1
- package/dist/charts/threeglobe/RingGlobe.js +1 -1
- package/dist/charts/threenetwork/ConeTreeChart.js +2 -1
- package/dist/charts/threenetwork/Gantt3DChart.js +3 -2
- package/dist/charts/threesurfaces/Ridgeline3DChart.js +22 -4
- package/dist/charts/threevolume/LorenzAttractor.js +19 -0
- package/dist/charts/threevolume/VolumeRaycastChart.js +4 -3
- package/dist/charts/threevolume/volumeEnums.js +1 -0
- package/dist/charts/timeline/index.js +2 -10
- package/dist/chunk-753UBLV2.cjs +33 -0
- package/dist/chunk-VX54A7KA.cjs +17 -0
- package/dist/{chunk-W5GAAAZY.cjs → chunk-WAODDPYX.cjs} +24874 -17620
- package/dist/chunk-Z3JPIJSC.cjs +338 -0
- package/dist/core/CartesianChart.js +87 -11
- package/dist/core/Chart.js +63 -15
- package/dist/core/Chart2D.js +54 -17
- package/dist/core/Chart3D.js +56 -20
- package/dist/core/ChartExportError.js +2 -0
- package/dist/core/ChartExporter.js +50 -8
- package/dist/core/ColumnCheck.js +53 -0
- package/dist/core/Context2D.js +29 -15
- package/dist/core/Context3D.js +7 -2
- package/dist/core/ExcelWriter.js +152 -0
- package/dist/core/ExportText.js +28 -0
- package/dist/core/MarkOrigin.js +88 -0
- package/dist/core/MarkStyle.js +294 -0
- package/dist/core/Palette.js +27 -7
- package/dist/core/PdfPage.js +13 -0
- package/dist/core/PdfWriter.js +245 -0
- package/dist/core/Scales.js +52 -2
- package/dist/core/SceneOverlay.js +4 -4
- package/dist/core/TextFit.js +37 -0
- package/dist/core/Theme.js +28 -1
- package/dist/core/enums.js +33 -1
- package/dist/core/pathMorph.js +15 -0
- package/dist/data/SampleData.js +1 -17
- package/dist/data/WorldSample.js +23 -0
- package/dist/index.cjs +810 -691
- package/dist/index.d.cts +8390 -2925
- package/dist/index.d.ts +8392 -2927
- package/dist/index.js +106 -56
- package/dist/react/ChartView.js +18 -8
- package/dist/react/GraphHandle.js +2 -0
- package/dist/react/components.js +74 -116
- package/package.json +13 -3
- package/dist/catalog-4UP2YFNR.cjs +0 -7
- package/dist/charts/bars/BulletBar.js +0 -184
- package/dist/charts/bars/ErrorBarColumn.js +0 -147
- package/dist/charts/bars/HistogramBar.js +0 -217
- package/dist/charts/bars/MarimekkoBar.js +0 -191
- package/dist/charts/bars/ParetoBar.js +0 -143
- package/dist/charts/comparison/PanelChart.js +0 -70
- package/dist/charts/comparison/SmallMultipleBars.js +0 -134
- package/dist/charts/comparison/SpanChart.js +0 -118
- package/dist/charts/comparison/TrellisChart.js +0 -158
- package/dist/charts/distribution/RidgeSlabs3DChart.js +0 -169
- package/dist/charts/financial/Candlestick3D.js +0 -157
- package/dist/charts/flow/BumpFlow.js +0 -212
- package/dist/charts/flow/StreamFlow.js +0 -174
- package/dist/charts/geomaps/ThiessenMap.js +0 -106
- package/dist/charts/hierarchy/ConeTree3D.js +0 -135
- package/dist/charts/hierarchy/Hierarchy3DChart.js +0 -27
- package/dist/charts/hierarchy/SpherePack3D.js +0 -81
- package/dist/charts/hierarchy/SunburstTower3D.js +0 -93
- package/dist/charts/hierarchy/TreemapSkyline3D.js +0 -98
- package/dist/charts/linesareas/ConnectedScatterTime.js +0 -206
- package/dist/charts/linesareas/SmallMultipleLines.js +0 -153
- package/dist/charts/mathematical/ParametricSurface3D.js +0 -234
- package/dist/charts/mathematical/StrangeAttractor3D.js +0 -251
- package/dist/charts/proportion/ConeFunnel.js +0 -126
- package/dist/charts/proportion/ExtrudedPieChart.js +0 -123
- package/dist/charts/scatter/BeeswarmScatter.js +0 -151
- package/dist/charts/scatter/BinnedScatterBase.js +0 -51
- package/dist/charts/scatter/ContourScatter.js +0 -116
- package/dist/charts/scatter/GridBinScatter.js +0 -98
- package/dist/charts/scatter/HexbinScatter.js +0 -74
- package/dist/charts/scatter/StripScatter.js +0 -107
- package/dist/charts/scatter/SunflowerScatter.js +0 -109
- package/dist/charts/signals/ConstellationChart.js +0 -153
- package/dist/charts/signals/CorrelogramChart.js +0 -171
- package/dist/charts/signals/EyeDiagramChart.js +0 -155
- package/dist/charts/signals/PoleZeroChart.js +0 -164
- package/dist/charts/signals/SpectrogramChart.js +0 -79
- package/dist/charts/timeline/CalendarSkylineChart.js +0 -128
- package/dist/charts/timeline/ScheduleBlocksChart.js +0 -119
- package/dist/charts/timeline/SpiralTimeline.js +0 -154
- package/dist/charts/timeline/StreamTimeline.js +0 -141
package/dist/core/Chart2D.js
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import { Chart } from "./Chart.js";
|
|
2
2
|
import { ChartExporter, ExportFormat } from "./ChartExporter.js";
|
|
3
3
|
import { Context2D } from "./Context2D.js";
|
|
4
|
-
import { LegendPosition } from "./enums.js";
|
|
4
|
+
import { LegendPosition, RenderType } from "./enums.js";
|
|
5
5
|
import { KeyboardNav } from "./KeyboardNav.js";
|
|
6
|
+
import { MarkOrigin } from "./MarkOrigin.js";
|
|
7
|
+
import { MarkStyle } from "./MarkStyle.js";
|
|
8
|
+
import { TextFit } from "./TextFit.js";
|
|
6
9
|
import { select } from "d3";
|
|
7
10
|
// src/core/Chart2D.ts
|
|
8
11
|
var ROW = 22;
|
|
9
12
|
var CHAR = 6.4;
|
|
13
|
+
/** Swatch and gap before an entry's text. */
|
|
14
|
+
var INDENT = 15;
|
|
15
|
+
/** Empty space kept between a legend's text and the edge of the chart. */
|
|
16
|
+
var EDGE = 8;
|
|
17
|
+
/** How far in from the right edge a right legend starts. */
|
|
18
|
+
var RAIL = 112;
|
|
10
19
|
/** Layout, title, legend and SVG root for all d3 charts. Subclasses implement `draw`. */
|
|
11
|
-
var Chart2D = class extends Chart {
|
|
20
|
+
var Chart2D = class Chart2D extends Chart {
|
|
12
21
|
svg = null;
|
|
13
22
|
#legendRows = 1;
|
|
14
23
|
#keyboard = null;
|
|
24
|
+
#marks = null;
|
|
25
|
+
/** Canvas when the marks live outside the SVG, which is what `finishCopy` has to add back to an export. */
|
|
26
|
+
get renderer() {
|
|
27
|
+
return this.finishCopy === Chart2D.prototype.finishCopy ? RenderType.Svg : RenderType.Canvas;
|
|
28
|
+
}
|
|
15
29
|
/** Room the marks need around the plot, excluding title and legend. */
|
|
16
30
|
plotMargin() {
|
|
17
31
|
return {
|
|
@@ -33,14 +47,25 @@ var Chart2D = class extends Chart {
|
|
|
33
47
|
}
|
|
34
48
|
/** The legend's size depends on what `draw` colours, so a change of row count lays out once more. */
|
|
35
49
|
paint() {
|
|
50
|
+
this.#readyMarks();
|
|
36
51
|
this.#layoutOnce();
|
|
37
52
|
const rows = this.#fitLegend();
|
|
38
53
|
if (rows !== this.#legendRows) {
|
|
39
54
|
this.#legendRows = rows;
|
|
40
55
|
this.#layoutOnce();
|
|
41
56
|
}
|
|
57
|
+
this.#marks?.apply(this.svg.node());
|
|
42
58
|
if (this.liveFrame) Context2D.releaseHover(this.svg.node());
|
|
43
59
|
}
|
|
60
|
+
/** Drawing starts from the chart's own values, and with no style there is nothing to keep. */
|
|
61
|
+
#readyMarks() {
|
|
62
|
+
const style = this.options.chartStyle;
|
|
63
|
+
if (MarkStyle.wanted(style)) (this.#marks ??= new MarkStyle()).begin(style, this.theme);
|
|
64
|
+
else if (this.#marks) {
|
|
65
|
+
this.#marks.end();
|
|
66
|
+
this.#marks = null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
44
69
|
#shownLegend() {
|
|
45
70
|
if (this.legendPosition === LegendPosition.None) return [];
|
|
46
71
|
const items = this.legendItems();
|
|
@@ -52,7 +77,7 @@ var Chart2D = class extends Chart {
|
|
|
52
77
|
const room = this.size.width - 24;
|
|
53
78
|
let rows = items.length ? 1 : 0, x = 0;
|
|
54
79
|
for (const item of items) {
|
|
55
|
-
const cost = legendCost(item);
|
|
80
|
+
const cost = this.#legendCost(this.#legendText(item));
|
|
56
81
|
if (x && x + cost > room) {
|
|
57
82
|
rows++;
|
|
58
83
|
x = 0;
|
|
@@ -61,6 +86,14 @@ var Chart2D = class extends Chart {
|
|
|
61
86
|
}
|
|
62
87
|
return rows;
|
|
63
88
|
}
|
|
89
|
+
/** An entry's text, cut to what is left beside its swatch. */
|
|
90
|
+
#legendText(item) {
|
|
91
|
+
const room = this.legendPosition === LegendPosition.Right ? 97 : this.size.width - 16 - INDENT - EDGE;
|
|
92
|
+
return TextFit.fit(item.label ?? item.key, room, CHAR * this.fontScale);
|
|
93
|
+
}
|
|
94
|
+
#legendCost(text) {
|
|
95
|
+
return 32 + TextFit.width(text, CHAR * this.fontScale);
|
|
96
|
+
}
|
|
64
97
|
#layoutOnce() {
|
|
65
98
|
const { width, height } = this.size;
|
|
66
99
|
const { title, subtitle } = this.options;
|
|
@@ -74,10 +107,10 @@ var Chart2D = class extends Chart {
|
|
|
74
107
|
const svg = this.svg;
|
|
75
108
|
svg.attr("viewBox", `0 0 ${width} ${height}`).attr("width", width).attr("height", height).attr("aria-label", this.label).style("background", this.theme.surface).style("font-family", this.theme.fontFamily);
|
|
76
109
|
svg.select("title").text(this.label);
|
|
77
|
-
const headerHeight = (title ? 24 : 0) + (subtitle ? 18 : 0);
|
|
110
|
+
const headerHeight = ((title ? 24 : 0) + (subtitle ? 18 : 0)) * this.textRoom;
|
|
78
111
|
const margin = this.margin(this.plotMargin());
|
|
79
112
|
const position = this.legendPosition;
|
|
80
|
-
const reserve = position === LegendPosition.Top || position === LegendPosition.Bottom ? ROW * this.#legendRows : 0;
|
|
113
|
+
const reserve = position === LegendPosition.Top || position === LegendPosition.Bottom ? ROW * this.textRoom * this.#legendRows : 0;
|
|
81
114
|
const rail = position === LegendPosition.Right && this.#legendRows ? 120 : 0;
|
|
82
115
|
const top = headerHeight + margin.top + (position === LegendPosition.Top ? reserve : 0);
|
|
83
116
|
const plot = {
|
|
@@ -87,12 +120,13 @@ var Chart2D = class extends Chart {
|
|
|
87
120
|
height: Math.max(10, height - top - margin.bottom - (position === LegendPosition.Bottom ? reserve : 0))
|
|
88
121
|
};
|
|
89
122
|
const root = svg.select("g.g-plot").attr("transform", `translate(${margin.left},${top})`);
|
|
90
|
-
const ctx = new Context2D(svg, root, plot, this.theme, this.palette, this.animated, (event, content) => this.tip(event, content), () => this.untip(), (datum) => this.options.onSelect?.(datum), this.#readyKeyboard({
|
|
123
|
+
const ctx = new Context2D(svg, root, plot, this.theme, this.palette, this.animated, (event, content) => this.tip(event, content), () => this.untip(), (datum) => this.options.onSelect?.(MarkOrigin.of(datum, this.options)), this.#readyKeyboard({
|
|
91
124
|
...plot,
|
|
92
125
|
x: margin.left,
|
|
93
126
|
y: top
|
|
94
127
|
})?.register);
|
|
95
128
|
ctx.live = this.liveFrame;
|
|
129
|
+
ctx.marking = this.#marks;
|
|
96
130
|
this.drawHeader(svg, width);
|
|
97
131
|
if (this.options.data != null) this.draw(ctx);
|
|
98
132
|
else root.selectAll("*").remove();
|
|
@@ -188,46 +222,49 @@ var Chart2D = class extends Chart {
|
|
|
188
222
|
fill: this.theme.inkSecondary,
|
|
189
223
|
y: this.options.title ? 37 : 14
|
|
190
224
|
}].filter(Boolean);
|
|
191
|
-
|
|
225
|
+
const room = this.textRoom, scale = this.fontScale;
|
|
226
|
+
svg.select("g.g-header").selectAll("text").data(lines).join("text").attr("x", 16).attr("y", (d) => d.y * room).attr("font-size", (d) => d.size).attr("font-weight", (d) => d.weight).attr("fill", (d) => d.fill).attr("textLength", (d) => d.text.length * d.size * scale * .6 > width - 32 ? width - 32 : null).text((d) => d.text);
|
|
192
227
|
}
|
|
193
228
|
drawLegend(svg, width, height, headerHeight, plotHeight, top, margin) {
|
|
194
229
|
const position = this.legendPosition;
|
|
195
230
|
const shown = this.#shownLegend();
|
|
196
|
-
|
|
197
|
-
|
|
231
|
+
const room = this.textRoom, row = ROW * room;
|
|
232
|
+
let x = 16, y = headerHeight + 15 * room;
|
|
233
|
+
if (position === LegendPosition.Bottom) y = top + plotHeight + margin.bottom + 14 * room;
|
|
198
234
|
if (position === LegendPosition.Right) {
|
|
199
|
-
x = width -
|
|
235
|
+
x = width - RAIL;
|
|
200
236
|
y = top + 10;
|
|
201
237
|
}
|
|
202
238
|
const left = x;
|
|
203
239
|
const placed = shown.map((item) => {
|
|
204
|
-
const name = item.label ?? item.key, cost = legendCost(
|
|
205
|
-
if (position !== LegendPosition.Right && x > left && x + cost > width -
|
|
240
|
+
const name = item.label ?? item.key, shown = this.#legendText(item), cost = this.#legendCost(shown);
|
|
241
|
+
if (position !== LegendPosition.Right && x > left && x + cost > width - EDGE) {
|
|
206
242
|
x = left;
|
|
207
|
-
y +=
|
|
243
|
+
y += row;
|
|
208
244
|
}
|
|
209
245
|
const at = {
|
|
210
246
|
...item,
|
|
211
247
|
name,
|
|
248
|
+
shown,
|
|
212
249
|
x,
|
|
213
250
|
y
|
|
214
251
|
};
|
|
215
|
-
if (position === LegendPosition.Right) y +=
|
|
252
|
+
if (position === LegendPosition.Right) y += row - 4;
|
|
216
253
|
else x += cost;
|
|
217
254
|
return at;
|
|
218
255
|
});
|
|
219
256
|
const legend = svg.select("g.g-legend").selectAll("g").data(placed, (d) => d.key).join((enter) => {
|
|
220
257
|
const g = enter.append("g");
|
|
221
258
|
g.append("rect").attr("width", 10).attr("height", 10).attr("rx", 3).attr("y", -9);
|
|
222
|
-
g.append("text").attr("x",
|
|
259
|
+
g.append("text").attr("x", INDENT).attr("font-size", 11.5);
|
|
223
260
|
return g;
|
|
224
261
|
});
|
|
225
262
|
legend.attr("transform", (d) => `translate(${d.x},${d.y})`);
|
|
226
263
|
legend.select("rect").attr("fill", (d) => d.color);
|
|
227
|
-
legend.select("text").attr("fill", this.theme.inkSecondary).text((d) => d.
|
|
264
|
+
legend.select("text").attr("fill", this.theme.inkSecondary).text((d) => d.shown);
|
|
265
|
+
TextFit.hint(legend, (d) => d.title ?? (d.shown === d.name ? void 0 : d.name));
|
|
228
266
|
}
|
|
229
267
|
};
|
|
230
|
-
var legendCost = (item) => 32 + (item.label ?? item.key).length * CHAR;
|
|
231
268
|
var GEOMETRY = [
|
|
232
269
|
"d",
|
|
233
270
|
"x",
|
package/dist/core/Chart3D.js
CHANGED
|
@@ -2,17 +2,19 @@ import { Chart } from "./Chart.js";
|
|
|
2
2
|
import { ChartExportError, ExportErrorCode } from "./ChartExportError.js";
|
|
3
3
|
import { ChartExporter } from "./ChartExporter.js";
|
|
4
4
|
import { Context3D } from "./Context3D.js";
|
|
5
|
-
import { LegendPosition } from "./enums.js";
|
|
5
|
+
import { LegendPosition, RenderType } from "./enums.js";
|
|
6
6
|
import { SceneOverlay } from "./SceneOverlay.js";
|
|
7
|
-
import { AmbientLight, Color, DirectionalLight, Group, HemisphereLight, Mesh, PCFSoftShadowMap, PerspectiveCamera, Raycaster, Scene, Timer, Vector2, WebGLRenderer } from "three";
|
|
7
|
+
import { AmbientLight, Color, DirectionalLight, Group, HemisphereLight, Mesh, PCFSoftShadowMap, PerspectiveCamera, Raycaster, Scene, ShaderMaterial, Timer, Vector2, WebGLRenderer } from "three";
|
|
8
8
|
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
9
9
|
import { CSS2DRenderer } from "three/addons/renderers/CSS2DRenderer.js";
|
|
10
10
|
// src/core/Chart3D.ts
|
|
11
11
|
var rad = Math.PI / 180;
|
|
12
|
+
/** The most `chartStyle.pixelRatio` can ask for: past this a large chart costs far more than it shows. */
|
|
13
|
+
var RATIO_LIMIT = 3;
|
|
12
14
|
/** three.js stage: renderer, camera, lights, orbit controls, picking, HTML overlay and disposal. Subclasses implement `build`. */
|
|
13
15
|
var Chart3D = class Chart3D extends Chart {
|
|
14
16
|
static shared = null;
|
|
15
|
-
|
|
17
|
+
webgl = null;
|
|
16
18
|
overlay = null;
|
|
17
19
|
controls = null;
|
|
18
20
|
camera = new PerspectiveCamera(38, 1.6, .01, 100);
|
|
@@ -22,18 +24,48 @@ var Chart3D = class Chart3D extends Chart {
|
|
|
22
24
|
timer = new Timer();
|
|
23
25
|
ray = new Raycaster();
|
|
24
26
|
legendEl = null;
|
|
27
|
+
#antialias = true;
|
|
28
|
+
get renderer() {
|
|
29
|
+
return RenderType.WebGL;
|
|
30
|
+
}
|
|
25
31
|
compose(animated) {
|
|
26
32
|
const scene = new Scene();
|
|
27
33
|
scene.background = new Color(this.theme.surface);
|
|
28
|
-
const ctx = new Context3D(new Group(), this.theme, this.palette, animated);
|
|
34
|
+
const ctx = new Context3D(new Group(), this.theme, this.palette, animated, this.fontScale, this.#opacity);
|
|
29
35
|
scene.add(ctx.root);
|
|
30
36
|
this.light(scene);
|
|
31
37
|
if (this.options.data != null) this.build(ctx);
|
|
38
|
+
this.#restyle(scene);
|
|
32
39
|
return {
|
|
33
40
|
scene,
|
|
34
41
|
ctx
|
|
35
42
|
};
|
|
36
43
|
}
|
|
44
|
+
/** A class may share one material between builds, so opacity is always taken from the value it first had. */
|
|
45
|
+
#restyle(scene) {
|
|
46
|
+
const { wireframe, opacity } = this.options.chartStyle ?? {};
|
|
47
|
+
if (wireframe === void 0 && opacity === void 0) return;
|
|
48
|
+
const share = this.#opacity;
|
|
49
|
+
scene.traverse((object) => {
|
|
50
|
+
for (const material of [object.material].flat()) {
|
|
51
|
+
if (!material) continue;
|
|
52
|
+
if (wireframe !== void 0 && "wireframe" in material) material.wireframe = wireframe;
|
|
53
|
+
if (opacity === void 0) continue;
|
|
54
|
+
material.opacity = (material.userData.opacity ??= material.opacity) * share;
|
|
55
|
+
material.transparent ||= share < 1;
|
|
56
|
+
if (material instanceof ShaderMaterial && material.uniforms.uOpacity) material.uniforms.uOpacity.value = share;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/** `chartStyle.opacity`, the share of every material's own opacity that shows. */
|
|
61
|
+
get #opacity() {
|
|
62
|
+
const { opacity = 1 } = this.options.chartStyle ?? {};
|
|
63
|
+
return Number.isNaN(opacity) ? 1 : Math.max(0, Math.min(1, opacity));
|
|
64
|
+
}
|
|
65
|
+
get #pixelRatio() {
|
|
66
|
+
const asked = this.options.chartStyle?.pixelRatio;
|
|
67
|
+
return asked && asked > 0 && Number.isFinite(asked) ? Math.min(asked, RATIO_LIMIT) : Math.min(devicePixelRatio, 2);
|
|
68
|
+
}
|
|
37
69
|
light(scene) {
|
|
38
70
|
const key = new DirectionalLight("#ffffff", 2.2);
|
|
39
71
|
key.position.set(2.5, 4, 3);
|
|
@@ -57,9 +89,11 @@ var Chart3D = class Chart3D extends Chart {
|
|
|
57
89
|
paint() {
|
|
58
90
|
const { width, height } = this.size;
|
|
59
91
|
this.clearScene();
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
92
|
+
const antialias = this.options.chartStyle?.antialias ?? true;
|
|
93
|
+
if (this.webgl && antialias !== this.#antialias) this.teardown();
|
|
94
|
+
this.#antialias = antialias;
|
|
95
|
+
if (!this.webgl) {
|
|
96
|
+
const renderer = this.webgl = new WebGLRenderer({ antialias });
|
|
63
97
|
renderer.shadowMap.enabled = true;
|
|
64
98
|
renderer.shadowMap.type = PCFSoftShadowMap;
|
|
65
99
|
this.host.appendChild(renderer.domElement);
|
|
@@ -83,8 +117,9 @@ var Chart3D = class Chart3D extends Chart {
|
|
|
83
117
|
this.place(this.camera);
|
|
84
118
|
renderer.setAnimationLoop(() => this.tick());
|
|
85
119
|
}
|
|
86
|
-
this.
|
|
87
|
-
this.
|
|
120
|
+
if (this.webgl.getPixelRatio() !== this.#pixelRatio) this.webgl.setPixelRatio(this.#pixelRatio);
|
|
121
|
+
this.webgl.domElement.setAttribute("aria-label", this.label);
|
|
122
|
+
this.webgl.setSize(width, height);
|
|
88
123
|
this.overlay.setSize(width, height);
|
|
89
124
|
this.camera.aspect = width / height;
|
|
90
125
|
this.camera.updateProjectionMatrix();
|
|
@@ -96,12 +131,12 @@ var Chart3D = class Chart3D extends Chart {
|
|
|
96
131
|
this.drawLegend();
|
|
97
132
|
}
|
|
98
133
|
tick() {
|
|
99
|
-
if (!this.scene || !this.
|
|
134
|
+
if (!this.scene || !this.webgl) return;
|
|
100
135
|
this.timer.update();
|
|
101
136
|
const t = this.timer.getElapsed();
|
|
102
137
|
for (const fn of this.ctx.frames) fn(t);
|
|
103
138
|
this.controls.update();
|
|
104
|
-
this.
|
|
139
|
+
this.webgl.render(this.scene, this.camera);
|
|
105
140
|
this.overlay.render(this.scene, this.camera);
|
|
106
141
|
}
|
|
107
142
|
onPointer = (event) => {
|
|
@@ -119,12 +154,12 @@ var Chart3D = class Chart3D extends Chart {
|
|
|
119
154
|
const items = this.palette.items();
|
|
120
155
|
const header = [this.options.title, this.options.subtitle].filter(Boolean);
|
|
121
156
|
if (!header.length && (items.length < 2 || this.legendPosition === LegendPosition.None)) return;
|
|
122
|
-
const el = this.legendEl = document.createElement("div");
|
|
157
|
+
const el = this.legendEl = document.createElement("div"), scale = this.fontScale;
|
|
123
158
|
Object.assign(el.style, {
|
|
124
159
|
position: "absolute",
|
|
125
160
|
top: "10px",
|
|
126
161
|
left: "14px",
|
|
127
|
-
font:
|
|
162
|
+
font: `${12 * scale}px ${this.theme.fontFamily}`,
|
|
128
163
|
color: this.theme.inkSecondary,
|
|
129
164
|
pointerEvents: "none"
|
|
130
165
|
});
|
|
@@ -132,7 +167,7 @@ var Chart3D = class Chart3D extends Chart {
|
|
|
132
167
|
const line = el.appendChild(document.createElement("div"));
|
|
133
168
|
line.textContent = text;
|
|
134
169
|
if (i === 0 && this.options.title) Object.assign(line.style, {
|
|
135
|
-
font: `600
|
|
170
|
+
font: `600 ${15 * scale}px ${this.theme.fontFamily}`,
|
|
136
171
|
color: this.theme.ink
|
|
137
172
|
});
|
|
138
173
|
});
|
|
@@ -171,14 +206,14 @@ var Chart3D = class Chart3D extends Chart {
|
|
|
171
206
|
this.ctx = null;
|
|
172
207
|
}
|
|
173
208
|
teardown() {
|
|
174
|
-
this.
|
|
209
|
+
this.webgl?.setAnimationLoop(null);
|
|
175
210
|
this.clearScene();
|
|
176
211
|
this.controls?.dispose();
|
|
177
212
|
this.legendEl?.remove();
|
|
178
213
|
this.overlay?.domElement.remove();
|
|
179
|
-
this.
|
|
180
|
-
this.
|
|
181
|
-
this.
|
|
214
|
+
this.webgl?.domElement.remove();
|
|
215
|
+
this.webgl?.dispose();
|
|
216
|
+
this.webgl = this.overlay = this.controls = null;
|
|
182
217
|
}
|
|
183
218
|
/** Works without WebGL: builds the scene and hashes its geometry and transforms. */
|
|
184
219
|
signature() {
|
|
@@ -216,7 +251,7 @@ var Chart3D = class Chart3D extends Chart {
|
|
|
216
251
|
*/
|
|
217
252
|
async rasterize(scale) {
|
|
218
253
|
await this.ready();
|
|
219
|
-
const { renderer, scene, camera } = this;
|
|
254
|
+
const { webgl: renderer, scene, camera } = this;
|
|
220
255
|
if (!renderer || !scene) return this.#rasterizeOffscreen(scale);
|
|
221
256
|
this.ctx.settle();
|
|
222
257
|
const ratio = renderer.getPixelRatio();
|
|
@@ -275,7 +310,8 @@ var Chart3D = class Chart3D extends Chart {
|
|
|
275
310
|
}, output.width / width, this.theme, {
|
|
276
311
|
title,
|
|
277
312
|
subtitle,
|
|
278
|
-
items
|
|
313
|
+
items,
|
|
314
|
+
fontScale: this.fontScale
|
|
279
315
|
});
|
|
280
316
|
return output;
|
|
281
317
|
}
|
|
@@ -8,6 +8,8 @@ var ExportErrorCode = /* @__PURE__ */ function(ExportErrorCode) {
|
|
|
8
8
|
ExportErrorCode["Tainted"] = "tainted";
|
|
9
9
|
/** The browser could not draw or encode the image, usually because it is too large. */
|
|
10
10
|
ExportErrorCode["Encoding"] = "encoding";
|
|
11
|
+
/** The code for a format loads on first use, and that load failed. */
|
|
12
|
+
ExportErrorCode["Unavailable"] = "unavailable";
|
|
11
13
|
/** The React handle has no chart mounted yet. */
|
|
12
14
|
ExportErrorCode["Unmounted"] = "unmounted";
|
|
13
15
|
return ExportErrorCode;
|
|
@@ -4,6 +4,8 @@ var ExportFormat = /* @__PURE__ */ function(ExportFormat) {
|
|
|
4
4
|
ExportFormat["SVG"] = "svg";
|
|
5
5
|
ExportFormat["PNG"] = "png";
|
|
6
6
|
ExportFormat["CSV"] = "csv";
|
|
7
|
+
ExportFormat["PDF"] = "pdf";
|
|
8
|
+
ExportFormat["Excel"] = "xlsx";
|
|
7
9
|
return ExportFormat;
|
|
8
10
|
}({});
|
|
9
11
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
@@ -58,9 +60,11 @@ var LIVE_ROOT = [
|
|
|
58
60
|
/** On-screen controls, hit areas and the keyboard cursor. None of them belongs in a picture of the chart. */
|
|
59
61
|
var CHROME = "[role=\"button\"], .m-zoom-hit, .g-keyboard";
|
|
60
62
|
var TIMEOUT = 2e4;
|
|
63
|
+
var HELD_STROKE = "non-scaling-stroke";
|
|
61
64
|
/** Characters XML has no way to write, which would make the whole file unreadable. */
|
|
62
65
|
var UNWRITABLE = /[^\t\n\r\x20-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/gu;
|
|
63
66
|
var FORMULA = /^[=+\-@\t\r]/;
|
|
67
|
+
var SHEET_ROWS = 1048576;
|
|
64
68
|
var quoteless = (name) => name.trim().replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
65
69
|
/**
|
|
66
70
|
* Turns a chart into files. Builds on what every chart already has: its SVG root or a canvas, and `table()`.
|
|
@@ -72,17 +76,29 @@ var ChartExporter = class ChartExporter {
|
|
|
72
76
|
if (!rows.length) throw new ChartExportError(ExportErrorCode.NoData, "CSV export needs rows, and this chart's data has no flat table view.");
|
|
73
77
|
return [columns, ...rows].map((row) => row.map((cell) => ChartExporter.#cell(cell)).join(",")).join("\r\n");
|
|
74
78
|
}
|
|
79
|
+
/** The formats built from rows. A worksheet has a fixed height, and one row of it is the header. */
|
|
80
|
+
static tabular(rows) {
|
|
81
|
+
return rows ? ["csv", ...rows < SHEET_ROWS ? ["xlsx"] : []] : [];
|
|
82
|
+
}
|
|
83
|
+
/** Awaits a writer's dynamic import, so a chunk that fails to load rejects like any other export. */
|
|
84
|
+
static async load(format, load) {
|
|
85
|
+
try {
|
|
86
|
+
return await load();
|
|
87
|
+
} catch (cause) {
|
|
88
|
+
throw new ChartExportError(ExportErrorCode.Unavailable, `The ${format} writer could not be loaded: ${cause.message}`, { cause });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
75
91
|
static #cell(cell) {
|
|
76
|
-
const text = ChartExporter
|
|
77
|
-
|
|
92
|
+
const text = ChartExporter.text(cell);
|
|
93
|
+
const guarded = typeof cell === "string" && FORMULA.test(text) && !Number.isFinite(+text) ? `'${text}` : text;
|
|
94
|
+
return /[",\r\n]/.test(guarded) ? `"${guarded.replaceAll("\"", "\"\"")}"` : guarded;
|
|
78
95
|
}
|
|
79
|
-
|
|
96
|
+
/** A cell as plain text. */
|
|
97
|
+
static text(cell) {
|
|
80
98
|
if (cell === null || cell === void 0) return "";
|
|
81
99
|
if (cell instanceof Date) return Number.isNaN(+cell) ? "" : cell.toISOString();
|
|
82
100
|
if (typeof cell === "number") return Number.isNaN(cell) ? "" : String(cell);
|
|
83
|
-
|
|
84
|
-
const text = String(cell);
|
|
85
|
-
return typeof cell === "string" && FORMULA.test(text) && !Number.isFinite(+text) ? `'${text}` : text;
|
|
101
|
+
return typeof cell === "object" ? ChartExporter.#json(cell) : String(cell);
|
|
86
102
|
}
|
|
87
103
|
/** Layouts leave cycles in the objects they are handed, which JSON cannot write. */
|
|
88
104
|
static #json(cell) {
|
|
@@ -217,11 +233,18 @@ var ChartExporter = class ChartExporter {
|
|
|
217
233
|
return out;
|
|
218
234
|
}
|
|
219
235
|
static serialize(svg) {
|
|
220
|
-
return new XMLSerializer().serializeToString(svg)
|
|
236
|
+
return ChartExporter.writable(new XMLSerializer().serializeToString(svg));
|
|
237
|
+
}
|
|
238
|
+
/** `text` without the characters XML cannot hold. */
|
|
239
|
+
static writable(text) {
|
|
240
|
+
return text.replace(UNWRITABLE, "");
|
|
221
241
|
}
|
|
222
242
|
/** Draws SVG text onto a canvas filled with `surface`. The SVG must already be embedded: an SVG image cannot load links. */
|
|
223
243
|
static async raster(svg, { width, height }, scale, surface) {
|
|
224
|
-
const url = URL.createObjectURL(ChartExporter.blob(svg,
|
|
244
|
+
const url = URL.createObjectURL(ChartExporter.blob(ChartExporter.#atScale(svg, {
|
|
245
|
+
width,
|
|
246
|
+
height
|
|
247
|
+
}, scale), "svg"));
|
|
225
248
|
try {
|
|
226
249
|
const image = await ChartExporter.#load(url);
|
|
227
250
|
const canvas = ChartExporter.canvas(width * scale, height * scale);
|
|
@@ -234,6 +257,25 @@ var ChartExporter = class ChartExporter {
|
|
|
234
257
|
URL.revokeObjectURL(url);
|
|
235
258
|
}
|
|
236
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* A held stroke is sized in screen pixels, and engines disagree on what those are in an image drawn larger:
|
|
262
|
+
* Chromium keeps it thin, WebKit scales it. In an image already at full size they agree, so that is what they get.
|
|
263
|
+
*/
|
|
264
|
+
static #atScale(svg, { width, height }, scale) {
|
|
265
|
+
if (scale === 1 || !svg.includes(HELD_STROKE)) return svg;
|
|
266
|
+
const root = new DOMParser().parseFromString(svg, "image/svg+xml").documentElement;
|
|
267
|
+
for (const [name, value] of Object.entries({
|
|
268
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
269
|
+
width: width * scale,
|
|
270
|
+
height: height * scale
|
|
271
|
+
})) root.setAttribute(name, `${value}`);
|
|
272
|
+
for (const el of root.querySelectorAll(`[vector-effect="${HELD_STROKE}"]`)) {
|
|
273
|
+
el.style.setProperty("stroke-width", `${parseFloat(el.closest("[stroke-width]")?.getAttribute("stroke-width") ?? "1") * scale}px`);
|
|
274
|
+
const dash = el.getAttribute("stroke-dasharray");
|
|
275
|
+
if (dash && dash !== "none") el.setAttribute("stroke-dasharray", dash.split(/[\s,]+/).map((length) => parseFloat(length) * scale).join(" "));
|
|
276
|
+
}
|
|
277
|
+
return ChartExporter.serialize(root);
|
|
278
|
+
}
|
|
237
279
|
static #load(url) {
|
|
238
280
|
return new Promise((resolve, reject) => {
|
|
239
281
|
const image = new Image();
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/core/ColumnCheck.ts
|
|
2
|
+
/** Bundlers replace this expression; a page with no bundler has no `process`. */
|
|
3
|
+
var DEV = (() => {
|
|
4
|
+
try {
|
|
5
|
+
return process.env.NODE_ENV !== "production";
|
|
6
|
+
} catch {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
})();
|
|
10
|
+
/** Enough rows to see every column, even where the first rows leave some out. */
|
|
11
|
+
var SCAN = 500;
|
|
12
|
+
var namesOf = (value) => {
|
|
13
|
+
if (typeof value === "string") return [value];
|
|
14
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : [];
|
|
15
|
+
};
|
|
16
|
+
var columnsOf = (data) => {
|
|
17
|
+
const columns = /* @__PURE__ */ new Set();
|
|
18
|
+
if (Array.isArray(data)) {
|
|
19
|
+
for (const row of data.slice(0, SCAN)) if (row !== null && typeof row === "object") for (const name in row) columns.add(name);
|
|
20
|
+
}
|
|
21
|
+
return columns;
|
|
22
|
+
};
|
|
23
|
+
/** Tells developers when an option names a column their rows lack, which would otherwise draw an empty chart. */
|
|
24
|
+
var ColumnCheck = class {
|
|
25
|
+
static #options = /* @__PURE__ */ new WeakMap();
|
|
26
|
+
static #warned = /* @__PURE__ */ new Set();
|
|
27
|
+
/**
|
|
28
|
+
* Which options name columns. By the authoring contract their defaults are `meta.sample` column
|
|
29
|
+
* names, so a default found there marks one. Variant axes are enums, even when a value looks like a column.
|
|
30
|
+
*/
|
|
31
|
+
static #columnOptions(meta, defaults) {
|
|
32
|
+
let keys = this.#options.get(meta);
|
|
33
|
+
if (!keys) {
|
|
34
|
+
const sample = columnsOf(meta.sample(1));
|
|
35
|
+
keys = Object.keys(defaults).filter((key) => !(key in (meta.variants ?? {})) && namesOf(defaults[key]).length > 0 && namesOf(defaults[key]).every((name) => sample.has(name)));
|
|
36
|
+
this.#options.set(meta, keys);
|
|
37
|
+
}
|
|
38
|
+
return keys;
|
|
39
|
+
}
|
|
40
|
+
/** Warns once per chart, option and name. Does nothing in a production build. */
|
|
41
|
+
static run(meta, defaults, options) {
|
|
42
|
+
if (!DEV) return;
|
|
43
|
+
const columns = columnsOf(options.data);
|
|
44
|
+
if (!columns.size) return;
|
|
45
|
+
for (const key of this.#columnOptions(meta, defaults)) for (const name of namesOf(options[key])) {
|
|
46
|
+
const id = `${meta.id}/${key}/${name}`;
|
|
47
|
+
if (columns.has(name) || this.#warned.has(id)) continue;
|
|
48
|
+
this.#warned.add(id);
|
|
49
|
+
console.warn(`${meta.name}: option "${key}" names column "${name}", but the data has no such column. Columns found: ${[...columns].join(", ")}.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
export { ColumnCheck };
|
package/dist/core/Context2D.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { pathMorph } from "./pathMorph.js";
|
|
2
|
+
import { easeCubicOut } from "d3";
|
|
2
3
|
import "d3-transition";
|
|
3
4
|
// src/core/Context2D.ts
|
|
4
5
|
var PLACED = "__placed";
|
|
5
6
|
/** Groups that frame the plot rather than mark data, by `group` name. They stay out of a sliding layer. */
|
|
6
7
|
var FRAMING = /^(axis-|grid$)/;
|
|
8
|
+
/** Plot groups that frame the marks. */
|
|
9
|
+
var FRAME = ".m-axis-x,.m-axis-y,.m-axis-title,.m-grid,.m-zoom-hit,.m-view-clip,.m-domain-clip,.m-brush,.m-zoom-controls";
|
|
7
10
|
var hovered = /* @__PURE__ */ new WeakMap();
|
|
8
11
|
/** Everything a 2D chart needs while drawing. `root` is a `<g>` already translated to the plot area. */
|
|
9
12
|
var Context2D = class Context2D {
|
|
@@ -19,6 +22,8 @@ var Context2D = class Context2D {
|
|
|
19
22
|
registrar;
|
|
20
23
|
/** Repaints may come every frame, so staggers and reveals would never get to finish. */
|
|
21
24
|
live = false;
|
|
25
|
+
/** Set while `chartStyle` changes attributes, so transitions land on the styled values. */
|
|
26
|
+
marking = null;
|
|
22
27
|
constructor(svg, root, plot, theme, palette, animated, tipper, untipper, selecter, registrar) {
|
|
23
28
|
this.svg = svg;
|
|
24
29
|
this.root = root;
|
|
@@ -51,6 +56,7 @@ var Context2D = class Context2D {
|
|
|
51
56
|
if (!this.animated) {
|
|
52
57
|
if (this.live) selection.property(PLACED, true);
|
|
53
58
|
apply(selection, final);
|
|
59
|
+
this.marking?.placed(selection, Object.keys(final));
|
|
54
60
|
return selection;
|
|
55
61
|
}
|
|
56
62
|
const fresh = selection.filter(function() {
|
|
@@ -64,12 +70,21 @@ var Context2D = class Context2D {
|
|
|
64
70
|
el[PLACED] = true;
|
|
65
71
|
});
|
|
66
72
|
apply(fresh, from);
|
|
67
|
-
const
|
|
68
|
-
for (const [name, value] of Object.entries(final)) if (this.live && name === "d") motion.attrTween("d", pathMorph(value));
|
|
69
|
-
else motion.attr(name, value);
|
|
70
|
-
for (const name of restore) motion.attr(name, function() {
|
|
73
|
+
const rest = Object.fromEntries(restore.map((name) => [name, function() {
|
|
71
74
|
return resting.has(this) ? resting.get(this)[name] : this.getAttribute(name);
|
|
75
|
+
}]));
|
|
76
|
+
const ends = this.marking?.ends(selection, {
|
|
77
|
+
...final,
|
|
78
|
+
...rest
|
|
72
79
|
});
|
|
80
|
+
const motion = selection.transition().duration(650).ease(easeCubicOut).delay((_, i) => this.live ? 0 : i * stagger);
|
|
81
|
+
for (const [name, value] of Object.entries(final)) if (name === "d") {
|
|
82
|
+
const ends = resolve(selection, value);
|
|
83
|
+
motion.attrTween("d", function() {
|
|
84
|
+
return pathMorph(this.getAttribute("d") ?? "", ends.get(this));
|
|
85
|
+
});
|
|
86
|
+
} else motion.attr(name, ends?.[name] ?? value);
|
|
87
|
+
for (const name of restore) motion.attr(name, ends?.[name] ?? rest[name]);
|
|
73
88
|
return selection;
|
|
74
89
|
}
|
|
75
90
|
/** Draws a line on from its start. */
|
|
@@ -149,16 +164,15 @@ var Context2D = class Context2D {
|
|
|
149
164
|
return selection.attr("fill", fill).attr("font-size", size).attr("font-family", this.theme.fontFamily);
|
|
150
165
|
}
|
|
151
166
|
};
|
|
167
|
+
/** Values for each element now, as `transition.attr` does: a chart may have moved on by the time a delayed tween starts. */
|
|
168
|
+
function resolve(selection, value) {
|
|
169
|
+
const ends = /* @__PURE__ */ new Map();
|
|
170
|
+
selection.each(function(datum, index) {
|
|
171
|
+
ends.set(this, String((typeof value === "function" ? value(datum, index) : value) ?? ""));
|
|
172
|
+
});
|
|
173
|
+
return ends;
|
|
174
|
+
}
|
|
152
175
|
function apply(selection, attrs) {
|
|
153
176
|
for (const [name, value] of Object.entries(attrs)) selection.attr(name, value);
|
|
154
177
|
}
|
|
155
|
-
|
|
156
|
-
/** Tweens a path, snapping when an arc changes flags: those cannot take in-between values. */
|
|
157
|
-
function pathMorph(value) {
|
|
158
|
-
return function(datum, index) {
|
|
159
|
-
const from = this.getAttribute("d") ?? "";
|
|
160
|
-
const to = String((typeof value === "function" ? value(datum, index) : value) ?? "");
|
|
161
|
-
return arcFlags(from) === arcFlags(to) ? interpolateString(from, to) : () => to;
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
export { Context2D, FRAMING };
|
|
178
|
+
export { Context2D, FRAME, FRAMING };
|
package/dist/core/Context3D.js
CHANGED
|
@@ -7,15 +7,20 @@ var Context3D = class {
|
|
|
7
7
|
theme;
|
|
8
8
|
palette;
|
|
9
9
|
animated;
|
|
10
|
+
fontScale;
|
|
11
|
+
opacity;
|
|
10
12
|
/** Per-frame callbacks; `t` is seconds since the chart was built. */
|
|
11
13
|
frames = [];
|
|
12
14
|
picks = /* @__PURE__ */ new Map();
|
|
13
15
|
#reveals = /* @__PURE__ */ new Set();
|
|
14
|
-
|
|
16
|
+
/** `fontScale` is `chartStyle.fontSize`. `opacity` is `chartStyle.opacity`: `onFrame` code that writes `material.opacity` multiplies by it. */
|
|
17
|
+
constructor(root, theme, palette, animated, fontScale = 1, opacity = 1) {
|
|
15
18
|
this.root = root;
|
|
16
19
|
this.theme = theme;
|
|
17
20
|
this.palette = palette;
|
|
18
21
|
this.animated = animated;
|
|
22
|
+
this.fontScale = fontScale;
|
|
23
|
+
this.opacity = opacity;
|
|
19
24
|
}
|
|
20
25
|
color(value) {
|
|
21
26
|
return new Color(value);
|
|
@@ -58,7 +63,7 @@ var Context3D = class {
|
|
|
58
63
|
const el = document.createElement("div");
|
|
59
64
|
el.textContent = text;
|
|
60
65
|
Object.assign(el.style, {
|
|
61
|
-
font: `${size}px ${this.theme.fontFamily}`,
|
|
66
|
+
font: `${size * this.fontScale}px ${this.theme.fontFamily}`,
|
|
62
67
|
whiteSpace: "nowrap",
|
|
63
68
|
pointerEvents: "none",
|
|
64
69
|
color: {
|