@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.
Files changed (238) hide show
  1. package/README.md +111 -14
  2. package/dist/ExcelWriter-3ZSYUH4T.cjs +154 -0
  3. package/dist/GraphType.js +24 -38
  4. package/dist/PdfWriter-6HJFYMCO.cjs +205 -0
  5. package/dist/catalog-YWX6FHRM.cjs +8 -0
  6. package/dist/catalog.js +5 -1
  7. package/dist/charts/bars/index.js +2 -12
  8. package/dist/charts/comparison/EffectForestPlot.js +1 -1
  9. package/dist/charts/comparison/index.js +1 -8
  10. package/dist/charts/demography/Nomogram.js +3 -3
  11. package/dist/charts/density/HexbinChart.js +6 -4
  12. package/dist/charts/diagrams/ArchitectureCanvas.js +68 -0
  13. package/dist/charts/diagrams/ArchitectureDiagram.js +331 -0
  14. package/dist/charts/diagrams/ArchitectureLayout.js +106 -0
  15. package/dist/charts/diagrams/ArchitectureLinkLabels.js +78 -0
  16. package/dist/charts/diagrams/ArchitectureModel.js +108 -0
  17. package/dist/charts/diagrams/ArchitectureRouter.js +348 -0
  18. package/dist/charts/diagrams/ArchitectureSamples.js +312 -0
  19. package/dist/charts/diagrams/ArchitectureShapes.js +130 -0
  20. package/dist/charts/diagrams/ArchitectureText.js +25 -0
  21. package/dist/charts/diagrams/DiagramText.js +38 -0
  22. package/dist/charts/diagrams/EntityRelationshipDiagram.js +465 -0
  23. package/dist/charts/diagrams/ErCrowsFoot.js +33 -0
  24. package/dist/charts/diagrams/ErSamples.js +205 -0
  25. package/dist/charts/diagrams/ErSchema.js +122 -0
  26. package/dist/charts/diagrams/LogicCircuit.js +154 -0
  27. package/dist/charts/diagrams/LogicCircuitLayout.js +600 -0
  28. package/dist/charts/diagrams/LogicCircuitSamples.js +66 -0
  29. package/dist/charts/diagrams/LogicGateDiagram.js +216 -0
  30. package/dist/charts/diagrams/LogicGateSymbol.js +113 -0
  31. package/dist/charts/diagrams/NetworkTopologyDiagram.js +247 -0
  32. package/dist/charts/diagrams/SequenceDiagram.js +351 -0
  33. package/dist/charts/diagrams/SequenceLayout.js +422 -0
  34. package/dist/charts/diagrams/SequenceModel.js +221 -0
  35. package/dist/charts/diagrams/TopologyLayout.js +143 -0
  36. package/dist/charts/diagrams/architectureEnums.js +50 -0
  37. package/dist/charts/diagrams/index.js +26 -0
  38. package/dist/charts/diagrams/logicEnums.js +42 -0
  39. package/dist/charts/diagrams/sequenceEnums.js +40 -0
  40. package/dist/charts/distribution/HistogramChart.js +25 -3
  41. package/dist/charts/distribution/index.js +1 -3
  42. package/dist/charts/earthscience/HovmollerDiagram.js +477 -0
  43. package/dist/charts/earthscience/HovmollerEnums.js +42 -0
  44. package/dist/charts/earthscience/HovmollerField.js +180 -0
  45. package/dist/charts/earthscience/HovmollerSample.js +40 -0
  46. package/dist/charts/earthscience/HovmollerSpeed.js +71 -0
  47. package/dist/charts/earthscience/index.js +10 -2
  48. package/dist/charts/engineering/ConstellationDiagram.js +26 -6
  49. package/dist/charts/engineering/EyeDiagram.js +2 -1
  50. package/dist/charts/engineering/PoleZeroPlot.js +32 -8
  51. package/dist/charts/financial/RelativeStrengthRotation.js +434 -0
  52. package/dist/charts/financial/RelativeStrengthRotationMath.js +71 -0
  53. package/dist/charts/financial/RelativeStrengthRotationSamples.js +27 -0
  54. package/dist/charts/financial/index.js +5 -3
  55. package/dist/charts/flow/index.js +1 -5
  56. package/dist/charts/frameworks/AnsoffMatrix.js +523 -0
  57. package/dist/charts/frameworks/FrameworkText.js +98 -0
  58. package/dist/charts/frameworks/NpsGauge.js +279 -0
  59. package/dist/charts/frameworks/NpsSurvey.js +58 -0
  60. package/dist/charts/frameworks/PerceptualMap.js +419 -0
  61. package/dist/charts/frameworks/PerceptualProjection.js +142 -0
  62. package/dist/charts/frameworks/PositioningSamples.js +191 -0
  63. package/dist/charts/frameworks/RaciMatrix.js +458 -0
  64. package/dist/charts/frameworks/RaciPlan.js +81 -0
  65. package/dist/charts/frameworks/RaciSamples.js +76 -0
  66. package/dist/charts/frameworks/SwotMatrix.js +358 -0
  67. package/dist/charts/frameworks/SwotSamples.js +85 -0
  68. package/dist/charts/frameworks/VanWestendorpChart.js +187 -0
  69. package/dist/charts/frameworks/VanWestendorpSurvey.js +197 -0
  70. package/dist/charts/frameworks/index.js +26 -0
  71. package/dist/charts/frameworks/raciEnums.js +30 -0
  72. package/dist/charts/frameworks/swotEnums.js +34 -0
  73. package/dist/charts/genomics/CircosPlot.js +1 -1
  74. package/dist/charts/geoflows/GeoFlowChart.js +2 -2
  75. package/dist/charts/geoflows/GeoFlowSample.js +2 -1
  76. package/dist/charts/geomaps/FloorPlanMap.js +324 -0
  77. package/dist/charts/geomaps/FloorPlanModel.js +179 -0
  78. package/dist/charts/geomaps/FloorPlanSamples.js +416 -0
  79. package/dist/charts/geomaps/GeoLand.js +2 -2
  80. package/dist/charts/geomaps/GeoSamples.js +2 -1
  81. package/dist/charts/geomaps/GraticuleMap.js +2 -2
  82. package/dist/charts/geomaps/RoomLabelFit.js +200 -0
  83. package/dist/charts/geomaps/TissotMap.js +2 -2
  84. package/dist/charts/geomaps/index.js +8 -4
  85. package/dist/charts/hierarchy/SunburstChart.js +17 -10
  86. package/dist/charts/hierarchy/index.js +2 -11
  87. package/dist/charts/indicators/BulletChart.js +8 -2
  88. package/dist/charts/indicators/SpeedometerChart.js +1 -1
  89. package/dist/charts/indicators/ThermometerChart.js +1 -1
  90. package/dist/charts/lifescience/LoessFit.js +113 -0
  91. package/dist/charts/lifescience/MaPlot.js +337 -0
  92. package/dist/charts/lifescience/MaPlotSamples.js +35 -0
  93. package/dist/charts/lifescience/OncoGlyph.js +137 -0
  94. package/dist/charts/lifescience/OncoPrint.js +373 -0
  95. package/dist/charts/lifescience/OncoPrintMatrix.js +107 -0
  96. package/dist/charts/lifescience/OncoPrintSamples.js +142 -0
  97. package/dist/charts/lifescience/PedigreeChart.js +306 -0
  98. package/dist/charts/lifescience/PedigreeLayout.js +261 -0
  99. package/dist/charts/lifescience/PedigreeRows.js +70 -0
  100. package/dist/charts/lifescience/PedigreeSamples.js +161 -0
  101. package/dist/charts/lifescience/PedigreeSymbol.js +47 -0
  102. package/dist/charts/lifescience/RamachandranPlot.js +364 -0
  103. package/dist/charts/lifescience/RamachandranRegions.js +2712 -0
  104. package/dist/charts/lifescience/RamachandranSamples.js +235 -0
  105. package/dist/charts/lifescience/index.js +25 -0
  106. package/dist/charts/lifescience/pedigreeEnums.js +21 -0
  107. package/dist/charts/lifescience/ramachandranEnums.js +34 -0
  108. package/dist/charts/linesareas/DualAxisChart.js +1 -1
  109. package/dist/charts/linesareas/LineEnums.js +1 -15
  110. package/dist/charts/linesareas/MultiLineChart.js +1 -1
  111. package/dist/charts/linesareas/TrendChart.js +15 -5
  112. package/dist/charts/linesareas/index.js +2 -6
  113. package/dist/charts/massive/MassiveChart.js +3 -2
  114. package/dist/charts/mathematical/FourierSeriesChart.js +7 -1
  115. package/dist/charts/mathematical/index.js +2 -6
  116. package/dist/charts/matrix/GridPlot.js +27 -2
  117. package/dist/charts/matrix/TilegramChart.js +2 -2
  118. package/dist/charts/mlcharts/AttentionGrid.js +79 -0
  119. package/dist/charts/mlcharts/AttentionHeatmap.js +329 -0
  120. package/dist/charts/mlcharts/AttentionModel.js +132 -0
  121. package/dist/charts/mlcharts/AttentionSamples.js +82 -0
  122. package/dist/charts/mlcharts/CellNumber.js +8 -0
  123. package/dist/charts/mlcharts/CurveDiagnosticSamples.js +95 -0
  124. package/dist/charts/mlcharts/CurvePickMark.js +31 -0
  125. package/dist/charts/mlcharts/DecisionBoundaryChart.js +316 -0
  126. package/dist/charts/mlcharts/DecisionSamples.js +60 -0
  127. package/dist/charts/mlcharts/DecisionSurface.js +130 -0
  128. package/dist/charts/mlcharts/DistinctTicks.js +13 -0
  129. package/dist/charts/mlcharts/ElbowMath.js +153 -0
  130. package/dist/charts/mlcharts/ElbowPlot.js +273 -0
  131. package/dist/charts/mlcharts/ExplainabilitySamples.js +70 -0
  132. package/dist/charts/mlcharts/FeatureImportanceChart.js +150 -0
  133. package/dist/charts/mlcharts/ImportanceRanking.js +98 -0
  134. package/dist/charts/mlcharts/LearningCurveChart.js +276 -0
  135. package/dist/charts/mlcharts/LearningCurveMath.js +92 -0
  136. package/dist/charts/mlcharts/PartialDependence.js +92 -0
  137. package/dist/charts/mlcharts/PartialDependencePlot.js +225 -0
  138. package/dist/charts/mlcharts/WideLabel.js +28 -0
  139. package/dist/charts/mlcharts/curveDiagnosticEnums.js +37 -0
  140. package/dist/charts/mlcharts/index.js +32 -0
  141. package/dist/charts/proportion/index.js +5 -9
  142. package/dist/charts/scatter/CategoryScatterBase.js +1 -4
  143. package/dist/charts/scatter/ConnectedScatter.js +7 -4
  144. package/dist/charts/scatter/MarginalScatter.js +13 -8
  145. package/dist/charts/scatter/index.js +4 -17
  146. package/dist/charts/scientific/SpectrogramChart.js +9 -3
  147. package/dist/charts/scientific/index.js +2 -2
  148. package/dist/charts/signals/index.js +3 -13
  149. package/dist/charts/statistics/LorenzCurve.js +7 -4
  150. package/dist/charts/threebars/Candlestick3DChart.js +18 -2
  151. package/dist/charts/threeglobe/GlobeChart.js +2 -2
  152. package/dist/charts/threeglobe/GlobeSamples.js +2 -1
  153. package/dist/charts/threeglobe/RingGlobe.js +1 -1
  154. package/dist/charts/threenetwork/ConeTreeChart.js +2 -1
  155. package/dist/charts/threenetwork/Gantt3DChart.js +3 -2
  156. package/dist/charts/threesurfaces/Ridgeline3DChart.js +22 -4
  157. package/dist/charts/threevolume/LorenzAttractor.js +19 -0
  158. package/dist/charts/threevolume/VolumeRaycastChart.js +4 -3
  159. package/dist/charts/threevolume/volumeEnums.js +1 -0
  160. package/dist/charts/timeline/index.js +2 -10
  161. package/dist/chunk-753UBLV2.cjs +33 -0
  162. package/dist/chunk-VX54A7KA.cjs +17 -0
  163. package/dist/{chunk-W5GAAAZY.cjs → chunk-WAODDPYX.cjs} +24874 -17620
  164. package/dist/chunk-Z3JPIJSC.cjs +338 -0
  165. package/dist/core/CartesianChart.js +87 -11
  166. package/dist/core/Chart.js +63 -15
  167. package/dist/core/Chart2D.js +54 -17
  168. package/dist/core/Chart3D.js +56 -20
  169. package/dist/core/ChartExportError.js +2 -0
  170. package/dist/core/ChartExporter.js +50 -8
  171. package/dist/core/ColumnCheck.js +53 -0
  172. package/dist/core/Context2D.js +29 -15
  173. package/dist/core/Context3D.js +7 -2
  174. package/dist/core/ExcelWriter.js +152 -0
  175. package/dist/core/ExportText.js +28 -0
  176. package/dist/core/MarkOrigin.js +88 -0
  177. package/dist/core/MarkStyle.js +294 -0
  178. package/dist/core/Palette.js +27 -7
  179. package/dist/core/PdfPage.js +13 -0
  180. package/dist/core/PdfWriter.js +245 -0
  181. package/dist/core/Scales.js +52 -2
  182. package/dist/core/SceneOverlay.js +4 -4
  183. package/dist/core/TextFit.js +37 -0
  184. package/dist/core/Theme.js +28 -1
  185. package/dist/core/enums.js +33 -1
  186. package/dist/core/pathMorph.js +15 -0
  187. package/dist/data/SampleData.js +1 -17
  188. package/dist/data/WorldSample.js +23 -0
  189. package/dist/index.cjs +810 -691
  190. package/dist/index.d.cts +8390 -2925
  191. package/dist/index.d.ts +8392 -2927
  192. package/dist/index.js +106 -56
  193. package/dist/react/ChartView.js +18 -8
  194. package/dist/react/GraphHandle.js +2 -0
  195. package/dist/react/components.js +74 -116
  196. package/package.json +13 -3
  197. package/dist/catalog-4UP2YFNR.cjs +0 -7
  198. package/dist/charts/bars/BulletBar.js +0 -184
  199. package/dist/charts/bars/ErrorBarColumn.js +0 -147
  200. package/dist/charts/bars/HistogramBar.js +0 -217
  201. package/dist/charts/bars/MarimekkoBar.js +0 -191
  202. package/dist/charts/bars/ParetoBar.js +0 -143
  203. package/dist/charts/comparison/PanelChart.js +0 -70
  204. package/dist/charts/comparison/SmallMultipleBars.js +0 -134
  205. package/dist/charts/comparison/SpanChart.js +0 -118
  206. package/dist/charts/comparison/TrellisChart.js +0 -158
  207. package/dist/charts/distribution/RidgeSlabs3DChart.js +0 -169
  208. package/dist/charts/financial/Candlestick3D.js +0 -157
  209. package/dist/charts/flow/BumpFlow.js +0 -212
  210. package/dist/charts/flow/StreamFlow.js +0 -174
  211. package/dist/charts/geomaps/ThiessenMap.js +0 -106
  212. package/dist/charts/hierarchy/ConeTree3D.js +0 -135
  213. package/dist/charts/hierarchy/Hierarchy3DChart.js +0 -27
  214. package/dist/charts/hierarchy/SpherePack3D.js +0 -81
  215. package/dist/charts/hierarchy/SunburstTower3D.js +0 -93
  216. package/dist/charts/hierarchy/TreemapSkyline3D.js +0 -98
  217. package/dist/charts/linesareas/ConnectedScatterTime.js +0 -206
  218. package/dist/charts/linesareas/SmallMultipleLines.js +0 -153
  219. package/dist/charts/mathematical/ParametricSurface3D.js +0 -234
  220. package/dist/charts/mathematical/StrangeAttractor3D.js +0 -251
  221. package/dist/charts/proportion/ConeFunnel.js +0 -126
  222. package/dist/charts/proportion/ExtrudedPieChart.js +0 -123
  223. package/dist/charts/scatter/BeeswarmScatter.js +0 -151
  224. package/dist/charts/scatter/BinnedScatterBase.js +0 -51
  225. package/dist/charts/scatter/ContourScatter.js +0 -116
  226. package/dist/charts/scatter/GridBinScatter.js +0 -98
  227. package/dist/charts/scatter/HexbinScatter.js +0 -74
  228. package/dist/charts/scatter/StripScatter.js +0 -107
  229. package/dist/charts/scatter/SunflowerScatter.js +0 -109
  230. package/dist/charts/signals/ConstellationChart.js +0 -153
  231. package/dist/charts/signals/CorrelogramChart.js +0 -171
  232. package/dist/charts/signals/EyeDiagramChart.js +0 -155
  233. package/dist/charts/signals/PoleZeroChart.js +0 -164
  234. package/dist/charts/signals/SpectrogramChart.js +0 -79
  235. package/dist/charts/timeline/CalendarSkylineChart.js +0 -128
  236. package/dist/charts/timeline/ScheduleBlocksChart.js +0 -119
  237. package/dist/charts/timeline/SpiralTimeline.js +0 -154
  238. package/dist/charts/timeline/StreamTimeline.js +0 -141
@@ -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
- svg.select("g.g-header").selectAll("text").data(lines).join("text").attr("x", 16).attr("y", (d) => d.y).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 * .6 > width - 32 ? width - 32 : null).text((d) => d.text);
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
- let x = 16, y = headerHeight + 15;
197
- if (position === LegendPosition.Bottom) y = top + plotHeight + margin.bottom + 14;
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 - 112;
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(item);
205
- if (position !== LegendPosition.Right && x > left && x + cost > width - 8) {
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 += ROW;
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 += 18;
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", 15).attr("font-size", 11.5);
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.name);
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",
@@ -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
- renderer = null;
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
- if (!this.renderer) {
61
- const renderer = this.renderer = new WebGLRenderer({ antialias: true });
62
- renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
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.renderer.domElement.setAttribute("aria-label", this.label);
87
- this.renderer.setSize(width, height);
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.renderer) return;
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.renderer.render(this.scene, this.camera);
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: `12px ${this.theme.fontFamily}`,
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 15px ${this.theme.fontFamily}`,
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.renderer?.setAnimationLoop(null);
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.renderer?.domElement.remove();
180
- this.renderer?.dispose();
181
- this.renderer = this.overlay = this.controls = null;
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.#text(cell);
77
- return /[",\r\n]/.test(text) ? `"${text.replaceAll("\"", "\"\"")}"` : text;
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
- static #text(cell) {
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
- if (typeof cell === "object") return ChartExporter.#json(cell);
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).replace(UNWRITABLE, "");
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, "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 };
@@ -1,9 +1,12 @@
1
- import { easeCubicOut, interpolateString } from "d3";
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 motion = selection.transition().duration(650).ease(easeCubicOut).delay((_, i) => this.live ? 0 : i * stagger);
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
- var arcFlags = (d) => [...d.matchAll(/A[^A-Za-z]*?,[^,]*,([01]),([01]),/g)].map((flag) => flag[1] + flag[2]).join();
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 };
@@ -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
- constructor(root, theme, palette, animated) {
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: {