@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
@@ -0,0 +1,329 @@
1
+ import { ChartFamily, ColorScale, DataShape, Dimension } from "../../core/enums.js";
2
+ import { Scales } from "../../core/Scales.js";
3
+ import { MatrixChart } from "../matrix/MatrixChart.js";
4
+ import { MatrixGlyph, MatrixMarginal } from "../matrix/matrixEnums.js";
5
+ import { AttentionGrid } from "./AttentionGrid.js";
6
+ import { AttentionModel } from "./AttentionModel.js";
7
+ import { AttentionSamples } from "./AttentionSamples.js";
8
+ import { WideLabel } from "./WideLabel.js";
9
+ import { scaleLinear } from "d3";
10
+ // src/charts/mlcharts/AttentionHeatmap.ts
11
+ var AttentionHeadView = /* @__PURE__ */ function(AttentionHeadView) {
12
+ AttentionHeadView["Mean"] = "mean-of-heads";
13
+ AttentionHeadView["SmallMultiples"] = "small-multiples";
14
+ return AttentionHeadView;
15
+ }({});
16
+ var AttentionAspect = /* @__PURE__ */ function(AttentionAspect) {
17
+ AttentionAspect["Square"] = "square-cells";
18
+ AttentionAspect["Fit"] = "fit";
19
+ return AttentionAspect;
20
+ }({});
21
+ var NOTE_SPACE = 16;
22
+ var COVERAGE_GUTTER = 52;
23
+ var PEAK = 4;
24
+ var ROOM = 36;
25
+ var TOLERANCE = .01;
26
+ /**
27
+ * Soft alignment between two token sequences, as in Bahdanau, Cho & Bengio (2015) and the scaled
28
+ * dot-product attention of Vaswani et al. (2017). Rows are target tokens (queries), columns are source
29
+ * tokens (keys), so each row is one softmax and should sum to 1. Weights are drawn as given: `check()`
30
+ * and a note under the plot report what is off, nothing is renormalised. Tokens keep sequence order.
31
+ * Coverage is the sum of a source token's attention over all targets (Tu et al., 2016).
32
+ */
33
+ var AttentionHeatmap = class extends MatrixChart {
34
+ static meta = {
35
+ id: "attention-heatmap",
36
+ name: "Attention heatmap",
37
+ family: ChartFamily.Matrix,
38
+ dimension: Dimension.Two,
39
+ shape: DataShape.Matrix,
40
+ description: "Shows how much each target token attends to each source token, per head or averaged, to inspect what a sequence model looks at.",
41
+ tags: [
42
+ "attention",
43
+ "transformer",
44
+ "alignment",
45
+ "multi-head",
46
+ "machine learning",
47
+ "nlp"
48
+ ],
49
+ sample: (seed) => AttentionSamples.crossAttention(seed),
50
+ variants: {
51
+ headView: ["mean-of-heads", "small-multiples"],
52
+ glyph: [MatrixGlyph.Tile, MatrixGlyph.Circle],
53
+ aspect: ["square-cells", "fit"],
54
+ marginals: [
55
+ MatrixMarginal.None,
56
+ MatrixMarginal.Rows,
57
+ MatrixMarginal.Columns,
58
+ MatrixMarginal.Both
59
+ ],
60
+ alignment: [false, true],
61
+ cellValues: [false, true]
62
+ },
63
+ valid: (v) => !(v.headView === "small-multiples" && (v.marginals !== MatrixMarginal.None || v.cellValues)) && !(v.cellValues && (v.alignment || v.glyph !== MatrixGlyph.Tile))
64
+ };
65
+ #panels = 0;
66
+ defaults() {
67
+ return {
68
+ ...super.defaults(),
69
+ source: "source",
70
+ target: "target",
71
+ weight: "weight",
72
+ head: "head",
73
+ sourceIndex: "sourceIndex",
74
+ targetIndex: "targetIndex",
75
+ headView: "mean-of-heads",
76
+ glyph: MatrixGlyph.Tile,
77
+ aspect: "square-cells",
78
+ marginals: MatrixMarginal.None,
79
+ alignment: false,
80
+ cellValues: false,
81
+ colorScale: ColorScale.Sequential,
82
+ sumTolerance: TOLERANCE
83
+ };
84
+ }
85
+ get attention() {
86
+ return AttentionModel.from(this.options.data, this.options);
87
+ }
88
+ get model() {
89
+ return this.attention.pooled;
90
+ }
91
+ get blank() {
92
+ return this.attention.empty;
93
+ }
94
+ /** Whether each row is a distribution, across every head. Reports only: the weights are never changed. */
95
+ check() {
96
+ const { sumTolerance } = this.options;
97
+ return this.attention.check(sumTolerance >= 0 ? sumTolerance : TOLERANCE);
98
+ }
99
+ table() {
100
+ const { heads } = this.attention;
101
+ return {
102
+ columns: [
103
+ "head",
104
+ "targetIndex",
105
+ "target",
106
+ "sourceIndex",
107
+ "source",
108
+ "weight"
109
+ ],
110
+ rows: heads.flatMap(({ name, model }) => model.cells.map((cell) => [
111
+ name,
112
+ cell.r,
113
+ cell.row,
114
+ cell.c,
115
+ cell.column,
116
+ cell.value
117
+ ]))
118
+ };
119
+ }
120
+ views(attention) {
121
+ const { headView, focusHead } = this.options, { heads } = attention;
122
+ if (headView === "small-multiples") return heads.map(({ name, model }) => ({
123
+ title: name,
124
+ model
125
+ }));
126
+ const focused = heads[focusHead ?? -1];
127
+ if (focused) return [{
128
+ title: focused.name,
129
+ model: focused.model
130
+ }];
131
+ return [{
132
+ title: heads.length > 1 ? `Mean of ${heads.length} heads` : heads[0].name,
133
+ model: attention.pooled
134
+ }];
135
+ }
136
+ domain(views) {
137
+ const low = Math.min(0, ...views.map(({ model }) => model.domain[0]));
138
+ const top = this.options.scaleMax ?? Math.max(...views.map(({ model }) => model.domain[1]));
139
+ return [low, top > low ? top : low + 1];
140
+ }
141
+ note({ checked, failing, tolerance, blankRows, missing, negative, irregular }) {
142
+ return [
143
+ failing.length && `Row sums off 1 by more than ${tolerance}: ${failing.length} of ${checked}`,
144
+ blankRows && `Blank rows: ${blankRows}`,
145
+ missing && `Missing weights: ${missing}`,
146
+ negative && `Negative weights: ${negative}`,
147
+ irregular && `Irregular entries: ${irregular}`
148
+ ].filter(Boolean).join(" · ");
149
+ }
150
+ drawMatrix(ctx) {
151
+ const { aspect, marginals, cellValues, glyph } = this.options;
152
+ const attention = this.attention, views = this.views(attention);
153
+ if (views.length !== this.#panels) ctx.root.selectAll("*").remove();
154
+ this.#panels = views.length;
155
+ const note = this.note(this.check());
156
+ const onRight = marginals === MatrixMarginal.Rows || marginals === MatrixMarginal.Both;
157
+ const onTop = marginals === MatrixMarginal.Columns || marginals === MatrixMarginal.Both;
158
+ const layout = AttentionGrid.plan({
159
+ plot: ctx.plot,
160
+ rows: attention.target,
161
+ columns: attention.source,
162
+ panels: views.length,
163
+ square: aspect === "square-cells",
164
+ titled: views.some((view) => view.title),
165
+ right: onRight ? 44 : 0,
166
+ top: onTop ? 44 : 0,
167
+ bottom: 22 + (note ? NOTE_SPACE : 0),
168
+ minGutter: onTop ? COVERAGE_GUTTER : 0
169
+ });
170
+ const domain = this.domain(views), fill = this.heat(ctx, domain);
171
+ const hasTitles = views.length > 1;
172
+ this.tokens(ctx, attention, layout);
173
+ views.forEach((view, panel) => this.cells(ctx, view, layout.frames[panel], panel, domain, fill, hasTitles));
174
+ this.titles(ctx, views, layout);
175
+ this.peaks(ctx, views, layout, fill);
176
+ if (views.length === 1) {
177
+ this.rails(ctx, views[0].model, layout.frames[0], onRight, onTop);
178
+ const shown = cellValues && glyph === MatrixGlyph.Tile ? views[0].model.cells.filter((cell) => cell.value !== null) : [];
179
+ this.cellText(ctx, "cell-values", shown, layout.frames[0], (cell) => cell.value.toFixed(2), (cell) => this.inkOn(ctx, fill(cell.value)));
180
+ }
181
+ const rampAt = layout.bottom + 6;
182
+ this.ramp(ctx, domain, layout.frames[0].x, rampAt, (value) => value !== 0 && Math.abs(domain[1]) < 1 ? value.toPrecision(2) : Scales.format(value, Math.abs(domain[1])));
183
+ ctx.text(ctx.marks("check-note", "text", note ? [note] : []), 10.5, "secondary").attr("x", 0).attr("y", rampAt + 27).text((text) => WideLabel.fit(text, ctx.plot.width, 5.8));
184
+ }
185
+ cells(ctx, { title, model }, frame, panel, [, top], fill, multiple) {
186
+ const sums = model.rowTotals;
187
+ this.glyphs(ctx, `cells-${panel}`, model.cells, frame, {
188
+ glyph: this.options.glyph,
189
+ size: (cell) => Math.max(0, cell.value ?? 0) / top,
190
+ fill: (cell) => fill(cell.value),
191
+ tip: (cell) => ({
192
+ title: `${cell.row} → ${cell.column}`,
193
+ color: fill(cell.value),
194
+ rows: [
195
+ ...multiple ? [["Head", title]] : [],
196
+ ["Weight", cell.value === null ? "no data" : cell.value.toFixed(3)],
197
+ ["Target position", cell.r + 1],
198
+ ["Source position", cell.c + 1],
199
+ ["Row sum", sums[cell.r].toFixed(3)]
200
+ ]
201
+ })
202
+ });
203
+ }
204
+ tokens(ctx, { target, source }, { frames, across, gutter, header, flatHeader, below, rowStep, columnStep }) {
205
+ const rows = frames.flatMap((frame, panel) => panel % across ? [] : target.flatMap((token, r) => r % rowStep ? [] : [{
206
+ key: `${panel}/${r}`,
207
+ token,
208
+ x: frame.x - 6,
209
+ y: frame.cy(r)
210
+ }]));
211
+ ctx.text(ctx.marks("row-labels", "text", rows, (d) => d.key)).attr("text-anchor", "end").attr("dy", "0.32em").attr("x", (d) => d.x).attr("y", (d) => d.y).text((d) => WideLabel.fit(d.token, gutter - 6));
212
+ const columns = frames.slice(0, across).flatMap((frame, panel) => source.flatMap((token, c) => {
213
+ if (c % columnStep) return [];
214
+ const y = below ? frame.y + frame.height + (flatHeader ? 13 : 5) : frame.y - 5;
215
+ return [{
216
+ key: `${panel}/${c}`,
217
+ token,
218
+ transform: `translate(${frame.cx(c)},${y})${flatHeader ? "" : " rotate(-90)"}`
219
+ }];
220
+ }));
221
+ const anchor = flatHeader ? "middle" : below ? "end" : "start";
222
+ ctx.text(ctx.marks("column-labels", "text", columns, (d) => d.key)).attr("text-anchor", anchor).attr("dy", flatHeader ? null : "0.32em").attr("transform", (d) => d.transform).text((d) => WideLabel.fit(d.token, flatHeader ? frames[0].cw : header - 4));
223
+ }
224
+ titles(ctx, views, { frames, across, header, below }) {
225
+ const placed = views.flatMap(({ title }, panel) => {
226
+ const frame = frames[panel], lifted = panel < across ? below ? 44 : header : 0;
227
+ return title ? [{
228
+ panel,
229
+ title,
230
+ frame,
231
+ y: frame.y - lifted - 5
232
+ }] : [];
233
+ });
234
+ ctx.text(ctx.marks("panel-titles", "text", placed, (d) => d.panel), 11, "ink").attr("x", (d) => d.frame.x).attr("y", (d) => d.y).text((d) => WideLabel.fit(d.title, d.frame.width));
235
+ }
236
+ /** The hard alignment: each row's largest weight, as a dot on a tile or a full-size ring round a sized glyph. A flat row has no peak. */
237
+ peaks(ctx, views, { frames }, fill) {
238
+ const peaks = this.options.alignment ? views.flatMap(({ model }, panel) => model.values.flatMap((row, r) => {
239
+ const present = row.filter((value) => value !== null);
240
+ if (new Set(present).size < 2) return [];
241
+ const value = Math.max(...present);
242
+ return [{
243
+ key: `${panel}/${r}`,
244
+ frame: frames[panel],
245
+ r,
246
+ c: row.indexOf(value),
247
+ value
248
+ }];
249
+ })) : [];
250
+ const tile = this.options.glyph === MatrixGlyph.Tile;
251
+ const room = (frame) => Math.min(frame.cw, frame.ch) / 2 - 1;
252
+ const marks = ctx.marks("peaks", "circle", peaks, (d) => d.key).attr("pointer-events", "none").attr("fill", (d) => tile ? this.inkOn(ctx, fill(d.value)) : "none").attr("stroke", tile ? null : ctx.theme.ink).attr("stroke-width", tile ? null : 1.5);
253
+ ctx.set(marks, {
254
+ cx: (d) => d.frame.cx(d.c),
255
+ cy: (d) => d.frame.cy(d.r),
256
+ r: (d) => Math.max(1.5, tile ? Math.min(PEAK, room(d.frame)) : room(d.frame))
257
+ }, { r: 0 });
258
+ }
259
+ /** Bars from zero, with a tick at 1 so a row sum or a full share of coverage can be read against it. */
260
+ rails(ctx, model, frame, onRight, onTop) {
261
+ const scale = (totals) => scaleLinear([Math.min(0, ...totals), Math.max(1, ...totals)], [0, ROOM]);
262
+ const format = (value) => value.toFixed(3);
263
+ const rowTotals = onRight ? model.rowTotals : [], across = scale(rowTotals), left = frame.x + frame.width + 6, thick = Math.max(1, Math.min(10, frame.ch - 2));
264
+ const rowBars = ctx.marks("row-rail", "rect", rowTotals.map((total, index) => ({
265
+ total,
266
+ index
267
+ })), (d) => d.index).attr("fill", ctx.palette.sequential(.62)).attr("rx", 2);
268
+ ctx.set(rowBars, {
269
+ x: (d) => left + Math.min(across(0), across(d.total)),
270
+ y: (d) => frame.cy(d.index) - thick / 2,
271
+ width: (d) => Math.abs(across(d.total) - across(0)),
272
+ height: thick
273
+ }, {
274
+ x: left + across(0),
275
+ y: (d) => frame.cy(d.index) - thick / 2,
276
+ width: 0,
277
+ height: thick
278
+ });
279
+ ctx.hover(rowBars, (d) => ({
280
+ title: model.rows[d.index],
281
+ rows: [["Row sum", format(d.total)]]
282
+ }));
283
+ const columnTotals = onTop ? model.columnTotals : [], up = scale(columnTotals), base = frame.y - 6, wide = Math.max(1, Math.min(10, frame.cw - 2));
284
+ const columnBars = ctx.marks("column-rail", "rect", columnTotals.map((total, index) => ({
285
+ total,
286
+ index
287
+ })), (d) => d.index).attr("fill", ctx.palette.sequential(.62)).attr("rx", 2);
288
+ ctx.set(columnBars, {
289
+ x: (d) => frame.cx(d.index) - wide / 2,
290
+ y: (d) => base - Math.max(up(0), up(d.total)),
291
+ width: wide,
292
+ height: (d) => Math.abs(up(d.total) - up(0))
293
+ }, {
294
+ x: (d) => frame.cx(d.index) - wide / 2,
295
+ y: base - up(0),
296
+ width: wide,
297
+ height: 0
298
+ });
299
+ ctx.hover(columnBars, (d) => ({
300
+ title: model.columns[d.index],
301
+ rows: [["Coverage", format(d.total)]]
302
+ }));
303
+ const ticks = [...onRight ? [{
304
+ key: "row",
305
+ x1: left + across(1),
306
+ x2: left + across(1),
307
+ y1: frame.y,
308
+ y2: frame.y + frame.height
309
+ }] : [], ...onTop ? [{
310
+ key: "column",
311
+ x1: frame.x,
312
+ x2: frame.x + frame.width,
313
+ y1: base - up(1),
314
+ y2: base - up(1)
315
+ }] : []];
316
+ ctx.marks("rail-ticks", "line", ticks, (d) => d.key).attr("stroke", ctx.theme.axis).attr("stroke-width", 1).attr("x1", (d) => d.x1).attr("x2", (d) => d.x2).attr("y1", (d) => d.y1).attr("y2", (d) => d.y2);
317
+ const titles = [...onRight ? [{
318
+ text: "Row sum",
319
+ x: left,
320
+ anchor: "start"
321
+ }] : [], ...onTop ? [{
322
+ text: "Coverage",
323
+ x: frame.x - 6,
324
+ anchor: "end"
325
+ }] : []];
326
+ ctx.text(ctx.marks("rail-titles", "text", titles, (d) => d.text), 10.5, "muted").attr("x", (d) => d.x).attr("y", base).attr("text-anchor", (d) => d.anchor).text((d) => d.text);
327
+ }
328
+ };
329
+ export { AttentionAspect, AttentionHeadView, AttentionHeatmap };
@@ -0,0 +1,132 @@
1
+ import { MatrixModel } from "../matrix/MatrixModel.js";
2
+ import { sum } from "d3";
3
+ // src/charts/mlcharts/AttentionModel.ts
4
+ var token = (value) => value === null || value === void 0 ? "" : String(value);
5
+ var finite = (value) => value !== null && value !== void 0 && value !== "" && Number.isFinite(+value);
6
+ /** Attention weights per head on one pair of token axes. Rows are targets (queries) and columns are sources (keys). */
7
+ var AttentionModel = class AttentionModel {
8
+ source;
9
+ target;
10
+ heads;
11
+ irregular;
12
+ constructor(source, target, heads, irregular) {
13
+ this.source = source;
14
+ this.target = target;
15
+ this.heads = heads;
16
+ this.irregular = irregular;
17
+ }
18
+ static from(data, fields = {}) {
19
+ if (!data || typeof data !== "object") return AttentionModel.grids({
20
+ source: [],
21
+ target: [],
22
+ heads: []
23
+ });
24
+ if (Array.isArray(data)) return AttentionModel.long(data, fields);
25
+ if ("heads" in data) return AttentionModel.grids(data);
26
+ const { rows = [], columns = [], values = [] } = data;
27
+ return AttentionModel.grids({
28
+ source: columns,
29
+ target: rows,
30
+ heads: [{ weights: values }]
31
+ });
32
+ }
33
+ static grids({ source: sources, target: targets, heads }, extra = 0) {
34
+ const source = Array.isArray(sources) ? sources.map(token) : [], target = Array.isArray(targets) ? targets.map(token) : [];
35
+ const list = Array.isArray(heads) ? heads : [];
36
+ let irregular = extra;
37
+ const models = list.map((head, h) => {
38
+ const weights = Array.isArray(head?.weights) ? head.weights : [];
39
+ irregular += Math.abs(weights.length - target.length);
40
+ for (const row of weights.slice(0, target.length)) if (row?.length !== source.length) irregular++;
41
+ return {
42
+ name: head?.name ?? (list.length > 1 ? `Head ${h + 1}` : ""),
43
+ model: MatrixModel.from({
44
+ rows: target,
45
+ columns: source,
46
+ values: weights
47
+ })
48
+ };
49
+ });
50
+ return new AttentionModel(source, target, models, irregular);
51
+ }
52
+ static long(rows, { source = "source", target = "target", weight = "weight", head = "head", sourceIndex = "sourceIndex", targetIndex = "targetIndex" }) {
53
+ const placed = rows.filter((row) => row && row[source] != null && row[target] != null);
54
+ const sources = AttentionModel.axis(placed, source, sourceIndex), targets = AttentionModel.axis(placed, target, targetIndex);
55
+ const heads = /* @__PURE__ */ new Map(), seen = /* @__PURE__ */ new Set();
56
+ let irregular = rows.length - placed.length;
57
+ for (const row of placed) {
58
+ const name = token(row[head]);
59
+ if (!heads.has(name)) heads.set(name, targets.tokens.map(() => sources.tokens.map(() => null)));
60
+ const t = targets.at(row), s = sources.at(row), cell = `${name}/${t}/${s}`;
61
+ if (seen.has(cell)) irregular++;
62
+ seen.add(cell);
63
+ heads.get(name)[t][s] = row[weight];
64
+ }
65
+ return AttentionModel.grids({
66
+ source: sources.tokens,
67
+ target: targets.tokens,
68
+ heads: [...heads].map(([name, weights]) => ({
69
+ name,
70
+ weights
71
+ }))
72
+ }, irregular);
73
+ }
74
+ /** Token order along one axis: by position when every row has one, otherwise first seen. */
75
+ static axis(rows, text, position) {
76
+ const positional = rows.every((row) => finite(row[position]));
77
+ const keyOf = (row) => positional ? +row[position] : token(row[text]);
78
+ const slots = /* @__PURE__ */ new Map();
79
+ for (const row of rows) if (!slots.has(keyOf(row))) slots.set(keyOf(row), token(row[text]));
80
+ const keys = positional ? [...slots.keys()].sort((a, b) => +a - +b) : [...slots.keys()];
81
+ const index = new Map(keys.map((key, i) => [key, i]));
82
+ return {
83
+ tokens: keys.map((key) => slots.get(key)),
84
+ at: (row) => index.get(keyOf(row))
85
+ };
86
+ }
87
+ get empty() {
88
+ return !this.source.length || !this.target.length || !this.heads.length;
89
+ }
90
+ /** The cell-wise mean over heads, ignoring heads with no value at that cell. */
91
+ get pooled() {
92
+ if (this.heads.length === 1) return this.heads[0].model;
93
+ const values = this.target.map((_, t) => this.source.map((_, s) => {
94
+ const present = this.heads.map(({ model }) => model.values[t][s]).filter((value) => value !== null);
95
+ return present.length ? sum(present) / present.length : null;
96
+ }));
97
+ return new MatrixModel(this.target, this.source, values);
98
+ }
99
+ /** Checks each head's rows against the softmax property that a row sums to 1, without changing a weight. */
100
+ check(tolerance = .01) {
101
+ const failing = [];
102
+ let checked = 0, blankRows = 0, missing = 0, negative = 0;
103
+ for (const { name, model } of this.heads) model.values.forEach((row, r) => {
104
+ const present = row.filter((value) => value !== null);
105
+ missing += row.length - present.length;
106
+ negative += present.filter((value) => value < 0).length;
107
+ if (!present.length) {
108
+ blankRows++;
109
+ return;
110
+ }
111
+ checked++;
112
+ const total = sum(present);
113
+ if (Math.abs(total - 1) > tolerance) failing.push({
114
+ head: name,
115
+ row: r,
116
+ token: this.target[r],
117
+ sum: total
118
+ });
119
+ });
120
+ return {
121
+ tolerance,
122
+ checked,
123
+ failing,
124
+ blankRows,
125
+ missing,
126
+ negative,
127
+ irregular: this.irregular,
128
+ ok: !failing.length && !blankRows && !missing && !negative && !this.irregular
129
+ };
130
+ }
131
+ };
132
+ export { AttentionModel };
@@ -0,0 +1,82 @@
1
+ import { SampleData } from "../../data/SampleData.js";
2
+ // src/charts/mlcharts/AttentionSamples.ts
3
+ var SOURCE = [
4
+ "the",
5
+ "cat",
6
+ "sat",
7
+ "on",
8
+ "the",
9
+ "mat",
10
+ ".",
11
+ "</s>"
12
+ ];
13
+ var TARGET = [
14
+ "le",
15
+ "chat",
16
+ "était",
17
+ "assis",
18
+ "sur",
19
+ "le",
20
+ "tapis",
21
+ ".",
22
+ "</s>"
23
+ ];
24
+ /** Which source position each target token translates. */
25
+ var ALIGNED = [
26
+ 0,
27
+ 1,
28
+ 2,
29
+ 2,
30
+ 3,
31
+ 4,
32
+ 5,
33
+ 6,
34
+ 7
35
+ ];
36
+ /** Each head's focus on the source and how wide it spreads, as a Gaussian in logit space. */
37
+ var HEADS = [
38
+ {
39
+ name: "Head 1",
40
+ focus: (aligned) => aligned,
41
+ spread: .6
42
+ },
43
+ {
44
+ name: "Head 2",
45
+ focus: (aligned) => aligned - 1,
46
+ spread: .8
47
+ },
48
+ {
49
+ name: "Head 3",
50
+ focus: (aligned) => aligned,
51
+ spread: 2.5
52
+ },
53
+ {
54
+ name: "Head 4",
55
+ focus: () => 1,
56
+ spread: 1.2
57
+ }
58
+ ];
59
+ var NOISE = .3;
60
+ /** SYNTHETIC. Invented weights for an invented sentence pair, not the output of any model. */
61
+ var AttentionSamples = class AttentionSamples {
62
+ /** Softmax over noisy logits, so each row is a true probability distribution. */
63
+ static crossAttention(seed = 1) {
64
+ return {
65
+ source: SOURCE,
66
+ target: TARGET,
67
+ heads: HEADS.map(({ name, focus, spread }, h) => {
68
+ const { values } = SampleData.matrix(seed + h, TARGET.length, SOURCE.length);
69
+ return {
70
+ name,
71
+ weights: TARGET.map((_, t) => AttentionSamples.softmax(SOURCE.map((_, s) => -((s - focus(ALIGNED[t])) ** 2) / (2 * spread ** 2) + NOISE * values[t][s])))
72
+ };
73
+ })
74
+ };
75
+ }
76
+ static softmax(logits) {
77
+ const peak = Math.max(...logits), raised = logits.map((logit) => Math.exp(logit - peak));
78
+ const total = raised.reduce((sum, value) => sum + value, 0);
79
+ return raised.map((value) => value / total);
80
+ }
81
+ };
82
+ export { AttentionSamples };
@@ -0,0 +1,8 @@
1
+ // src/charts/mlcharts/CellNumber.ts
2
+ /** A table cell as a finite number, or `null` for blanks, text and dates. */
3
+ var cellNumber = (cell) => {
4
+ if (typeof cell === "string" && !cell.trim()) return null;
5
+ const value = typeof cell === "number" || typeof cell === "string" ? Number(cell) : NaN;
6
+ return Number.isFinite(value) ? value : null;
7
+ };
8
+ export { cellNumber };
@@ -0,0 +1,95 @@
1
+ import { SampleData } from "../../data/SampleData.js";
2
+ // src/charts/mlcharts/CurveDiagnosticSamples.ts
3
+ var squared = (a, b) => (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2;
4
+ /** Synthetic demo data: nothing here is a real measurement. */
5
+ var CurveDiagnosticSamples = class {
6
+ /** Standard normal draws from `SampleData`, since its generators are the only seeded source. */
7
+ static #normals(seed, count) {
8
+ const values = SampleData.distribution(seed, count, 1).map((row) => (row.value - 40) / 8);
9
+ let i = 0;
10
+ return () => values[i++ % values.length];
11
+ }
12
+ /**
13
+ * `{ epoch, run, split, value }`: a made-up loss from exponential decay, with validation drifting upward after a
14
+ * run-specific epoch so that it overfits. Not the output of any model.
15
+ */
16
+ static trainingRuns(seed, epochs = 60, runs = 5) {
17
+ const z = this.#normals(seed, runs * (3 + epochs * 2));
18
+ return Array.from({ length: runs }, (_, r) => {
19
+ const run = `seed-${r + 1}`, tau = 9 + z() * .8, turn = 24 + z() * 3, level = .33 + z() * .025;
20
+ return Array.from({ length: epochs }, (_, i) => {
21
+ const epoch = i + 1, decay = 2.2 * Math.exp(-epoch / tau), noise = .25 + Math.exp(-epoch / 20);
22
+ const train = decay + .09 * Math.exp(-epoch / 60) + z() * .012 * noise;
23
+ const drift = 45e-5 * Math.max(0, epoch - turn) ** 2;
24
+ const validation = decay + level + drift + z() * .03 * noise;
25
+ return [{
26
+ epoch,
27
+ run,
28
+ split: "train",
29
+ value: +Math.max(.001, train).toFixed(4)
30
+ }, {
31
+ epoch,
32
+ run,
33
+ split: "validation",
34
+ value: +validation.toFixed(4)
35
+ }];
36
+ }).flat();
37
+ }).flat();
38
+ }
39
+ /** `{ k, inertia, silhouette }` from running k-means and the silhouette on `SampleData.points`, which is itself synthetic. */
40
+ static clusterSweep(seed, most = 10) {
41
+ const points = SampleData.points(seed, 160, 4).map((row, i) => [row.x, row.y - .8 * row.x + i % 2 * 42]);
42
+ return Array.from({ length: most }, (_, i) => {
43
+ const k = i + 1, { labels, inertia } = this.#kMeans(points, k);
44
+ return {
45
+ k,
46
+ inertia: +inertia.toFixed(1),
47
+ silhouette: k > 1 ? +this.#silhouette(points, labels, k).toFixed(4) : null
48
+ };
49
+ });
50
+ }
51
+ /** Lloyd's algorithm from farthest-first seeds, so that one seed always gives one answer. */
52
+ static #kMeans(points, k) {
53
+ const centers = [points[0]];
54
+ while (centers.length < k) {
55
+ const reach = points.map((point) => Math.min(...centers.map((center) => squared(point, center))));
56
+ centers.push(points[reach.indexOf(Math.max(...reach))]);
57
+ }
58
+ let labels = [];
59
+ for (let pass = 0; pass < 40; pass++) {
60
+ const next = points.map((point) => {
61
+ const reach = centers.map((center) => squared(point, center));
62
+ return reach.indexOf(Math.min(...reach));
63
+ });
64
+ if (next.every((label, i) => label === labels[i])) break;
65
+ labels = next;
66
+ centers.forEach((_, c) => {
67
+ const members = points.filter((__, i) => labels[i] === c);
68
+ if (members.length) centers[c] = [members.reduce((a, p) => a + p[0], 0) / members.length, members.reduce((a, p) => a + p[1], 0) / members.length];
69
+ });
70
+ }
71
+ return {
72
+ labels,
73
+ inertia: points.reduce((a, point, i) => a + squared(point, centers[labels[i]]), 0)
74
+ };
75
+ }
76
+ /** Mean silhouette width (Rousseeuw 1987); a point alone in its cluster scores zero. */
77
+ static #silhouette(points, labels, k) {
78
+ const widths = points.map((point, i) => {
79
+ const totals = new Array(k).fill(0), sizes = new Array(k).fill(0);
80
+ points.forEach((other, j) => {
81
+ if (j !== i) {
82
+ totals[labels[j]] += Math.sqrt(squared(point, other));
83
+ sizes[labels[j]]++;
84
+ }
85
+ });
86
+ const own = labels[i];
87
+ if (!sizes[own]) return 0;
88
+ const a = totals[own] / sizes[own];
89
+ const b = Math.min(...totals.flatMap((total, c) => c === own || !sizes[c] ? [] : [total / sizes[c]]));
90
+ return (b - a) / Math.max(a, b);
91
+ });
92
+ return widths.reduce((a, b) => a + b, 0) / widths.length;
93
+ }
94
+ };
95
+ export { CurveDiagnosticSamples };
@@ -0,0 +1,31 @@
1
+ // src/charts/mlcharts/CurvePickMark.ts
2
+ var CHAR = 6.2;
3
+ var OFFSET = 7;
4
+ /** Dashed rule, ringed dot and one label, shared by the charts that single out a point on a curve. */
5
+ var CurvePickMark = class {
6
+ /** `labelY` is the text baseline, so the caller can keep the label off its curve. */
7
+ static draw(ctx, name, pick, labelY) {
8
+ const { width, height } = ctx.plot, picks = pick ? [pick] : [];
9
+ const rule = ctx.marks(`${name}-rule`, "line", picks).attr("stroke", ctx.theme.inkMuted).attr("stroke-width", 1).attr("stroke-dasharray", "4 3");
10
+ ctx.set(rule, {
11
+ x1: (d) => d.x,
12
+ x2: (d) => d.x,
13
+ y1: 0,
14
+ y2: height
15
+ });
16
+ const dot = ctx.marks(`${name}-dot`, "circle", picks).attr("fill", ctx.theme.ink).attr("stroke", ctx.theme.surface).attr("stroke-width", 2);
17
+ ctx.set(dot, {
18
+ cx: (d) => d.x,
19
+ cy: (d) => d.y,
20
+ r: 6
21
+ }, { r: 0 });
22
+ const reach = (d) => d.text.length * CHAR;
23
+ const flip = (d) => d.x + OFFSET + reach(d) > width;
24
+ const label = ctx.text(ctx.marks(`${name}-label`, "text", picks.filter((d) => reach(d) <= width)), 11, "ink").attr("text-anchor", (d) => flip(d) ? "end" : "start").attr("paint-order", "stroke").attr("stroke", ctx.theme.surface).attr("stroke-width", 3).text((d) => d.text);
25
+ ctx.set(label, {
26
+ x: (d) => flip(d) ? Math.max(reach(d), d.x - OFFSET) : d.x + OFFSET,
27
+ y: labelY
28
+ });
29
+ }
30
+ };
31
+ export { CurvePickMark };