exploreda 0.0.5 → 0.1.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 (208) hide show
  1. package/README.md +144 -0
  2. package/dist/ExplorEda.cjs +4608 -10858
  3. package/dist/ExplorEda.cjs.map +1 -1
  4. package/dist/ExplorEda.css +2332 -1653
  5. package/dist/ExplorEda.css.map +1 -1
  6. package/dist/ExplorEda.d.cts +125 -278
  7. package/dist/ExplorEda.d.ts +125 -278
  8. package/dist/ExplorEda.js +4521 -10782
  9. package/dist/ExplorEda.js.map +1 -1
  10. package/dist/calculations.cjs +18 -0
  11. package/dist/calculations.cjs.map +1 -0
  12. package/dist/calculations.d.cts +63 -0
  13. package/dist/calculations.d.ts +63 -0
  14. package/dist/calculations.js +3 -0
  15. package/dist/calculations.js.map +1 -0
  16. package/dist/charts/bar.cjs +27 -0
  17. package/dist/charts/bar.cjs.map +1 -0
  18. package/dist/charts/bar.d.cts +2 -0
  19. package/dist/charts/bar.d.ts +2 -0
  20. package/dist/charts/bar.js +18 -0
  21. package/dist/charts/bar.js.map +1 -0
  22. package/dist/charts/box-plot.cjs +26 -0
  23. package/dist/charts/box-plot.cjs.map +1 -0
  24. package/dist/charts/box-plot.d.cts +2 -0
  25. package/dist/charts/box-plot.d.ts +2 -0
  26. package/dist/charts/box-plot.js +17 -0
  27. package/dist/charts/box-plot.js.map +1 -0
  28. package/dist/charts/color-legend.cjs +22 -0
  29. package/dist/charts/color-legend.cjs.map +1 -0
  30. package/dist/charts/color-legend.d.cts +2 -0
  31. package/dist/charts/color-legend.d.ts +2 -0
  32. package/dist/charts/color-legend.js +13 -0
  33. package/dist/charts/color-legend.js.map +1 -0
  34. package/dist/charts/data-table.cjs +22 -0
  35. package/dist/charts/data-table.cjs.map +1 -0
  36. package/dist/charts/data-table.d.cts +2 -0
  37. package/dist/charts/data-table.d.ts +2 -0
  38. package/dist/charts/data-table.js +13 -0
  39. package/dist/charts/data-table.js.map +1 -0
  40. package/dist/charts/line.cjs +30 -0
  41. package/dist/charts/line.cjs.map +1 -0
  42. package/dist/charts/line.d.cts +2 -0
  43. package/dist/charts/line.d.ts +2 -0
  44. package/dist/charts/line.js +17 -0
  45. package/dist/charts/line.js.map +1 -0
  46. package/dist/charts/markdown.cjs +16 -0
  47. package/dist/charts/markdown.cjs.map +1 -0
  48. package/dist/charts/markdown.css +77 -0
  49. package/dist/charts/markdown.css.map +1 -0
  50. package/dist/charts/markdown.d.cts +2 -0
  51. package/dist/charts/markdown.d.ts +2 -0
  52. package/dist/charts/markdown.js +7 -0
  53. package/dist/charts/markdown.js.map +1 -0
  54. package/dist/charts/pivot-table.cjs +23 -0
  55. package/dist/charts/pivot-table.cjs.map +1 -0
  56. package/dist/charts/pivot-table.d.cts +2 -0
  57. package/dist/charts/pivot-table.d.ts +2 -0
  58. package/dist/charts/pivot-table.js +14 -0
  59. package/dist/charts/pivot-table.js.map +1 -0
  60. package/dist/charts/row.cjs +27 -0
  61. package/dist/charts/row.cjs.map +1 -0
  62. package/dist/charts/row.d.cts +6 -0
  63. package/dist/charts/row.d.ts +6 -0
  64. package/dist/charts/row.js +18 -0
  65. package/dist/charts/row.js.map +1 -0
  66. package/dist/charts/scatter.cjs +24 -0
  67. package/dist/charts/scatter.cjs.map +1 -0
  68. package/dist/charts/scatter.d.cts +2 -0
  69. package/dist/charts/scatter.d.ts +2 -0
  70. package/dist/charts/scatter.js +15 -0
  71. package/dist/charts/scatter.js.map +1 -0
  72. package/dist/charts/summary-table.cjs +19 -0
  73. package/dist/charts/summary-table.cjs.map +1 -0
  74. package/dist/charts/summary-table.d.cts +2 -0
  75. package/dist/charts/summary-table.d.ts +2 -0
  76. package/dist/charts/summary-table.js +10 -0
  77. package/dist/charts/summary-table.js.map +1 -0
  78. package/dist/charts/three-d-scatter.cjs +24 -0
  79. package/dist/charts/three-d-scatter.cjs.map +1 -0
  80. package/dist/charts/three-d-scatter.d.cts +6 -0
  81. package/dist/charts/three-d-scatter.d.ts +6 -0
  82. package/dist/charts/three-d-scatter.js +15 -0
  83. package/dist/charts/three-d-scatter.js.map +1 -0
  84. package/dist/chunk-3PGYFLE3.cjs +499 -0
  85. package/dist/chunk-3PGYFLE3.cjs.map +1 -0
  86. package/dist/chunk-4HNDKMWF.js +167 -0
  87. package/dist/chunk-4HNDKMWF.js.map +1 -0
  88. package/dist/chunk-4QNENKTV.cjs +1214 -0
  89. package/dist/chunk-4QNENKTV.cjs.map +1 -0
  90. package/dist/chunk-54M32JJB.js +132 -0
  91. package/dist/chunk-54M32JJB.js.map +1 -0
  92. package/dist/chunk-5B23JVH3.cjs +2238 -0
  93. package/dist/chunk-5B23JVH3.cjs.map +1 -0
  94. package/dist/chunk-5LH6NSNL.js +456 -0
  95. package/dist/chunk-5LH6NSNL.js.map +1 -0
  96. package/dist/chunk-6DOKKCJ5.cjs +313 -0
  97. package/dist/chunk-6DOKKCJ5.cjs.map +1 -0
  98. package/dist/chunk-6HZX5CBT.cjs +1074 -0
  99. package/dist/chunk-6HZX5CBT.cjs.map +1 -0
  100. package/dist/chunk-6U5DOIQM.js +1071 -0
  101. package/dist/chunk-6U5DOIQM.js.map +1 -0
  102. package/dist/chunk-7GBZIK2R.cjs +80 -0
  103. package/dist/chunk-7GBZIK2R.cjs.map +1 -0
  104. package/dist/chunk-7PTJNIOD.js +520 -0
  105. package/dist/chunk-7PTJNIOD.js.map +1 -0
  106. package/dist/chunk-AHZYQ7FN.js +319 -0
  107. package/dist/chunk-AHZYQ7FN.js.map +1 -0
  108. package/dist/chunk-AOODAOL4.cjs +55 -0
  109. package/dist/chunk-AOODAOL4.cjs.map +1 -0
  110. package/dist/chunk-ASKFIRMC.cjs +165 -0
  111. package/dist/chunk-ASKFIRMC.cjs.map +1 -0
  112. package/dist/chunk-B7OQUBH4.cjs +171 -0
  113. package/dist/chunk-B7OQUBH4.cjs.map +1 -0
  114. package/dist/chunk-BIMEHDLB.js +72 -0
  115. package/dist/chunk-BIMEHDLB.js.map +1 -0
  116. package/dist/chunk-C62KHV7S.js +1225 -0
  117. package/dist/chunk-C62KHV7S.js.map +1 -0
  118. package/dist/chunk-C7GQT65O.cjs +1119 -0
  119. package/dist/chunk-C7GQT65O.cjs.map +1 -0
  120. package/dist/chunk-CGDWDV4Z.js +1211 -0
  121. package/dist/chunk-CGDWDV4Z.js.map +1 -0
  122. package/dist/chunk-CJQQFNEY.js +69 -0
  123. package/dist/chunk-CJQQFNEY.js.map +1 -0
  124. package/dist/chunk-DHYLSXNR.js +964 -0
  125. package/dist/chunk-DHYLSXNR.js.map +1 -0
  126. package/dist/chunk-DKFA4KOJ.cjs +87 -0
  127. package/dist/chunk-DKFA4KOJ.cjs.map +1 -0
  128. package/dist/chunk-DS5BS7PP.cjs +357 -0
  129. package/dist/chunk-DS5BS7PP.cjs.map +1 -0
  130. package/dist/chunk-EGMFQSGK.js +909 -0
  131. package/dist/chunk-EGMFQSGK.js.map +1 -0
  132. package/dist/chunk-EU5EFHO5.cjs +53 -0
  133. package/dist/chunk-EU5EFHO5.cjs.map +1 -0
  134. package/dist/chunk-FM46QSDM.js +837 -0
  135. package/dist/chunk-FM46QSDM.js.map +1 -0
  136. package/dist/chunk-GV2LRR32.cjs +843 -0
  137. package/dist/chunk-GV2LRR32.cjs.map +1 -0
  138. package/dist/chunk-H3XKFSA5.cjs +322 -0
  139. package/dist/chunk-H3XKFSA5.cjs.map +1 -0
  140. package/dist/chunk-H5AVMMCE.js +347 -0
  141. package/dist/chunk-H5AVMMCE.js.map +1 -0
  142. package/dist/chunk-JDMUN4VK.cjs +986 -0
  143. package/dist/chunk-JDMUN4VK.cjs.map +1 -0
  144. package/dist/chunk-JNO7OXCC.js +2171 -0
  145. package/dist/chunk-JNO7OXCC.js.map +1 -0
  146. package/dist/chunk-JQMG4ZR7.js +111 -0
  147. package/dist/chunk-JQMG4ZR7.js.map +1 -0
  148. package/dist/chunk-L2Q2R43U.cjs +118 -0
  149. package/dist/chunk-L2Q2R43U.cjs.map +1 -0
  150. package/dist/chunk-LBTZVERO.js +466 -0
  151. package/dist/chunk-LBTZVERO.js.map +1 -0
  152. package/dist/chunk-LUUGGTTK.cjs +1227 -0
  153. package/dist/chunk-LUUGGTTK.cjs.map +1 -0
  154. package/dist/chunk-MEEPH7ZC.cjs +911 -0
  155. package/dist/chunk-MEEPH7ZC.cjs.map +1 -0
  156. package/dist/chunk-MJPDJ5AS.js +184 -0
  157. package/dist/chunk-MJPDJ5AS.js.map +1 -0
  158. package/dist/chunk-MJROUIDZ.cjs +219 -0
  159. package/dist/chunk-MJROUIDZ.cjs.map +1 -0
  160. package/dist/chunk-MUWSBYJS.js +528 -0
  161. package/dist/chunk-MUWSBYJS.js.map +1 -0
  162. package/dist/chunk-NDF5LCST.js +1088 -0
  163. package/dist/chunk-NDF5LCST.js.map +1 -0
  164. package/dist/chunk-NGWTPED4.js +311 -0
  165. package/dist/chunk-NGWTPED4.js.map +1 -0
  166. package/dist/chunk-PP4VCONW.js +739 -0
  167. package/dist/chunk-PP4VCONW.js.map +1 -0
  168. package/dist/chunk-PRMC2A6P.cjs +488 -0
  169. package/dist/chunk-PRMC2A6P.cjs.map +1 -0
  170. package/dist/chunk-PST6UU6S.cjs +742 -0
  171. package/dist/chunk-PST6UU6S.cjs.map +1 -0
  172. package/dist/chunk-Q7LK2UWH.js +33 -0
  173. package/dist/chunk-Q7LK2UWH.js.map +1 -0
  174. package/dist/chunk-SFV5LZPD.js +162 -0
  175. package/dist/chunk-SFV5LZPD.js.map +1 -0
  176. package/dist/chunk-SKXGYJJG.cjs +370 -0
  177. package/dist/chunk-SKXGYJJG.cjs.map +1 -0
  178. package/dist/chunk-SWLNKY6R.js +477 -0
  179. package/dist/chunk-SWLNKY6R.js.map +1 -0
  180. package/dist/chunk-TGVLNHUB.cjs +483 -0
  181. package/dist/chunk-TGVLNHUB.cjs.map +1 -0
  182. package/dist/chunk-TLLRW6S3.js +74 -0
  183. package/dist/chunk-TLLRW6S3.js.map +1 -0
  184. package/dist/chunk-TRAIXZ6M.cjs +140 -0
  185. package/dist/chunk-TRAIXZ6M.cjs.map +1 -0
  186. package/dist/chunk-U74A5VK5.cjs +74 -0
  187. package/dist/chunk-U74A5VK5.cjs.map +1 -0
  188. package/dist/chunk-UAVDLBTA.cjs +74 -0
  189. package/dist/chunk-UAVDLBTA.cjs.map +1 -0
  190. package/dist/chunk-UJWS6TRV.js +354 -0
  191. package/dist/chunk-UJWS6TRV.js.map +1 -0
  192. package/dist/chunk-UUJMUPB3.cjs +531 -0
  193. package/dist/chunk-UUJMUPB3.cjs.map +1 -0
  194. package/dist/chunk-VTKHZIJB.cjs +549 -0
  195. package/dist/chunk-VTKHZIJB.cjs.map +1 -0
  196. package/dist/chunk-YXPAWUXO.js +51 -0
  197. package/dist/chunk-YXPAWUXO.js.map +1 -0
  198. package/dist/chunk-ZD64SCZB.js +80 -0
  199. package/dist/chunk-ZD64SCZB.js.map +1 -0
  200. package/dist/core.cjs +28 -0
  201. package/dist/core.cjs.map +1 -0
  202. package/dist/core.d.cts +22 -0
  203. package/dist/core.d.ts +22 -0
  204. package/dist/core.js +3 -0
  205. package/dist/core.js.map +1 -0
  206. package/dist/dataLayerState-B4XmQjQN.d.cts +258 -0
  207. package/dist/dataLayerState-B4XmQjQN.d.ts +258 -0
  208. package/package.json +32 -52
@@ -0,0 +1,1227 @@
1
+ 'use strict';
2
+
3
+ var chunkTRAIXZ6M_cjs = require('./chunk-TRAIXZ6M.cjs');
4
+ var chunkB7OQUBH4_cjs = require('./chunk-B7OQUBH4.cjs');
5
+ var chunkU74A5VK5_cjs = require('./chunk-U74A5VK5.cjs');
6
+ var chunkUUJMUPB3_cjs = require('./chunk-UUJMUPB3.cjs');
7
+ var chunkASKFIRMC_cjs = require('./chunk-ASKFIRMC.cjs');
8
+ var chunkEU5EFHO5_cjs = require('./chunk-EU5EFHO5.cjs');
9
+ var chunkC7GQT65O_cjs = require('./chunk-C7GQT65O.cjs');
10
+ var chunkMJROUIDZ_cjs = require('./chunk-MJROUIDZ.cjs');
11
+ var chunk5B23JVH3_cjs = require('./chunk-5B23JVH3.cjs');
12
+ var lucideReact = require('lucide-react');
13
+ var jsxRuntime = require('react/jsx-runtime');
14
+ var react = require('react');
15
+
16
+ function ScatterPlotSettingsPanel({
17
+ settings,
18
+ onSettingsChange
19
+ }) {
20
+ const { getOrCreateScaleForField } = chunkB7OQUBH4_cjs.useColorScales();
21
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
22
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-[120px_1fr] items-center gap-4", children: [
23
+ /* @__PURE__ */ jsxRuntime.jsx(chunkMJROUIDZ_cjs.Label, { children: "X Field" }),
24
+ /* @__PURE__ */ jsxRuntime.jsx(
25
+ chunkASKFIRMC_cjs.FieldSelector,
26
+ {
27
+ label: "",
28
+ value: settings.xField,
29
+ onChange: (value) => onSettingsChange({ ...settings, xField: value })
30
+ }
31
+ ),
32
+ /* @__PURE__ */ jsxRuntime.jsx(chunkMJROUIDZ_cjs.Label, { children: "Y Field" }),
33
+ /* @__PURE__ */ jsxRuntime.jsx(
34
+ chunkASKFIRMC_cjs.FieldSelector,
35
+ {
36
+ label: "",
37
+ value: settings.yField,
38
+ onChange: (value) => onSettingsChange({ ...settings, yField: value })
39
+ }
40
+ ),
41
+ /* @__PURE__ */ jsxRuntime.jsx(chunkMJROUIDZ_cjs.Label, { children: "Color Field" }),
42
+ /* @__PURE__ */ jsxRuntime.jsx(
43
+ chunkASKFIRMC_cjs.FieldSelector,
44
+ {
45
+ label: "",
46
+ value: settings.colorField ?? "",
47
+ allowClear: true,
48
+ onChange: (value) => onSettingsChange({
49
+ ...settings,
50
+ colorField: value || void 0,
51
+ colorScaleId: value ? getOrCreateScaleForField(value) : void 0
52
+ })
53
+ }
54
+ ),
55
+ /* @__PURE__ */ jsxRuntime.jsx(chunkMJROUIDZ_cjs.Label, { htmlFor: "scatter-point-size", children: "Point size" }),
56
+ /* @__PURE__ */ jsxRuntime.jsx(
57
+ chunkMJROUIDZ_cjs.Input,
58
+ {
59
+ id: "scatter-point-size",
60
+ type: "number",
61
+ min: 1,
62
+ max: 12,
63
+ step: 0.5,
64
+ value: settings.pointSize ?? 3,
65
+ onChange: (event) => {
66
+ const value = Number(event.target.value);
67
+ if (value >= 1 && value <= 12)
68
+ onSettingsChange({ ...settings, pointSize: value });
69
+ }
70
+ }
71
+ ),
72
+ /* @__PURE__ */ jsxRuntime.jsx(chunkMJROUIDZ_cjs.Label, { htmlFor: "scatter-opacity", children: "Opacity" }),
73
+ /* @__PURE__ */ jsxRuntime.jsx(
74
+ chunkMJROUIDZ_cjs.Input,
75
+ {
76
+ id: "scatter-opacity",
77
+ type: "number",
78
+ min: 0.1,
79
+ max: 1,
80
+ step: 0.1,
81
+ value: settings.pointOpacity ?? 0.7,
82
+ onChange: (event) => {
83
+ const value = Number(event.target.value);
84
+ if (value >= 0.1 && value <= 1)
85
+ onSettingsChange({ ...settings, pointOpacity: value });
86
+ }
87
+ }
88
+ )
89
+ ] }),
90
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Smaller, transparent points reveal overlap. Drag a rectangle to filter the other views." })
91
+ ] });
92
+ }
93
+
94
+ // src/components/charts/ScatterPlot/planScatterPoints.ts
95
+ function planScatterPoints({
96
+ settings,
97
+ style,
98
+ ids,
99
+ xData,
100
+ yData,
101
+ colorData,
102
+ xScale,
103
+ yScale,
104
+ getColor
105
+ }) {
106
+ const xFilter = chunkUUJMUPB3_cjs.getRangeFilterForField(settings.filters, settings.xField);
107
+ const yFilter = chunkUUJMUPB3_cjs.getRangeFilterForField(settings.filters, settings.yField);
108
+ const colorFilters = settings.filters.filter(
109
+ (filter) => filter.field === settings.colorField
110
+ );
111
+ const points = [];
112
+ for (const sourceId of ids) {
113
+ const rawX = xData[sourceId];
114
+ const rawY = yData[sourceId];
115
+ const xValue = chunk5B23JVH3_cjs.finiteNumber(rawX) ?? NaN;
116
+ const yValue = chunk5B23JVH3_cjs.finiteNumber(rawY) ?? NaN;
117
+ if (!Number.isFinite(xValue) || !Number.isFinite(yValue)) {
118
+ continue;
119
+ }
120
+ const x = xScale(xValue);
121
+ const y = yScale(yValue);
122
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
123
+ continue;
124
+ }
125
+ const passesOwnFilter = (!xFilter || chunkEU5EFHO5_cjs.applyFilter(xValue, xFilter)) && (!yFilter || chunkEU5EFHO5_cjs.applyFilter(yValue, yFilter)) && colorFilters.every((filter) => chunkEU5EFHO5_cjs.applyFilter(colorData[sourceId], filter));
126
+ const mappedColor = getColor(colorData[sourceId]);
127
+ points.push({
128
+ id: `${settings.id}:point:${sourceId}`,
129
+ sourceId,
130
+ xValue,
131
+ yValue,
132
+ colorValue: colorData[sourceId],
133
+ mappedColor,
134
+ x,
135
+ y,
136
+ color: passesOwnFilter ? mappedColor : "rgb(156 163 175)",
137
+ opacity: passesOwnFilter ? style.opacity.value : style.dimmedOpacity.value,
138
+ radius: style.radius.value,
139
+ passesOwnFilter
140
+ });
141
+ }
142
+ return points;
143
+ }
144
+
145
+ // src/components/charts/ScatterPlot/scatterPlan.ts
146
+ function numeric(value) {
147
+ return chunk5B23JVH3_cjs.finiteNumber(value) ?? NaN;
148
+ }
149
+ function bounds(ids, data) {
150
+ let min = Infinity;
151
+ let max = -Infinity;
152
+ for (const id of ids) {
153
+ const value = numeric(data[id]);
154
+ if (!Number.isFinite(value)) {
155
+ continue;
156
+ }
157
+ min = Math.min(min, value);
158
+ max = Math.max(max, value);
159
+ }
160
+ return min === Infinity ? [0, 1] : [min, max];
161
+ }
162
+ function fieldLabel(field, settings) {
163
+ return field === "__ID" ? "Row sequence" : chunk5B23JVH3_cjs.getFieldLabel(field, settings[field]);
164
+ }
165
+ function display(field, value, settings) {
166
+ return chunk5B23JVH3_cjs.hasFieldDisplayFormat(settings[field]) ? chunk5B23JVH3_cjs.formatFieldValue(field, value, settings[field]) : String(value);
167
+ }
168
+ function planScatter(settings, snapshot, width, height) {
169
+ const xLabel = settings.xAxisLabel || fieldLabel(settings.xField, snapshot.fieldSettings);
170
+ const yLabel = settings.yAxisLabel || fieldLabel(settings.yField, snapshot.fieldSettings);
171
+ const [xMin, xMax] = bounds(snapshot.allIds, snapshot.xData);
172
+ const [yMin, yMax] = bounds(snapshot.allIds, snapshot.yData);
173
+ const xBuffer = (xMax - xMin) * 0.1;
174
+ const yBuffer = (yMax - yMin) * 0.1;
175
+ const yDomain = [yMin - yBuffer, yMax + yBuffer];
176
+ const { margin, policy: marginPolicy } = chunkUUJMUPB3_cjs.planChartMargin({
177
+ margin: settings.margin,
178
+ width,
179
+ yDomain,
180
+ hasXLabel: Boolean(xLabel),
181
+ hasYLabel: Boolean(yLabel)
182
+ });
183
+ const plotWidth = width - margin.left - margin.right;
184
+ const plotHeight = height - margin.top - margin.bottom;
185
+ const xDomain = [
186
+ settings.xAxis.scaleType === "symlog" ? xMin : xMin - xBuffer,
187
+ xMax + xBuffer
188
+ ];
189
+ const actualYDomain = settings.yAxis.scaleType === "symlog" ? [yMin, yDomain[1]] : yDomain;
190
+ const xScale = chunkUUJMUPB3_cjs.numericScale(settings.xAxis).domain(xDomain).range([0, plotWidth]);
191
+ const yScale = chunkUUJMUPB3_cjs.numericScale(settings.yAxis).domain(actualYDomain).range([plotHeight, 0]);
192
+ const xType = settings.xAxis.scaleType === "symlog" ? "symlog" : "linear";
193
+ const yType = settings.yAxis.scaleType === "symlog" ? "symlog" : "linear";
194
+ const facetSet = new Set(snapshot.facetIds);
195
+ const ids = snapshot.facetIds?.length ? snapshot.chartIds.filter((id) => facetSet.has(id)) : snapshot.chartIds;
196
+ const resolveColor = snapshot.colorScale ? chunkB7OQUBH4_cjs.makeColorScale(snapshot.colorScale) : null;
197
+ const getColor = (value) => {
198
+ if (!settings.colorScaleId) {
199
+ return "#3479a8";
200
+ }
201
+ if (!resolveColor) {
202
+ return "#000000";
203
+ }
204
+ try {
205
+ return resolveColor(value);
206
+ } catch {
207
+ return "#000000";
208
+ }
209
+ };
210
+ const filteredSet = new Set(snapshot.filteredIds);
211
+ const pointStyle = {
212
+ radius: {
213
+ value: settings.pointSize ?? 3,
214
+ source: settings.pointSize == null ? "scatter-default" : "chart-setting"
215
+ },
216
+ opacity: {
217
+ value: settings.pointOpacity ?? 0.7,
218
+ source: settings.pointOpacity == null ? "scatter-default" : "chart-setting"
219
+ },
220
+ dimmedOpacity: { value: 0.15, source: "own-filter-rule" }
221
+ };
222
+ const points = planScatterPoints({
223
+ settings,
224
+ style: pointStyle,
225
+ ids,
226
+ xData: snapshot.xData,
227
+ yData: snapshot.yData,
228
+ colorData: snapshot.colorData,
229
+ xScale,
230
+ yScale,
231
+ getColor
232
+ }).map((point) => ({
233
+ ...point,
234
+ passesAllFilters: filteredSet.has(point.sourceId)
235
+ }));
236
+ const included = new Set(points.map((point) => point.sourceId));
237
+ const format = (field) => (value) => chunk5B23JVH3_cjs.formatFieldValue(field, value, snapshot.fieldSettings[field]);
238
+ const labelSource = (local) => local ? "chart-setting" : "field-label";
239
+ const axes = chunkUUJMUPB3_cjs.planAxes({
240
+ plotWidth,
241
+ plotHeight,
242
+ margin,
243
+ marginPolicy,
244
+ x: {
245
+ scale: xScale,
246
+ scaleType: xType,
247
+ field: settings.xField,
248
+ fieldLabel: xLabel,
249
+ density: settings.xGridLines,
250
+ grid: settings.xAxis.grid,
251
+ format: format(settings.xField),
252
+ label: [xLabel, xType === "symlog" && "symlog"].filter(Boolean).join(" \xB7 "),
253
+ labelSource: labelSource(settings.xAxisLabel),
254
+ domainSource: {
255
+ population: "all source rows",
256
+ rows: snapshot.allIds.length,
257
+ bounds: [xMin, xMax],
258
+ padding: xType === "symlog" ? "10% above" : "10% on each side"
259
+ }
260
+ },
261
+ y: {
262
+ scale: yScale,
263
+ scaleType: yType,
264
+ field: settings.yField,
265
+ fieldLabel: yLabel,
266
+ density: settings.yGridLines,
267
+ grid: settings.yAxis.grid,
268
+ format: format(settings.yField),
269
+ label: [yLabel, yType === "symlog" && "symlog"].filter(Boolean).join(" \xB7 "),
270
+ labelSource: labelSource(settings.yAxisLabel),
271
+ rule: false,
272
+ domainSource: {
273
+ population: "all source rows",
274
+ rows: snapshot.allIds.length,
275
+ bounds: [yMin, yMax],
276
+ padding: yType === "symlog" ? "10% above" : "10% on each side"
277
+ }
278
+ }
279
+ });
280
+ const xFilter = chunkUUJMUPB3_cjs.getRangeFilterForField(settings.filters, settings.xField);
281
+ const yFilter = chunkUUJMUPB3_cjs.getRangeFilterForField(settings.filters, settings.yField);
282
+ const round = (value) => Math.round(value * 1e4) / 1e4;
283
+ const brushExtent = xFilter?.min !== void 0 && xFilter.max !== void 0 && yFilter?.min !== void 0 && yFilter.max !== void 0 ? [
284
+ [round(xScale(xFilter.min)), round(yScale(yFilter.max))],
285
+ [round(xScale(xFilter.max)), round(yScale(yFilter.min))]
286
+ ] : null;
287
+ const title = chunk5B23JVH3_cjs.getChartTitle(
288
+ settings,
289
+ (field) => fieldLabel(field, snapshot.fieldSettings)
290
+ );
291
+ const calculatedBadges = [];
292
+ if (snapshot.calculatedFields?.includes(settings.xField)) {
293
+ calculatedBadges.push({
294
+ id: "calculation:x",
295
+ field: settings.xField,
296
+ x: margin.left + plotWidth / 2 + Math.min(90, xLabel.length * 3.2) + 8,
297
+ y: margin.top + plotHeight + Math.max(30, margin.bottom - 10) - 12,
298
+ rotation: 0
299
+ });
300
+ }
301
+ if (snapshot.calculatedFields?.includes(settings.yField)) {
302
+ calculatedBadges.push({
303
+ id: "calculation:y",
304
+ field: settings.yField,
305
+ x: 0,
306
+ y: margin.top + plotHeight / 2 - Math.min(90, yLabel.length * 3.2) - 30,
307
+ rotation: -90
308
+ });
309
+ }
310
+ let legend;
311
+ if (settings.colorField && settings.colorScaleId && snapshot.colorScale) {
312
+ const scale = snapshot.colorScale;
313
+ if (scale.type === "categorical") {
314
+ const values = /* @__PURE__ */ new Map();
315
+ for (const id of snapshot.allIds) {
316
+ const value = chunk5B23JVH3_cjs.categoryValue(snapshot.colorData[id]);
317
+ values.set(chunk5B23JVH3_cjs.categoryKey(value), value);
318
+ }
319
+ const counts = /* @__PURE__ */ new Map();
320
+ for (const id of snapshot.chartIds) {
321
+ const key = chunk5B23JVH3_cjs.categoryKey(snapshot.colorData[id]);
322
+ counts.set(key, (counts.get(key) ?? 0) + 1);
323
+ }
324
+ const selected = settings.filters.filter(
325
+ (filter) => filter.type === "value" && filter.field === settings.colorField
326
+ ).flatMap((filter) => filter.values);
327
+ legend = {
328
+ field: settings.colorField,
329
+ scaleId: scale.id,
330
+ type: "categorical",
331
+ palette: [...scale.palette],
332
+ items: [...values].map(([id, value]) => ({
333
+ id,
334
+ value,
335
+ label: chunk5B23JVH3_cjs.hasFieldDisplayFormat(
336
+ snapshot.fieldSettings[settings.colorField]
337
+ ) ? chunk5B23JVH3_cjs.formatFieldValue(
338
+ settings.colorField,
339
+ value,
340
+ snapshot.fieldSettings[settings.colorField]
341
+ ) : chunk5B23JVH3_cjs.categoryLabel(value),
342
+ count: counts.get(id) ?? 0,
343
+ color: getColor(value),
344
+ selected: chunk5B23JVH3_cjs.categoryIncludes(selected, value)
345
+ }))
346
+ };
347
+ } else {
348
+ legend = {
349
+ field: settings.colorField,
350
+ scaleId: scale.id,
351
+ type: "numerical",
352
+ palette: scale.palette,
353
+ domain: [scale.min, scale.max],
354
+ items: []
355
+ };
356
+ }
357
+ }
358
+ return {
359
+ revision: snapshot.revision,
360
+ width,
361
+ height,
362
+ margin,
363
+ plotWidth,
364
+ plotHeight,
365
+ clipWidth: Math.max(0, plotWidth),
366
+ clipHeight: Math.max(0, plotHeight),
367
+ pixelRatio: snapshot.pixelRatio ?? 1,
368
+ calculatedBadges,
369
+ xScale: { type: xType, domain: xDomain, range: [0, plotWidth] },
370
+ yScale: { type: yType, domain: actualYDomain, range: [plotHeight, 0] },
371
+ title,
372
+ description: [
373
+ `Interactive scatter chart.`,
374
+ settings.xAxisLabel && `Horizontal axis: ${settings.xAxisLabel}.`,
375
+ settings.yAxisLabel && `Vertical axis: ${settings.yAxisLabel}.`
376
+ ].filter(Boolean).join(" "),
377
+ axes,
378
+ domainInputs: {
379
+ x: [xMin, xMax],
380
+ y: [yMin, yMax],
381
+ buffer: 0.1,
382
+ population: "all"
383
+ },
384
+ legend,
385
+ brushExtent,
386
+ pointStyle,
387
+ points,
388
+ exclusions: ids.filter((id) => !included.has(id)).map((sourceId) => ({
389
+ sourceId,
390
+ reason: !Number.isFinite(numeric(snapshot.xData[sourceId])) ? "invalid-x" : !Number.isFinite(numeric(snapshot.yData[sourceId])) ? "invalid-y" : "nonfinite-position"
391
+ })),
392
+ populations: {
393
+ all: snapshot.allIds.length,
394
+ chart: snapshot.chartIds.length,
395
+ filtered: snapshot.filteredIds.length,
396
+ facet: ids.length
397
+ },
398
+ rowSets: {
399
+ all: snapshot.allIds,
400
+ chart: snapshot.chartIds,
401
+ filtered: snapshot.filteredIds,
402
+ facet: ids
403
+ },
404
+ xField: settings.xField,
405
+ yField: settings.yField,
406
+ xDisplay: xLabel,
407
+ yDisplay: yLabel,
408
+ fieldSettings: snapshot.fieldSettings
409
+ };
410
+ }
411
+ function scatterPointReadout(plan, point) {
412
+ return {
413
+ xText: display(plan.xField, point.xValue, plan.fieldSettings),
414
+ yText: display(plan.yField, point.yValue, plan.fieldSettings)
415
+ };
416
+ }
417
+ function scatterHoverReadout(plan, snapshot, settings, point) {
418
+ const value = (field, item) => chunk5B23JVH3_cjs.hasFieldDisplayFormat(snapshot.fieldSettings[field]) ? chunk5B23JVH3_cjs.formatFieldValue(field, item, snapshot.fieldSettings[field]) : chunk5B23JVH3_cjs.categoryLabel(item);
419
+ return {
420
+ ...scatterPointReadout(plan, point),
421
+ colorText: settings.colorField ? value(settings.colorField, point.colorValue) : void 0,
422
+ facetRowText: settings.facet.enabled ? value(
423
+ settings.facet.rowVariable,
424
+ snapshot.facetRowData?.[point.sourceId]
425
+ ) : void 0,
426
+ facetColumnText: settings.facet.enabled && settings.facet.type === "grid" ? value(
427
+ settings.facet.columnVariable,
428
+ snapshot.facetColumnData?.[point.sourceId]
429
+ ) : void 0
430
+ };
431
+ }
432
+ function brushFilters(plan, extent) {
433
+ const xScale = chunkUUJMUPB3_cjs.numericScale({ scaleType: plan.xScale.type }).domain(plan.xScale.domain).range(plan.xScale.range);
434
+ const yScale = chunkUUJMUPB3_cjs.numericScale({ scaleType: plan.yScale.type }).domain(plan.yScale.domain).range(plan.yScale.range);
435
+ return {
436
+ x: [
437
+ Math.min(xScale.invert(extent[0][0]), xScale.invert(extent[1][0])),
438
+ Math.max(xScale.invert(extent[0][0]), xScale.invert(extent[1][0]))
439
+ ],
440
+ y: [
441
+ Math.min(yScale.invert(extent[0][1]), yScale.invert(extent[1][1])),
442
+ Math.max(yScale.invert(extent[0][1]), yScale.invert(extent[1][1]))
443
+ ]
444
+ };
445
+ }
446
+ function planScatterOverlay(plan, extent, hoveredId) {
447
+ const brush = [];
448
+ if (extent) {
449
+ const [[x0, y0], [x1, y1]] = extent;
450
+ brush.push({
451
+ kind: "rect",
452
+ id: "brush",
453
+ x: x0,
454
+ y: y0,
455
+ width: Math.max(0, x1 - x0),
456
+ height: Math.max(0, y1 - y0),
457
+ fill: "var(--primary)",
458
+ fillOpacity: 0.09,
459
+ stroke: "var(--primary)",
460
+ strokeWidth: 1.25,
461
+ cursor: "move"
462
+ });
463
+ for (const [i, x] of [x0, x1].entries()) {
464
+ brush.push({
465
+ kind: "rect",
466
+ id: `brush:x:${i}`,
467
+ x: x - 2,
468
+ y: (y0 + y1) / 2 - 8,
469
+ width: 4,
470
+ height: 16,
471
+ rx: 2,
472
+ fill: "var(--primary)",
473
+ cursor: "ew-resize"
474
+ });
475
+ }
476
+ for (const [i, y] of [y0, y1].entries()) {
477
+ brush.push({
478
+ kind: "rect",
479
+ id: `brush:y:${i}`,
480
+ x: (x0 + x1) / 2 - 8,
481
+ y: y - 2,
482
+ width: 16,
483
+ height: 4,
484
+ rx: 2,
485
+ fill: "var(--primary)",
486
+ cursor: "ns-resize"
487
+ });
488
+ }
489
+ }
490
+ const point = plan.points.find((item) => item.id === hoveredId);
491
+ const readout = [];
492
+ let readoutLabel;
493
+ if (point) {
494
+ const { x, y, color } = point;
495
+ const { xText, yText } = scatterPointReadout(plan, point);
496
+ readoutLabel = `Source row ${point.sourceId}; ${plan.xDisplay}: ${xText}; ${plan.yDisplay}: ${yText}`;
497
+ const xWidth = xText.length * 6 + 12;
498
+ const yWidth = yText.length * 6 + 12;
499
+ const labelX = Math.max(
500
+ xWidth / 2,
501
+ Math.min(plan.plotWidth - xWidth / 2, x)
502
+ );
503
+ readout.push(
504
+ {
505
+ kind: "line",
506
+ id: "readout:x-rule",
507
+ x1: x,
508
+ x2: x,
509
+ y1: y,
510
+ y2: plan.plotHeight + 3,
511
+ stroke: color,
512
+ strokeOpacity: 0.4,
513
+ strokeWidth: 1,
514
+ strokeDasharray: "2 3"
515
+ },
516
+ {
517
+ kind: "line",
518
+ id: "readout:y-rule",
519
+ x1: x,
520
+ x2: 0,
521
+ y1: y,
522
+ y2: y,
523
+ stroke: color,
524
+ strokeOpacity: 0.4,
525
+ strokeWidth: 1,
526
+ strokeDasharray: "2 3"
527
+ },
528
+ {
529
+ kind: "circle",
530
+ id: "readout:ring",
531
+ cx: x,
532
+ cy: y,
533
+ r: point.radius + 5,
534
+ fill: "none",
535
+ stroke: "var(--primary)",
536
+ strokeWidth: 2.5
537
+ },
538
+ { kind: "circle", id: "readout:center", cx: x, cy: y, r: 2, fill: color },
539
+ {
540
+ kind: "rect",
541
+ id: "readout:x-bg",
542
+ x: labelX - xWidth / 2,
543
+ y: plan.plotHeight + 5,
544
+ width: xWidth,
545
+ height: 20,
546
+ fill: "var(--card)"
547
+ },
548
+ {
549
+ kind: "rect",
550
+ id: "readout:y-bg",
551
+ x: -yWidth - 3,
552
+ y: y - 10,
553
+ width: yWidth,
554
+ height: 20,
555
+ fill: "var(--card)"
556
+ },
557
+ {
558
+ kind: "text",
559
+ id: "readout:x-text",
560
+ x: labelX,
561
+ y: plan.plotHeight + 18,
562
+ text: xText,
563
+ fill: color,
564
+ fontSize: 10,
565
+ fontWeight: 600,
566
+ textAnchor: "middle"
567
+ },
568
+ {
569
+ kind: "text",
570
+ id: "readout:y-text",
571
+ x: -8,
572
+ y,
573
+ dy: ".32em",
574
+ text: yText,
575
+ fill: color,
576
+ fontSize: 10,
577
+ fontWeight: 600,
578
+ textAnchor: "end"
579
+ }
580
+ );
581
+ }
582
+ return {
583
+ brush,
584
+ readout,
585
+ readoutLabel
586
+ };
587
+ }
588
+ function Primitive({
589
+ item,
590
+ selected
591
+ }) {
592
+ if (item.kind === "line") {
593
+ const { kind: kind2, id: id2, hitStrokeWidth, ...props2 } = item;
594
+ const hasHitTarget = hitStrokeWidth !== void 0;
595
+ const line = /* @__PURE__ */ jsxRuntime.jsx(
596
+ "line",
597
+ {
598
+ "data-plan-id": id2,
599
+ ...props2,
600
+ style: selected ? { stroke: "var(--primary)" } : void 0,
601
+ strokeOpacity: selected ? 1 : item.strokeOpacity,
602
+ strokeWidth: selected ? 2.5 : item.strokeWidth,
603
+ pointerEvents: hasHitTarget ? "none" : void 0
604
+ }
605
+ );
606
+ return hasHitTarget ? /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
607
+ line,
608
+ /* @__PURE__ */ jsxRuntime.jsx(
609
+ "line",
610
+ {
611
+ "data-plan-id": id2,
612
+ x1: item.x1,
613
+ y1: item.y1,
614
+ x2: item.x2,
615
+ y2: item.y2,
616
+ stroke: "transparent",
617
+ strokeWidth: hitStrokeWidth,
618
+ pointerEvents: "stroke"
619
+ }
620
+ )
621
+ ] }) : line;
622
+ }
623
+ if (item.kind === "rect") {
624
+ const { kind: kind2, id: id2, cursor, ...props2 } = item;
625
+ return /* @__PURE__ */ jsxRuntime.jsx("rect", { "data-plan-id": id2, ...props2, style: { cursor } });
626
+ }
627
+ if (item.kind === "circle") {
628
+ const { kind: kind2, id: id2, ...props2 } = item;
629
+ return /* @__PURE__ */ jsxRuntime.jsx("circle", { "data-plan-id": id2, ...props2 });
630
+ }
631
+ const { kind, id, title, text, ...props } = item;
632
+ return /* @__PURE__ */ jsxRuntime.jsx(
633
+ "text",
634
+ {
635
+ "data-plan-id": id,
636
+ ...props,
637
+ "aria-label": title,
638
+ fill: selected ? "var(--primary)" : item.fill,
639
+ textDecoration: selected ? "underline" : void 0,
640
+ children: text
641
+ }
642
+ );
643
+ }
644
+ function Primitives({
645
+ items,
646
+ selectedId
647
+ }) {
648
+ return items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(Primitive, { item, selected: item.id === selectedId }, item.id));
649
+ }
650
+ function ScatterSvg({
651
+ plan,
652
+ hoveredId,
653
+ onBrushChange,
654
+ onInspectPoint,
655
+ onInspectGuide,
656
+ onInspectOverlay,
657
+ selectedId
658
+ }) {
659
+ const svgRef = react.useRef(null);
660
+ const altAtDown = react.useRef(false);
661
+ const [hoveredGuideId, setHoveredGuideId] = react.useState(null);
662
+ const [altHover, setAltHover] = react.useState(false);
663
+ const chartId = react.useId();
664
+ const brush = chunkUUJMUPB3_cjs.useBrush({
665
+ svgRef,
666
+ marginLeft: plan.margin.left,
667
+ marginTop: plan.margin.top,
668
+ innerWidth: plan.plotWidth,
669
+ innerHeight: plan.plotHeight,
670
+ mode: "2d",
671
+ onBrushChange,
672
+ onPlotClick: (_, event) => event.altKey || altAtDown.current,
673
+ defaultExtent: plan.brushExtent
674
+ });
675
+ const overlay = planScatterOverlay(plan, brush.extent ?? null, hoveredId);
676
+ if (plan.width < 1 || plan.height < 1) {
677
+ return null;
678
+ }
679
+ return /* @__PURE__ */ jsxRuntime.jsxs(
680
+ "svg",
681
+ {
682
+ ref: svgRef,
683
+ width: plan.width,
684
+ height: plan.height,
685
+ role: "group",
686
+ "aria-label": plan.title,
687
+ "aria-describedby": `${chartId}-description`,
688
+ className: "absolute select-none",
689
+ style: {
690
+ cursor: altHover && (hoveredId || hoveredGuideId) ? "pointer" : brush.getCursor(),
691
+ touchAction: "none"
692
+ },
693
+ tabIndex: 0,
694
+ onKeyDown: (event) => {
695
+ const id = event.target.closest("[data-plan-id]")?.getAttribute("data-plan-id");
696
+ if (event.altKey && event.key === "Enter" && id && chunkUUJMUPB3_cjs.findAxisGuide(plan.axes, id)) {
697
+ event.preventDefault();
698
+ event.stopPropagation();
699
+ onInspectGuide(id);
700
+ return;
701
+ }
702
+ if (event.key === "Escape") {
703
+ event.stopPropagation();
704
+ brush.clear();
705
+ }
706
+ },
707
+ onPointerDownCapture: (event) => {
708
+ altAtDown.current = event.altKey;
709
+ brush.handlePointerDown(event);
710
+ },
711
+ onPointerMoveCapture: (event) => {
712
+ brush.handlePointerMove(event);
713
+ setAltHover(event.altKey);
714
+ const id = event.target.closest("[data-plan-id]")?.getAttribute("data-plan-id");
715
+ setHoveredGuideId(
716
+ event.altKey && id && chunkUUJMUPB3_cjs.findAxisGuide(plan.axes, id) ? id : null
717
+ );
718
+ },
719
+ onPointerLeave: () => {
720
+ setHoveredGuideId(null);
721
+ setAltHover(false);
722
+ },
723
+ onPointerUpCapture: brush.handlePointerUp,
724
+ onPointerCancel: brush.cancel,
725
+ onLostPointerCapture: brush.cancel,
726
+ onClick: (event) => {
727
+ if (!event.altKey || brush.wasDrag.current) return;
728
+ const rect = event.currentTarget.getBoundingClientRect();
729
+ const x = event.clientX - rect.left - plan.margin.left;
730
+ const y = event.clientY - rect.top - plan.margin.top;
731
+ if (x >= 0 && x <= plan.plotWidth && y >= 0 && y <= plan.plotHeight && onInspectPoint(x, y))
732
+ return;
733
+ const id = event.target.closest("[data-plan-id]")?.getAttribute("data-plan-id");
734
+ if (id && chunkUUJMUPB3_cjs.findAxisGuide(plan.axes, id)) {
735
+ onInspectGuide(id);
736
+ return;
737
+ }
738
+ if (id && overlay.brush.some((item) => item.id === id)) {
739
+ onInspectOverlay(id);
740
+ }
741
+ },
742
+ children: [
743
+ /* @__PURE__ */ jsxRuntime.jsx("desc", { id: `${chartId}-description`, children: plan.description }),
744
+ /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsx("clipPath", { id: `${chartId}-plot`, children: /* @__PURE__ */ jsxRuntime.jsx("rect", { width: plan.clipWidth, height: plan.clipHeight }) }) }),
745
+ /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `translate(${plan.margin.left},${plan.margin.top})`, children: [
746
+ /* @__PURE__ */ jsxRuntime.jsx("g", { clipPath: `url(#${chartId}-plot)`, children: /* @__PURE__ */ jsxRuntime.jsx(
747
+ chunkUUJMUPB3_cjs.PlannedGrid,
748
+ {
749
+ plan: plan.axes,
750
+ interactive: true,
751
+ activeId: hoveredGuideId ?? selectedId
752
+ }
753
+ ) }),
754
+ /* @__PURE__ */ jsxRuntime.jsx("g", { className: "eda-brush", clipPath: `url(#${chartId}-plot)`, children: /* @__PURE__ */ jsxRuntime.jsx(Primitives, { items: overlay.brush }) }),
755
+ /* @__PURE__ */ jsxRuntime.jsx(
756
+ chunkUUJMUPB3_cjs.PlannedAxes,
757
+ {
758
+ plan: plan.axes,
759
+ interactive: true,
760
+ activeId: hoveredGuideId ?? selectedId
761
+ }
762
+ ),
763
+ /* @__PURE__ */ jsxRuntime.jsx(
764
+ "g",
765
+ {
766
+ className: "eda-axis-readout",
767
+ role: overlay.readoutLabel ? "img" : void 0,
768
+ "aria-label": overlay.readoutLabel,
769
+ pointerEvents: "none",
770
+ children: /* @__PURE__ */ jsxRuntime.jsx(Primitives, { items: overlay.readout })
771
+ }
772
+ )
773
+ ] })
774
+ ]
775
+ }
776
+ );
777
+ }
778
+
779
+ // src/components/charts/ScatterPlot/scatterTrace.ts
780
+ function sourceFields(trace) {
781
+ const calculated = new Set(trace.dependencies.map((item) => item.field));
782
+ return [
783
+ .../* @__PURE__ */ new Set([
784
+ ...trace.inputs.filter((item) => !calculated.has(item.field)).map((item) => item.field),
785
+ ...trace.dependencies.flatMap(sourceFields)
786
+ ])
787
+ ];
788
+ }
789
+ function resolveScatterTrace(selection, plan, snapshot, settings, rawRows, preparedRows, profiles, manager) {
790
+ if (selection.kind === "guide")
791
+ return chunkTRAIXZ6M_cjs.resolveGuideTrace(plan.axes, selection.id, plan.revision);
792
+ if (selection.kind === "badge") {
793
+ const badge = plan.calculatedBadges.find(
794
+ (item) => item.id === selection.id
795
+ );
796
+ if (!badge) return void 0;
797
+ return { kind: "badge", id: badge.id, revision: plan.revision, badge };
798
+ }
799
+ if (selection.kind === "overlay") {
800
+ const primitive = planScatterOverlay(
801
+ plan,
802
+ plan.brushExtent,
803
+ null
804
+ ).brush.find((item) => item.id === selection.id);
805
+ if (!primitive) return void 0;
806
+ return {
807
+ kind: "overlay",
808
+ id: selection.id,
809
+ revision: plan.revision,
810
+ primitive,
811
+ extent: plan.brushExtent,
812
+ filters: settings.filters.filter(
813
+ (filter) => filter.type === "range" && (filter.field === settings.xField || filter.field === settings.yField)
814
+ )
815
+ };
816
+ }
817
+ const conversionFor = (field, raw) => {
818
+ const fieldSetting = snapshot.fieldSettings[field] ?? {};
819
+ const applied = Boolean(
820
+ fieldSetting.type || fieldSetting.datePreset || fieldSetting.nullTokens?.length
821
+ );
822
+ const type = fieldSetting.type ?? profiles.find((item) => item.name === field)?.dataType;
823
+ return applied && type ? { type, error: chunk5B23JVH3_cjs.convertFieldValue(raw, type, fieldSetting).error } : void 0;
824
+ };
825
+ const fieldTrace = (field, value, sourceId) => {
826
+ const raw = rawRows[sourceId]?.[field];
827
+ const calculation = manager.traceRow(field, sourceId);
828
+ const conversion = calculation ? void 0 : conversionFor(field, raw);
829
+ const sources = calculation ? sourceFields(calculation).map((name) => ({
830
+ field: name,
831
+ raw: rawRows[sourceId]?.[name],
832
+ prepared: preparedRows[sourceId]?.[name],
833
+ conversion: conversionFor(name, rawRows[sourceId]?.[name])
834
+ })) : [];
835
+ return { field, raw, prepared: value, conversion, calculation, sources };
836
+ };
837
+ if (selection.kind === "point") {
838
+ const point = plan.points.find((item) => item.id === selection.id);
839
+ if (!point) return void 0;
840
+ return {
841
+ kind: "point",
842
+ id: point.id,
843
+ revision: plan.revision,
844
+ plan,
845
+ sourceId: point.sourceId,
846
+ x: {
847
+ ...fieldTrace(settings.xField, point.xValue, point.sourceId),
848
+ pixel: point.x
849
+ },
850
+ y: {
851
+ ...fieldTrace(settings.yField, point.yValue, point.sourceId),
852
+ pixel: point.y
853
+ },
854
+ color: settings.colorField ? {
855
+ ...fieldTrace(
856
+ settings.colorField,
857
+ point.colorValue,
858
+ point.sourceId
859
+ ),
860
+ mapped: point.mappedColor,
861
+ rendered: point.color
862
+ } : void 0,
863
+ facet: settings.facet.enabled && snapshot.facetIds?.length ? {
864
+ type: settings.facet.type,
865
+ row: fieldTrace(
866
+ settings.facet.rowVariable,
867
+ snapshot.facetRowData?.[point.sourceId],
868
+ point.sourceId
869
+ ),
870
+ column: settings.facet.type === "grid" ? fieldTrace(
871
+ settings.facet.columnVariable,
872
+ snapshot.facetColumnData?.[point.sourceId],
873
+ point.sourceId
874
+ ) : void 0,
875
+ sourceRows: snapshot.facetIds.length,
876
+ chartRows: plan.populations.facet
877
+ } : void 0,
878
+ radius: point.radius,
879
+ opacity: point.opacity,
880
+ passesOwnFilter: point.passesOwnFilter,
881
+ passesAllFilters: point.passesAllFilters,
882
+ hover: scatterHoverReadout(plan, snapshot, settings, point)
883
+ };
884
+ }
885
+ if (selection.kind === "excluded") {
886
+ const sourceId = Number(selection.id);
887
+ const exclusion = plan.exclusions.find(
888
+ (item) => item.sourceId === sourceId
889
+ );
890
+ if (!exclusion) return void 0;
891
+ return {
892
+ kind: "excluded",
893
+ id: selection.id,
894
+ revision: plan.revision,
895
+ sourceId,
896
+ reason: exclusion.reason,
897
+ x: fieldTrace(settings.xField, snapshot.xData[sourceId], sourceId),
898
+ y: fieldTrace(settings.yField, snapshot.yData[sourceId], sourceId)
899
+ };
900
+ }
901
+ return void 0;
902
+ }
903
+ function scatterTraceTargets(plan) {
904
+ return [
905
+ ...chunkTRAIXZ6M_cjs.guideTargets(plan.axes),
906
+ ...planScatterOverlay(plan, plan.brushExtent, null).brush.map(({ id }) => ({
907
+ kind: "overlay",
908
+ id,
909
+ label: id
910
+ })),
911
+ ...plan.calculatedBadges.map((badge) => ({
912
+ kind: "badge",
913
+ id: badge.id,
914
+ label: `Calculated: ${badge.field}`
915
+ }))
916
+ ];
917
+ }
918
+ function findScatterTraceRow(plan, id) {
919
+ const point = plan.points.find((item) => item.sourceId === id);
920
+ if (point) return { kind: "point", id: point.id };
921
+ return plan.exclusions.some((item) => item.sourceId === id) ? { kind: "excluded", id: String(id) } : void 0;
922
+ }
923
+ function ScatterPlot({
924
+ settings,
925
+ width,
926
+ height,
927
+ facetIds
928
+ }) {
929
+ const [hoveredId, setHoveredId] = react.useState(null);
930
+ const trace = chunkTRAIXZ6M_cjs.useChartTrace();
931
+ const traceApi = chunkTRAIXZ6M_cjs.useChartTraceApi();
932
+ const owner = react.useId();
933
+ const canvasRef = react.useRef(null);
934
+ const data = chunk5B23JVH3_cjs.useDataLayer((state) => state.data);
935
+ const rawData = chunk5B23JVH3_cjs.useDataLayer((state) => state.rawData);
936
+ const profiles = chunk5B23JVH3_cjs.useDataLayer((state) => state.fieldProfiles);
937
+ const manager = chunk5B23JVH3_cjs.useDataLayer((state) => state.calculationManager);
938
+ const calculations = chunk5B23JVH3_cjs.useDataLayer((state) => state.calculations);
939
+ const nonce = chunk5B23JVH3_cjs.useDataLayer((state) => state.nonce);
940
+ const chartItems = chunk5B23JVH3_cjs.useDataLayer((state) => state.liveItems[settings.id]);
941
+ const crossfilter = chunk5B23JVH3_cjs.useDataLayer((state) => state.crossfilterWrapper);
942
+ const getColumnData = chunk5B23JVH3_cjs.useDataLayer((state) => state.getColumnData);
943
+ const fieldSettings = chunk5B23JVH3_cjs.useDataLayer((state) => state.fieldSettings);
944
+ const colorScale = chunk5B23JVH3_cjs.useDataLayer(
945
+ (state) => state.colorScales.find((item) => item.id === settings.colorScaleId)
946
+ );
947
+ const updateChart = chunk5B23JVH3_cjs.useDataLayer((state) => state.updateChart);
948
+ const allIds = react.useMemo(() => data.map((row) => row.__ID), [data]);
949
+ const snapshot = react.useMemo(() => {
950
+ const chartIds = chartItems?.items.filter((item) => item.value > 0).map((item) => item.key) ?? [];
951
+ const column = (field) => field ? getColumnData(field) : {};
952
+ return {
953
+ revision: `${nonce}:${chartItems?.nonce ?? 0}`,
954
+ allIds,
955
+ chartIds,
956
+ filteredIds: crossfilter.getFilteredRowIds(),
957
+ facetIds: facetIds?.slice(),
958
+ xData: column(settings.xField),
959
+ yData: column(settings.yField),
960
+ colorData: column(settings.colorField),
961
+ facetRowData: settings.facet.enabled ? column(settings.facet.rowVariable) : void 0,
962
+ facetColumnData: settings.facet.enabled && settings.facet.type === "grid" ? column(settings.facet.columnVariable) : void 0,
963
+ fieldSettings: Object.fromEntries(
964
+ Object.entries(fieldSettings).map(([field, value]) => [
965
+ field,
966
+ { ...value }
967
+ ])
968
+ ),
969
+ colorScale: colorScale?.type === "categorical" ? {
970
+ ...colorScale,
971
+ mapping: new Map(colorScale.mapping),
972
+ palette: [...colorScale.palette]
973
+ } : colorScale && { ...colorScale },
974
+ calculatedFields: calculations.map((calc) => calc.resultColumnName),
975
+ pixelRatio: typeof window === "undefined" ? 1 : window.devicePixelRatio || 1
976
+ };
977
+ }, [
978
+ allIds,
979
+ chartItems,
980
+ crossfilter,
981
+ getColumnData,
982
+ fieldSettings,
983
+ colorScale,
984
+ calculations,
985
+ nonce,
986
+ settings.xField,
987
+ settings.yField,
988
+ settings.colorField,
989
+ settings.facet,
990
+ facetIds
991
+ ]);
992
+ const plan = react.useMemo(
993
+ () => planScatter(settings, snapshot, width, height),
994
+ [settings, snapshot, width, height]
995
+ );
996
+ const source = react.useMemo(
997
+ () => ({
998
+ role: "chart",
999
+ revision: plan.revision,
1000
+ resolve: (kind, id) => resolveScatterTrace(
1001
+ { kind, id },
1002
+ plan,
1003
+ snapshot,
1004
+ settings,
1005
+ rawData,
1006
+ data,
1007
+ profiles,
1008
+ manager
1009
+ ),
1010
+ findRow: (id) => findScatterTraceRow(plan, id),
1011
+ targets: () => scatterTraceTargets(plan),
1012
+ legendItems: plan.legend?.type === "categorical" ? plan.legend.items : void 0
1013
+ }),
1014
+ [plan, snapshot, settings, rawData, data, profiles, manager]
1015
+ );
1016
+ chunkTRAIXZ6M_cjs.useTraceSource(owner, source);
1017
+ const choose = (kind, id) => traceApi?.inspect(owner, kind, id);
1018
+ const activeSelection = trace?.selection?.owner === owner ? trace.selection : null;
1019
+ const pointAt = (x, y) => {
1020
+ let nearest;
1021
+ let distance = 100;
1022
+ for (const point of plan.points) {
1023
+ const dx = point.x - x;
1024
+ const dy = point.y - y;
1025
+ const squared = dx * dx + dy * dy;
1026
+ if (squared < distance) {
1027
+ nearest = point;
1028
+ distance = squared;
1029
+ }
1030
+ }
1031
+ return nearest;
1032
+ };
1033
+ const hoveredPoint = plan.points.find((point) => point.id === hoveredId);
1034
+ const hoveredText = hoveredPoint && scatterHoverReadout(plan, snapshot, settings, hoveredPoint);
1035
+ react.useEffect(() => {
1036
+ const canvas = canvasRef.current;
1037
+ if (!canvas) {
1038
+ return;
1039
+ }
1040
+ const ctx = canvas.getContext("2d");
1041
+ if (!ctx) {
1042
+ return;
1043
+ }
1044
+ const dpr = plan.pixelRatio;
1045
+ canvas.width = width * dpr;
1046
+ canvas.height = height * dpr;
1047
+ ctx.scale(dpr, dpr);
1048
+ ctx.clearRect(0, 0, width, height);
1049
+ ctx.translate(plan.margin.left, plan.margin.top);
1050
+ ctx.beginPath();
1051
+ ctx.rect(0, 0, plan.plotWidth, plan.plotHeight);
1052
+ ctx.clip();
1053
+ for (const point of plan.points) {
1054
+ ctx.fillStyle = point.color;
1055
+ ctx.globalAlpha = point.opacity;
1056
+ ctx.beginPath();
1057
+ ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2);
1058
+ ctx.fill();
1059
+ }
1060
+ }, [plan, width, height]);
1061
+ const handleBrushChange = react.useCallback(
1062
+ (extent) => {
1063
+ const filters = settings.filters.filter(
1064
+ (filter) => filter.field !== settings.xField && filter.field !== settings.yField
1065
+ );
1066
+ if (extent) {
1067
+ const next = brushFilters(plan, extent);
1068
+ filters.push({
1069
+ type: "range",
1070
+ field: settings.xField,
1071
+ min: next.x[0],
1072
+ max: next.x[1]
1073
+ });
1074
+ filters.push({
1075
+ type: "range",
1076
+ field: settings.yField,
1077
+ min: next.y[0],
1078
+ max: next.y[1]
1079
+ });
1080
+ }
1081
+ updateChart(settings.id, { filters });
1082
+ },
1083
+ [plan, settings, updateChart]
1084
+ );
1085
+ return /* @__PURE__ */ jsxRuntime.jsx(
1086
+ "div",
1087
+ {
1088
+ style: { width, height },
1089
+ className: "relative",
1090
+ onPointerLeave: () => setHoveredId(null),
1091
+ onPointerDownCapture: () => setHoveredId(null),
1092
+ onKeyDownCapture: (event) => {
1093
+ if (event.key === "Escape") {
1094
+ setHoveredId(null);
1095
+ }
1096
+ },
1097
+ onPointerMove: (event) => {
1098
+ if (event.buttons) {
1099
+ return;
1100
+ }
1101
+ const bounds2 = event.currentTarget.getBoundingClientRect();
1102
+ const px = event.clientX - bounds2.left - plan.margin.left;
1103
+ const py = event.clientY - bounds2.top - plan.margin.top;
1104
+ if (px < 0 || px > plan.plotWidth || py < 0 || py > plan.plotHeight) {
1105
+ setHoveredId(null);
1106
+ return;
1107
+ }
1108
+ setHoveredId(pointAt(px, py)?.id ?? null);
1109
+ },
1110
+ children: plan.populations.facet > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1111
+ /* @__PURE__ */ jsxRuntime.jsx(
1112
+ "canvas",
1113
+ {
1114
+ ref: canvasRef,
1115
+ className: "absolute inset-0 pointer-events-none",
1116
+ style: { width, height }
1117
+ }
1118
+ ),
1119
+ /* @__PURE__ */ jsxRuntime.jsx(
1120
+ ScatterSvg,
1121
+ {
1122
+ plan,
1123
+ hoveredId,
1124
+ onBrushChange: handleBrushChange,
1125
+ onInspectPoint: (x, y) => {
1126
+ const point = pointAt(x, y);
1127
+ if (!point) return false;
1128
+ return Boolean(choose("point", point.id));
1129
+ },
1130
+ onInspectGuide: (id) => choose("guide", id),
1131
+ onInspectOverlay: (id) => choose("overlay", id),
1132
+ selectedId: activeSelection?.kind === "guide" ? activeSelection.id : void 0
1133
+ }
1134
+ ),
1135
+ plan.calculatedBadges.map((badge) => /* @__PURE__ */ jsxRuntime.jsx(
1136
+ "span",
1137
+ {
1138
+ className: `eda-scatter-axis-calc ${badge.rotation ? "eda-scatter-axis-calc-y" : ""}`,
1139
+ style: { left: badge.x, top: badge.y },
1140
+ onClickCapture: (event) => {
1141
+ if (!event.altKey) return;
1142
+ event.stopPropagation();
1143
+ choose("badge", badge.id);
1144
+ },
1145
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1146
+ chunkC7GQT65O_cjs.CalculatedFieldBadge,
1147
+ {
1148
+ field: badge.field,
1149
+ hoverOpen: false,
1150
+ side: badge.rotation ? "right" : "top"
1151
+ }
1152
+ )
1153
+ },
1154
+ badge.id
1155
+ )),
1156
+ hoveredPoint && /* @__PURE__ */ jsxRuntime.jsxs(
1157
+ "div",
1158
+ {
1159
+ className: "pointer-events-none absolute left-2 top-2 max-w-[min(16rem,70%)] rounded border border-border bg-card/95 px-2 py-1 text-xs text-card-foreground shadow-sm",
1160
+ role: "status",
1161
+ children: [
1162
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1163
+ plan.xDisplay,
1164
+ ": ",
1165
+ hoveredText?.xText
1166
+ ] }),
1167
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1168
+ plan.yDisplay,
1169
+ ": ",
1170
+ hoveredText?.yText
1171
+ ] }),
1172
+ settings.colorField && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1173
+ "Color \xB7 ",
1174
+ settings.colorField,
1175
+ ": ",
1176
+ hoveredText?.colorText
1177
+ ] }),
1178
+ settings.facet.enabled && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1179
+ "Facet ",
1180
+ settings.facet.rowVariable,
1181
+ ":",
1182
+ " ",
1183
+ hoveredText?.facetRowText,
1184
+ settings.facet.type === "grid" && ` \xB7 ${settings.facet.columnVariable}: ${hoveredText?.facetColumnText}`
1185
+ ] })
1186
+ ]
1187
+ }
1188
+ )
1189
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-muted-foreground", children: "No data available" })
1190
+ }
1191
+ );
1192
+ }
1193
+
1194
+ // src/components/charts/ScatterPlot/definition.ts
1195
+ var scatterPlotDefinition = {
1196
+ type: "scatter",
1197
+ name: "Scatter Plot",
1198
+ description: "Display data as points in a 2D space",
1199
+ icon: lucideReact.ScatterChart,
1200
+ component: ScatterPlot,
1201
+ settingsPanel: ScatterPlotSettingsPanel,
1202
+ createDefaultSettings: (layout, field) => ({
1203
+ ...chunkU74A5VK5_cjs.DEFAULT_CHART_SETTINGS,
1204
+ id: crypto.randomUUID(),
1205
+ type: "scatter",
1206
+ title: "Scatter Plot",
1207
+ layout,
1208
+ margin: { top: 20, right: 20, bottom: 20, left: 20 },
1209
+ xField: "__ID",
1210
+ yField: field ?? "",
1211
+ filters: []
1212
+ }),
1213
+ validateSettings: (settings) => {
1214
+ return !!settings.xField && !!settings.yField;
1215
+ },
1216
+ getFilterFunction: (settings, fieldGetter) => {
1217
+ const filters = settings.filters.map((filter) => ({
1218
+ filter,
1219
+ values: fieldGetter(filter.field)
1220
+ }));
1221
+ return (id) => filters.every(({ filter, values }) => chunkEU5EFHO5_cjs.applyFilter(values[id], filter));
1222
+ }
1223
+ };
1224
+
1225
+ exports.scatterPlotDefinition = scatterPlotDefinition;
1226
+ //# sourceMappingURL=chunk-LUUGGTTK.cjs.map
1227
+ //# sourceMappingURL=chunk-LUUGGTTK.cjs.map