ggaction 0.0.16 → 0.0.18

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 (299) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +3 -2
  3. package/knowledge/action-card.schema.json +1 -1
  4. package/knowledge/action-cards.json +1 -1
  5. package/knowledge/intent-taxonomy.json +1 -1
  6. package/knowledge/mcp-resources.json +1 -1
  7. package/knowledge/task-resolver.js +1 -1
  8. package/package.json +10 -4
  9. package/src/BasicChartProgram.js +1 -1
  10. package/src/accessibility.js +3 -2
  11. package/src/actions/boxPlots/create.js +14 -4
  12. package/src/actions/boxPlots/edit.js +39 -33
  13. package/src/actions/boxPlots/materialize.js +7 -3
  14. package/src/actions/boxPlots/options.js +37 -11
  15. package/src/actions/canvas/actions.js +70 -95
  16. package/src/actions/canvas/fitting.js +16 -1
  17. package/src/actions/canvas/index.js +3 -0
  18. package/src/actions/canvas/plotBorder.js +28 -0
  19. package/src/actions/charts/area.js +2 -2
  20. package/src/actions/charts/bar.js +34 -7
  21. package/src/actions/charts/ecdf.js +4 -18
  22. package/src/actions/charts/endpoints.js +4 -18
  23. package/src/actions/charts/heatmap.js +4 -4
  24. package/src/actions/charts/index.js +2 -0
  25. package/src/actions/charts/interval-regression.js +2 -2
  26. package/src/actions/charts/line.js +1 -1
  27. package/src/actions/charts/pie.js +3 -47
  28. package/src/actions/charts/radial.js +34 -12
  29. package/src/actions/charts/scatter.js +16 -2
  30. package/src/actions/charts/shared.js +2 -2
  31. package/src/actions/charts/text.js +35 -0
  32. package/src/actions/composition/actions.js +6 -2
  33. package/src/actions/data/bin.js +1 -1
  34. package/src/actions/data/box.js +2 -1
  35. package/src/actions/data/create.js +18 -5
  36. package/src/actions/data/density.js +1 -1
  37. package/src/actions/data/derived.js +15 -1
  38. package/src/actions/data/edit.js +20 -7
  39. package/src/actions/data/filter.js +15 -4
  40. package/src/actions/data/index.js +3 -0
  41. package/src/actions/data/interval.js +2 -0
  42. package/src/actions/data/regression.js +6 -2
  43. package/src/actions/data/revise.js +37 -5
  44. package/src/actions/data/shared.js +33 -4
  45. package/src/actions/data/sort.js +21 -0
  46. package/src/actions/data/stack.js +3 -1
  47. package/src/actions/data/summary.js +1 -1
  48. package/src/actions/encodings/angle.js +4 -1
  49. package/src/actions/encodings/appearance.js +43 -31
  50. package/src/actions/encodings/barWidth.js +36 -11
  51. package/src/actions/encodings/channels.js +0 -1
  52. package/src/actions/encodings/color/continuous.js +5 -2
  53. package/src/actions/encodings/color/index.js +5 -2
  54. package/src/actions/encodings/color/policy.js +10 -3
  55. package/src/actions/encodings/density/resolve.js +1 -6
  56. package/src/actions/encodings/offset.js +4 -1
  57. package/src/actions/encodings/position/policies/arc.js +12 -10
  58. package/src/actions/encodings/position/policies/area.js +3 -3
  59. package/src/actions/encodings/position/policies/bar.js +15 -6
  60. package/src/actions/encodings/position/policies/line.js +17 -6
  61. package/src/actions/encodings/position/resolve.js +9 -3
  62. package/src/actions/encodings/ranged.js +23 -1
  63. package/src/actions/encodings/remove.js +3 -2
  64. package/src/actions/encodings/ruleAppearance.js +4 -1
  65. package/src/actions/encodings/seriesLayout.js +1 -0
  66. package/src/actions/encodings/stroke.js +4 -1
  67. package/src/actions/encodings/strokeDash.js +6 -1
  68. package/src/actions/encodings/text.js +12 -4
  69. package/src/actions/errorBands/create.js +2 -2
  70. package/src/actions/errorBands/edit.js +2 -1
  71. package/src/actions/facets/actions.js +158 -240
  72. package/src/actions/gradientPlots/resolve.js +2 -17
  73. package/src/actions/guides/applicability.js +1 -1
  74. package/src/actions/guides/axes/axis.js +16 -4
  75. package/src/actions/guides/axes/edit.js +4 -4
  76. package/src/actions/guides/axes/labels.js +120 -46
  77. package/src/actions/guides/axes/lines.js +6 -5
  78. package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
  79. package/src/actions/guides/axes/parallel/policy.js +8 -7
  80. package/src/actions/guides/axes/remove.js +3 -2
  81. package/src/actions/guides/axes/tickGroups.js +5 -2
  82. package/src/actions/guides/axes/ticks.js +6 -3
  83. package/src/actions/guides/axes/titles.js +71 -26
  84. package/src/actions/guides/basic.js +2 -0
  85. package/src/actions/guides/facadeLegend.js +10 -2
  86. package/src/actions/guides/grids/grid.js +7 -1
  87. package/src/actions/guides/grids/resolve.js +59 -12
  88. package/src/actions/guides/legends/blocks.js +3 -1
  89. package/src/actions/guides/legends/categorical/actions.js +70 -17
  90. package/src/actions/guides/legends/categorical/components.js +12 -8
  91. package/src/actions/guides/legends/categorical/index.js +6 -0
  92. package/src/actions/guides/legends/categorical/layout.js +37 -8
  93. package/src/actions/guides/legends/categorical/options.js +31 -20
  94. package/src/actions/guides/legends/categorical/resolve.js +2 -2
  95. package/src/actions/guides/legends/categorical/symbols.js +18 -12
  96. package/src/actions/guides/legends/categorical/validation.js +1 -6
  97. package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
  98. package/src/actions/guides/legends/continuous/common.js +1 -447
  99. package/src/actions/guides/legends/continuous/gradient.js +47 -34
  100. package/src/actions/guides/legends/continuous/interval.js +4 -4
  101. package/src/actions/guides/legends/continuous/opacity.js +19 -12
  102. package/src/actions/guides/legends/edit.js +1 -604
  103. package/src/actions/guides/legends/focused.js +2 -2
  104. package/src/actions/guides/legends/group.js +1 -0
  105. package/src/actions/guides/legends/lane.js +1 -516
  106. package/src/actions/guides/legends/remove.js +3 -0
  107. package/src/actions/guides/legends/sampling.js +1 -1
  108. package/src/actions/guides/legends/size.js +51 -43
  109. package/src/actions/guides/legends/strokeWidth.js +11 -29
  110. package/src/actions/guides/legends/target.js +3 -3
  111. package/src/actions/guides/legends/transition.js +21 -7
  112. package/src/actions/guides/polar/axes/labels.js +4 -2
  113. package/src/actions/guides/polar/axes/titles.js +7 -5
  114. package/src/actions/guides/polar/resolve.js +6 -14
  115. package/src/actions/index.js +16 -16
  116. package/src/actions/marks/arc/actions.js +3 -10
  117. package/src/actions/marks/area/actions.js +8 -5
  118. package/src/actions/marks/area/materialize.js +8 -9
  119. package/src/actions/marks/bar/create.js +17 -4
  120. package/src/actions/marks/bar/edit.js +18 -7
  121. package/src/actions/marks/bar/materialize.js +9 -6
  122. package/src/actions/marks/line/actions.js +11 -4
  123. package/src/actions/marks/line/materialize.js +9 -5
  124. package/src/actions/marks/point/create.js +8 -1
  125. package/src/actions/marks/point/edit.js +13 -6
  126. package/src/actions/marks/point/materialize.js +4 -2
  127. package/src/actions/marks/rect/actions.js +26 -11
  128. package/src/actions/marks/remove.js +2 -0
  129. package/src/actions/marks/rule/actions.js +31 -12
  130. package/src/actions/marks/text/actions.js +42 -11
  131. package/src/actions/marks/text/layout.js +3 -0
  132. package/src/actions/marks/tick/actions.js +23 -8
  133. package/src/actions/primitives/semanticAction.js +37 -3
  134. package/src/actions/primitives/semanticValidation/dataset.js +13 -1
  135. package/src/actions/primitives/semanticValidation/layer.js +26 -2
  136. package/src/actions/primitives/semanticValidation/scale.js +6 -0
  137. package/src/actions/regression/create.js +26 -5
  138. package/src/actions/regression/edit.js +50 -15
  139. package/src/actions/regression/follow.js +22 -0
  140. package/src/actions/regression/resolve.js +2 -6
  141. package/src/actions/scales/channels.js +20 -15
  142. package/src/actions/scales/consumers/common.js +2 -1
  143. package/src/actions/scales/consumers/families.js +16 -5
  144. package/src/actions/scales/create.js +10 -4
  145. package/src/actions/scales/definitions.js +1 -425
  146. package/src/actions/scales/edit.js +3 -3
  147. package/src/actions/scales/editPolicy.js +9 -3
  148. package/src/actions/scales/materialize.js +1 -1
  149. package/src/actions/scales/parallel.js +1 -1
  150. package/src/actions/scales/patch.js +1 -1
  151. package/src/actions/scales/preview.js +49 -2
  152. package/src/actions/scales/quantitativeColor.js +3 -3
  153. package/src/actions/selection/actions.js +1 -13
  154. package/src/actions/theme/reconcile.js +83 -27
  155. package/src/actions/theme/state.js +12 -0
  156. package/src/actions/titles/actions.js +5 -18
  157. package/src/actions/titles/resolve.js +41 -19
  158. package/src/actions/violinPlots/edit.js +3 -20
  159. package/src/core/action.js +3 -2
  160. package/src/core/compositionState.js +4 -1
  161. package/src/core/font.js +9 -2
  162. package/src/core/optionRequirements.js +1 -0
  163. package/src/core/programState.js +5 -1
  164. package/src/core/resourceReferences.js +3 -0
  165. package/src/core/textMetricProfile.js +3 -2
  166. package/src/core/textMetrics.js +41 -4
  167. package/src/core/validation.js +26 -0
  168. package/src/core/vocabulary.js +4 -0
  169. package/src/grammar/aggregate.js +4 -18
  170. package/src/grammar/arcs.js +1 -386
  171. package/src/grammar/areaPaint.js +10 -0
  172. package/src/grammar/areaSeries.js +18 -10
  173. package/src/grammar/bars/geometry.js +30 -3
  174. package/src/grammar/bars/policy.js +12 -1
  175. package/src/grammar/bin.js +41 -22
  176. package/src/grammar/bin2d.js +1 -6
  177. package/src/grammar/boxPlot.js +56 -9
  178. package/src/grammar/categoryOrder.js +1 -6
  179. package/src/grammar/complete.js +15 -19
  180. package/src/grammar/computed.js +92 -25
  181. package/src/grammar/curveCommands.js +29 -16
  182. package/src/grammar/datasetSchema.js +382 -0
  183. package/src/grammar/density.js +53 -5
  184. package/src/grammar/displayLabels.js +28 -0
  185. package/src/grammar/ecdf.js +2 -9
  186. package/src/grammar/facets/dependencies.js +6 -2
  187. package/src/grammar/facets/guides.js +5 -0
  188. package/src/grammar/facets/index.js +2 -5
  189. package/src/grammar/filter.js +133 -39
  190. package/src/grammar/fold.js +2 -12
  191. package/src/grammar/gradientProfile.js +1 -6
  192. package/src/grammar/impute.js +21 -48
  193. package/src/grammar/interval.js +34 -10
  194. package/src/grammar/itemMissing.js +59 -0
  195. package/src/grammar/legendGradient.js +18 -0
  196. package/src/grammar/legendGroups.js +1 -0
  197. package/src/grammar/legendTitleStyle.js +22 -0
  198. package/src/grammar/lineSeries.js +32 -17
  199. package/src/grammar/markLabels.js +5 -2
  200. package/src/grammar/normalize.js +4 -54
  201. package/src/grammar/numeric.js +1 -290
  202. package/src/grammar/parallelCoordinates.js +1 -6
  203. package/src/grammar/parseTime.js +41 -0
  204. package/src/grammar/pathCommands.js +11 -4
  205. package/src/grammar/polar.js +1 -184
  206. package/src/grammar/positionCompatibility.js +3 -3
  207. package/src/grammar/regression/derive.js +63 -16
  208. package/src/grammar/regression/index.js +1 -0
  209. package/src/grammar/regression/models.js +116 -59
  210. package/src/grammar/regression/parameters.js +69 -6
  211. package/src/grammar/roundedRect.js +42 -54
  212. package/src/grammar/scales/appearance.js +1 -206
  213. package/src/grammar/scales/color.js +1 -382
  214. package/src/grammar/scales/colorConsumers.js +5 -2
  215. package/src/grammar/scales/definition.js +1 -190
  216. package/src/grammar/scales/discretized.js +1 -195
  217. package/src/grammar/scales/size.js +1 -456
  218. package/src/grammar/scales/temporal.js +2 -13
  219. package/src/grammar/scales/transformed.js +1 -383
  220. package/src/grammar/scales/types.js +1 -1
  221. package/src/grammar/scales/validation.js +1 -66
  222. package/src/grammar/schemas/concreteGraphic.js +11 -0
  223. package/src/grammar/schemas/graphic.js +3 -0
  224. package/src/grammar/schemas/semanticPath.js +5 -3
  225. package/src/grammar/sort.js +88 -0
  226. package/src/grammar/stack.js +2 -10
  227. package/src/grammar/statisticalReference.js +1 -6
  228. package/src/grammar/summary.js +120 -45
  229. package/src/grammar/text.js +19 -0
  230. package/src/grammar/timeUnit.js +16 -7
  231. package/src/grammar/transformKeys.js +47 -0
  232. package/src/grammar/transformTopology.js +1 -0
  233. package/src/grammar/transforms.js +25 -12
  234. package/src/grammar/valueFormat.js +17 -5
  235. package/src/grammar/weightedStatistics.js +1 -6
  236. package/src/grammar/window.js +4 -58
  237. package/src/inspection.js +495 -0
  238. package/src/inspectionDescriptors.js +3 -0
  239. package/src/layout/canvas.js +1 -110
  240. package/src/layout/facets.js +1 -244
  241. package/src/layout/labels.js +1 -0
  242. package/src/layout/legend.js +13 -0
  243. package/src/layout/legendGroup.js +1 -0
  244. package/src/layout/legendItems.js +59 -15
  245. package/src/layout/legendLane.js +1 -441
  246. package/src/layout/legendText.js +1 -0
  247. package/src/layout/title.js +35 -13
  248. package/src/materialization/bars/aggregate.js +11 -5
  249. package/src/materialization/bars/histogram.js +4 -1
  250. package/src/materialization/bars/ranged.js +19 -6
  251. package/src/materialization/bars/resolve.js +26 -3
  252. package/src/materialization/coordinateBounds.js +8 -0
  253. package/src/materialization/dependencies.js +4 -44
  254. package/src/materialization/facetGuides/layout.js +1 -0
  255. package/src/materialization/facetGuides/placement.js +19 -8
  256. package/src/materialization/facetGuides/preparation.js +15 -7
  257. package/src/materialization/facetHeaders.js +1 -347
  258. package/src/materialization/facets.js +1 -185
  259. package/src/materialization/graphicHierarchy.js +1 -1
  260. package/src/materialization/guides/layout.js +1 -57
  261. package/src/materialization/guides/resources.js +1 -123
  262. package/src/materialization/layout.js +53 -0
  263. package/src/materialization/legendGroups.js +1 -0
  264. package/src/materialization/legendText.js +1 -0
  265. package/src/materialization/legends.js +1 -1
  266. package/src/materialization/marks/capabilities.js +5 -6
  267. package/src/materialization/rect.js +1 -1
  268. package/src/materialization/revisionIdentity.js +19 -0
  269. package/src/materialization/scaleGuideDependencies.js +1 -1
  270. package/src/materialization/scales/map.js +3 -0
  271. package/src/materialization/scales/policies/arc.js +8 -7
  272. package/src/materialization/scales/policies/bar.js +19 -7
  273. package/src/materialization/scales/policies/series.js +2 -1
  274. package/src/materialization/scales/resolve.js +72 -7
  275. package/src/materialization/selection/items/arc.js +1 -1
  276. package/src/materialization/selection/items/bar.js +7 -6
  277. package/src/materialization/selection/items/path.js +7 -6
  278. package/src/materialization/selection/items/rect.js +2 -2
  279. package/src/materialization/text.js +17 -6
  280. package/src/materialization/textLines.js +15 -0
  281. package/src/persistence/codec.js +37 -6
  282. package/src/persistence/validation.js +47 -0
  283. package/src/persistence.js +94 -8
  284. package/src/renderers/canvas/circle.js +6 -3
  285. package/src/renderers/canvas/fill.js +7 -1
  286. package/src/renderers/canvas/index.js +2 -1
  287. package/src/renderers/canvas/line.js +1 -0
  288. package/src/renderers/canvas/path.js +1 -1
  289. package/src/renderers/canvas/rect.js +11 -8
  290. package/src/renderers/canvas/stroke.js +2 -1
  291. package/src/renderers/canvas/text.js +5 -3
  292. package/src/renderers/svg.js +4 -1
  293. package/src/selectors/layers.js +19 -0
  294. package/src/theme/defaults.js +8 -2
  295. package/src/version.js +1 -1
  296. package/types/basic.d.ts +1 -0
  297. package/types/index.d.ts +7 -0
  298. package/types/inspection.d.ts +17 -0
  299. package/types/program.d.ts +494 -112
@@ -1,184 +1 @@
1
- import { cloneAndFreeze, isPlainObject } from "../core/immutable.js";
2
- import { validateOptionObject } from "../core/validation.js";
3
- import { validatePair } from "./scales/validation.js";
4
-
5
- const POLAR_FRAME_OPTIONS = Object.freeze(["center", "radius"]);
6
- const POLAR_CENTER_OPTIONS = Object.freeze(["x", "y"]);
7
- const POLAR_RADIUS_OPTIONS = Object.freeze(["unit", "value"]);
8
- const POLAR_RADIUS_UNITS = new Set(["fraction", "px"]);
9
-
10
- function validateBounds(bounds) {
11
- if (
12
- !isPlainObject(bounds) ||
13
- ![bounds.x, bounds.y, bounds.width, bounds.height].every(Number.isFinite) ||
14
- bounds.width < 0 ||
15
- bounds.height < 0
16
- ) {
17
- throw new TypeError(
18
- "Polar geometry requires finite, non-negative graphical bounds."
19
- );
20
- }
21
- return bounds;
22
- }
23
-
24
- function finiteUnitInterval(value, label) {
25
- if (!Number.isFinite(value) || value < 0 || value > 1) {
26
- throw new RangeError(`${label} must be a finite number from 0 through 1.`);
27
- }
28
- return value;
29
- }
30
-
31
- export function normalizePolarFrameOptions(value) {
32
- if (value === "auto") return value;
33
- if (!isPlainObject(value)) {
34
- throw new TypeError('Polar frame must be "auto" or a plain object.');
35
- }
36
- validateOptionObject(value, POLAR_FRAME_OPTIONS, "polar frame");
37
- const center = value.center ?? {};
38
- if (!isPlainObject(center)) {
39
- throw new TypeError("Polar frame center must be a plain object.");
40
- }
41
- validateOptionObject(center, POLAR_CENTER_OPTIONS, "polar frame center");
42
- const radius = value.radius ?? { unit: "fraction", value: 1 };
43
- if (!isPlainObject(radius)) {
44
- throw new TypeError("Polar frame radius must be a plain object.");
45
- }
46
- validateOptionObject(radius, POLAR_RADIUS_OPTIONS, "polar frame radius", {
47
- allowEmpty: false,
48
- emptyError: TypeError
49
- });
50
- if (!POLAR_RADIUS_UNITS.has(radius.unit)) {
51
- throw new Error(`Unknown Polar frame radius unit "${radius.unit}".`);
52
- }
53
- if (!Number.isFinite(radius.value) || radius.value <= 0) {
54
- throw new RangeError("Polar frame radius must be a positive finite number.");
55
- }
56
- if (radius.unit === "fraction" && radius.value > 1) {
57
- throw new RangeError("Polar frame radius fraction must not exceed 1.");
58
- }
59
- return cloneAndFreeze({
60
- center: {
61
- x: finiteUnitInterval(center.x ?? 0.5, "Polar frame center x"),
62
- y: finiteUnitInterval(center.y ?? 0.5, "Polar frame center y")
63
- },
64
- radius: {
65
- unit: radius.unit,
66
- value: radius.value
67
- }
68
- });
69
- }
70
-
71
- export function resolvePolarFrame(bounds, requestedFrame = "auto") {
72
- validateBounds(bounds);
73
- if (requestedFrame === undefined || requestedFrame === "auto") {
74
- return cloneAndFreeze({
75
- centerX: bounds.x + bounds.width / 2,
76
- centerY: bounds.y + bounds.height / 2,
77
- availableRadius: Math.min(bounds.width, bounds.height) / 2
78
- });
79
- }
80
- const requested = normalizePolarFrameOptions(requestedFrame);
81
- const centerX = bounds.x + bounds.width * requested.center.x;
82
- const centerY = bounds.y + bounds.height * requested.center.y;
83
- const maximumRadius = Math.min(
84
- centerX - bounds.x,
85
- bounds.x + bounds.width - centerX,
86
- centerY - bounds.y,
87
- bounds.y + bounds.height - centerY
88
- );
89
- if (!Number.isFinite(maximumRadius) || maximumRadius <= 0) {
90
- throw new RangeError(
91
- "Polar frame center must leave a positive radius inside its bounds."
92
- );
93
- }
94
- const availableRadius = requested.radius.unit === "fraction"
95
- ? maximumRadius * requested.radius.value
96
- : requested.radius.value;
97
- if (availableRadius > maximumRadius) {
98
- throw new RangeError(
99
- `Polar frame radius ${availableRadius} exceeds the maximum radius ${maximumRadius}.`
100
- );
101
- }
102
- return cloneAndFreeze({
103
- centerX,
104
- centerY,
105
- availableRadius
106
- });
107
- }
108
-
109
- export function validateThetaRange(range) {
110
- if (range === "auto") return range;
111
- const validated = validatePair(range, "Theta scale range");
112
- if (Math.abs(validated[1] - validated[0]) > 360) {
113
- throw new RangeError("Theta scale range span must not exceed 360 degrees.");
114
- }
115
- return validated;
116
- }
117
-
118
- export function validateRadialRange(range, availableRadius) {
119
- if (range === "auto") return range;
120
- const validated = validatePair(range, "Radius scale range");
121
- if (validated.some(value => value < 0)) {
122
- throw new RangeError("Radius scale range values must be non-negative.");
123
- }
124
- if (
125
- availableRadius !== undefined &&
126
- validated.some(value => value > availableRadius)
127
- ) {
128
- throw new RangeError(
129
- `Radius scale range must fit within the available radius ${availableRadius}.`
130
- );
131
- }
132
- return validated;
133
- }
134
-
135
- export function resolvePolarScaleRange(range, channel, bounds, frame) {
136
- if (channel === "theta") {
137
- return range === "auto"
138
- ? cloneAndFreeze([0, 360])
139
- : validateThetaRange(range);
140
- }
141
- if (channel !== "radius") {
142
- throw new Error(`Unknown Polar position channel "${channel}".`);
143
- }
144
- const resolvedFrame = frame ?? resolvePolarFrame(bounds);
145
- return range === "auto"
146
- ? cloneAndFreeze([0, resolvedFrame.availableRadius])
147
- : validateRadialRange(range, resolvedFrame.availableRadius);
148
- }
149
-
150
- export function polarDirection(theta) {
151
- if (!Number.isFinite(theta)) {
152
- throw new TypeError("Polar theta must be a finite number of degrees.");
153
- }
154
- const angle = theta * Math.PI / 180;
155
- return cloneAndFreeze({
156
- x: Math.sin(angle),
157
- y: -Math.cos(angle)
158
- });
159
- }
160
-
161
- export function polarToCartesian({ theta, radius, frame }) {
162
- const direction = polarDirection(theta);
163
- if (!Number.isFinite(radius) || radius < 0) {
164
- throw new TypeError("Polar radius must be a non-negative finite number.");
165
- }
166
- const resolvedFrame = frame?.availableRadius === undefined
167
- ? resolvePolarFrame(frame)
168
- : frame;
169
- if (
170
- ![resolvedFrame.centerX, resolvedFrame.centerY,
171
- resolvedFrame.availableRadius].every(Number.isFinite)
172
- ) {
173
- throw new TypeError("Polar frame must contain finite geometry.");
174
- }
175
- if (radius > resolvedFrame.availableRadius) {
176
- throw new RangeError(
177
- `Polar radius ${radius} exceeds the available radius ${resolvedFrame.availableRadius}.`
178
- );
179
- }
180
- return cloneAndFreeze({
181
- x: resolvedFrame.centerX + radius * direction.x,
182
- y: resolvedFrame.centerY + radius * direction.y
183
- });
184
- }
1
+ import{cloneAndFreeze as n,isPlainObject as l}from"../core/immutable.js";import{validateOptionObject as u}from"../core/validation.js";import{validatePair as c}from"./scales/validation.js";const w=Object.freeze(["center","radius","overflow"]),h=Object.freeze(["x","y"]),d=Object.freeze(["unit","value"]),p=new Set(["fraction","px"]);function v(e){if(!l(e)||![e.x,e.y,e.width,e.height].every(Number.isFinite)||e.width<0||e.height<0)throw new TypeError("Polar geometry requires finite, non-negative graphical bounds.");return e}function s(e,r){if(!Number.isFinite(e)||e<0||e>1)throw new RangeError(`${r} must be a finite number from 0 through 1.`);return e}function R(e){if(e==="auto")return e;if(!l(e))throw new TypeError('Polar frame must be "auto" or a plain object.');if(u(e,w,"polar frame"),e.overflow!==void 0&&!["error","allow"].includes(e.overflow))throw new Error("Polar frame overflow must be error or allow.");const r=e.center??{};if(!l(r))throw new TypeError("Polar frame center must be a plain object.");u(r,h,"polar frame center");const t=e.radius??{unit:"fraction",value:1};if(!l(t))throw new TypeError("Polar frame radius must be a plain object.");if(u(t,d,"polar frame radius",{allowEmpty:!1,emptyError:TypeError}),!p.has(t.unit))throw new Error(`Unknown Polar frame radius unit "${t.unit}".`);if(!Number.isFinite(t.value)||t.value<=0)throw new RangeError("Polar frame radius must be a positive finite number.");if(t.unit==="fraction"&&t.value>1)throw new RangeError("Polar frame radius fraction must not exceed 1.");return n({center:{x:s(r.x??.5,"Polar frame center x"),y:s(r.y??.5,"Polar frame center y")},radius:{unit:t.unit,value:t.value},...e.overflow==="allow"?{overflow:"allow"}:{}})}function m(e,r="auto"){if(v(e),r===void 0||r==="auto")return n({centerX:e.x+e.width/2,centerY:e.y+e.height/2,availableRadius:Math.min(e.width,e.height)/2});const t=R(r),a=e.x+e.width*t.center.x,i=e.y+e.height*t.center.y,o=Math.min(a-e.x,e.x+e.width-a,i-e.y,e.y+e.height-i);if(!Number.isFinite(o)||o<=0)throw new RangeError("Polar frame center must leave a positive radius inside its bounds.");const f=t.radius.unit==="fraction"?o*t.radius.value:t.radius.value;if(f>o&&t.overflow!=="allow")throw new RangeError(`Polar frame radius ${f} exceeds the maximum radius ${o}.`);return n({centerX:a,centerY:i,availableRadius:f})}function P(e){if(e==="auto")return e;const r=c(e,"Theta scale range");if(Math.abs(r[1]-r[0])>360)throw new RangeError("Theta scale range span must not exceed 360 degrees.");return r}function x(e,r){if(e==="auto")return e;const t=c(e,"Radius scale range");if(t.some(a=>a<0))throw new RangeError("Radius scale range values must be non-negative.");if(r!==void 0&&t.some(a=>a>r))throw new RangeError(`Radius scale range must fit within the available radius ${r}.`);return t}function T(e,r,t,a){if(r==="theta")return e==="auto"?n([0,360]):P(e);if(r!=="radius")throw new Error(`Unknown Polar position channel "${r}".`);const i=a??m(t);return e==="auto"?n([0,i.availableRadius]):x(e,i.availableRadius)}function y(e){if(!Number.isFinite(e))throw new TypeError("Polar theta must be a finite number of degrees.");const r=e*Math.PI/180;return n({x:Math.sin(r),y:-Math.cos(r)})}function b({theta:e,radius:r,frame:t}){const a=y(e);if(!Number.isFinite(r)||r<0)throw new TypeError("Polar radius must be a non-negative finite number.");const i=t?.availableRadius===void 0?m(t):t;if(![i.centerX,i.centerY,i.availableRadius].every(Number.isFinite))throw new TypeError("Polar frame must contain finite geometry.");if(r>i.availableRadius)throw new RangeError(`Polar radius ${r} exceeds the available radius ${i.availableRadius}.`);return n({x:i.centerX+r*a.x,y:i.centerY+r*a.y})}export{R as normalizePolarFrameOptions,y as polarDirection,b as polarToCartesian,m as resolvePolarFrame,T as resolvePolarScaleRange,x as validateRadialRange,P as validateThetaRange};
@@ -6,14 +6,14 @@ export const POSITION_FIELD_COMPATIBILITY = Object.freeze({
6
6
  radius: Object.freeze(["quantitative"])
7
7
  }),
8
8
  line: Object.freeze({
9
- x: Object.freeze(["quantitative", "temporal"]),
9
+ x: Object.freeze(["quantitative", "temporal", "ordinal", "nominal"]),
10
10
  y: Object.freeze(["quantitative", "temporal", "ordinal", "nominal"]),
11
11
  theta: Object.freeze(["quantitative", "temporal", "ordinal", "nominal"]),
12
12
  radius: Object.freeze(["quantitative"])
13
13
  }),
14
14
  area: Object.freeze({
15
- x: Object.freeze(["quantitative", "temporal"]),
16
- y: Object.freeze(["quantitative", "temporal"])
15
+ x: Object.freeze(["quantitative", "temporal", "ordinal", "nominal"]),
16
+ y: Object.freeze(["quantitative", "temporal", "ordinal", "nominal"])
17
17
  }),
18
18
  arc: Object.freeze({
19
19
  theta: Object.freeze(["quantitative", "ordinal", "nominal"]),
@@ -24,10 +24,13 @@ export function deriveRegression(values, {
24
24
  method,
25
25
  degree,
26
26
  span,
27
+ robustIterations,
27
28
  confidenceMethod,
28
29
  level,
29
30
  confidence,
30
- interval
31
+ interval,
32
+ predict,
33
+ missing
31
34
  } = {}) {
32
35
  if (!Array.isArray(values)) {
33
36
  throw new TypeError("Regression values must be an array.");
@@ -41,20 +44,35 @@ export function deriveRegression(values, {
41
44
  method,
42
45
  degree,
43
46
  span,
47
+ robustIterations,
44
48
  confidenceMethod,
45
49
  level,
46
50
  confidence,
47
51
  interval
48
52
  });
49
- readQuantitativeField(values, x);
50
- readQuantitativeField(values, y);
51
- if (groupBy !== undefined) readNominalField(values, groupBy);
53
+ let missingRows = 0;
54
+ const eligibleValues = missing === undefined ? values : values.filter((row, index) => {
55
+ const xValue = row?.[x];
56
+ const yValue = row?.[y];
57
+ if (xValue === null || xValue === undefined || yValue === null || yValue === undefined) {
58
+ if (missing === "error") throw new TypeError(`Regression x/y pair is missing at row ${index}.`);
59
+ missingRows += 1;
60
+ return false;
61
+ }
62
+ if (!Number.isFinite(xValue) || !Number.isFinite(yValue)) {
63
+ throw new TypeError(`Regression x/y fields must contain finite numbers at row ${index}.`);
64
+ }
65
+ return true;
66
+ });
67
+ readQuantitativeField(eligibleValues, x);
68
+ readQuantitativeField(eligibleValues, y);
69
+ if (groupBy !== undefined) readNominalField(eligibleValues, groupBy);
52
70
 
53
71
  const groupedRows = new Map();
54
72
  if (groupBy === undefined) {
55
- groupedRows.set(undefined, values);
73
+ groupedRows.set(undefined, eligibleValues);
56
74
  } else {
57
- for (const row of values) {
75
+ for (const row of eligibleValues) {
58
76
  const group = row[groupBy];
59
77
  const rows = groupedRows.get(group) ?? [];
60
78
  rows.push(row);
@@ -68,13 +86,29 @@ export function deriveRegression(values, {
68
86
 
69
87
  for (const group of groups) {
70
88
  const groupRows = groupedRows.get(group);
71
- const xCount = new Set(groupRows.map(row => row[x])).size;
72
- validateGeneratedItemLimit(rows.length + xCount, "Regression generated row count");
89
+ const observedX = [...new Set(groupRows.map(row => row[x]))]
90
+ .sort((left, right) => left - right);
91
+ const xValues = predict?.values ?? (predict?.domain === undefined
92
+ ? observedX
93
+ : Array.from({ length: predict.steps }, (_, index) => {
94
+ if (index === 0) return predict.domain[0];
95
+ if (index === predict.steps - 1) return predict.domain[1];
96
+ const ratio = index / (predict.steps - 1);
97
+ return requireFiniteResult(
98
+ predict.domain[0] * (1 - ratio) + predict.domain[1] * ratio,
99
+ "Regression prediction grid"
100
+ );
101
+ }));
102
+ validateGeneratedItemLimit(rows.length + xValues.length, "Regression generated row count");
73
103
  if (parameters.method === "polynomial") {
74
104
  const size = parameters.degree + 1;
75
105
  work += groupRows.length * size ** 2 + size ** 3;
76
106
  } else if (parameters.method === "loess") {
77
- work += groupRows.length * xCount *
107
+ const observedSet = new Set(observedX);
108
+ const newPredictions = predict === undefined ? 0 :
109
+ xValues.filter(value => !observedSet.has(value)).length;
110
+ work += groupRows.length *
111
+ (observedX.length * (1 + (parameters.robustIterations ?? 0)) + newPredictions) *
78
112
  Math.ceil(Math.log2(groupRows.length + 1));
79
113
  }
80
114
  validateWorkLimit(work, "Regression computation");
@@ -84,8 +118,6 @@ export function deriveRegression(values, {
84
118
  group,
85
119
  parameters
86
120
  });
87
- const xValues = [...new Set(groupRows.map(row => row[x]))]
88
- .sort((left, right) => left - right);
89
121
  models.push({ ...(groupBy === undefined ? {} : { group }), ...model, xValues });
90
122
 
91
123
  for (const xValue of xValues) {
@@ -94,13 +126,13 @@ export function deriveRegression(values, {
94
126
  prediction.prediction,
95
127
  `Regression group "${group === undefined ? "all" : String(group)}" prediction`
96
128
  );
97
- const lower = parameters.method === "loess"
129
+ const lower = parameters.method === "loess" || parameters.interval === false
98
130
  ? undefined
99
131
  : requireFiniteResult(
100
132
  fitted - prediction.margin,
101
133
  `Regression group "${group === undefined ? "all" : String(group)}" lower interval`
102
134
  );
103
- const upper = parameters.method === "loess"
135
+ const upper = parameters.method === "loess" || parameters.interval === false
104
136
  ? undefined
105
137
  : requireFiniteResult(
106
138
  fitted + prediction.margin,
@@ -110,7 +142,7 @@ export function deriveRegression(values, {
110
142
  ...(groupBy === undefined ? {} : { [groupBy]: group }),
111
143
  [x]: xValue,
112
144
  [y]: fitted,
113
- ...(parameters.method === "loess" ? {} : {
145
+ ...(parameters.method === "loess" || parameters.interval === false ? {} : {
114
146
  [REGRESSION_LOWER_FIELD]: lower,
115
147
  [REGRESSION_UPPER_FIELD]: upper
116
148
  })
@@ -123,7 +155,7 @@ export function deriveRegression(values, {
123
155
  x,
124
156
  y,
125
157
  ...(groupBy === undefined ? {} : { group: groupBy }),
126
- ...(parameters.method === "loess" ? {} : {
158
+ ...(parameters.method === "loess" || parameters.interval === false ? {} : {
127
159
  lower: REGRESSION_LOWER_FIELD,
128
160
  upper: REGRESSION_UPPER_FIELD
129
161
  })
@@ -131,7 +163,22 @@ export function deriveRegression(values, {
131
163
  parameters,
132
164
  groups,
133
165
  models,
134
- values: rows
166
+ values: rows,
167
+ ...(missing === undefined ? {} : {
168
+ report: {
169
+ version: 1,
170
+ owner: { kind: "data", id: "pending" },
171
+ inputs: [],
172
+ units: [{
173
+ role: "fit",
174
+ group: {},
175
+ inputRows: values.length,
176
+ usedRows: eligibleValues.length,
177
+ excludedRows: missingRows,
178
+ excludedByReason: missingRows === 0 ? {} : { "missing-value": missingRows }
179
+ }]
180
+ }
181
+ })
135
182
  });
136
183
  }
137
184
 
@@ -9,6 +9,7 @@ export {
9
9
  } from "./derive.js";
10
10
  export {
11
11
  normalizeRegressionParameters,
12
+ normalizeRegressionPredict,
12
13
  normalizeRegressionTransform,
13
14
  validateRegressionTransform
14
15
  } from "./parameters.js";
@@ -58,14 +58,14 @@ function linearPrediction(model, xValue) {
58
58
  }
59
59
 
60
60
  function fitLinearGroup(rows, {
61
- x, y, group, confidenceMethod, level
61
+ x, y, group, confidenceMethod, level, interval
62
62
  }) {
63
63
  const count = rows.length;
64
64
  const groupLabel = group === undefined ? "all" : String(group);
65
65
  const label = `Regression group "${groupLabel}"`;
66
- if (count < 3) {
66
+ if (count < (interval === false ? 2 : 3)) {
67
67
  throw new Error(
68
- `${label} requires at least three rows.`
68
+ `${label} requires at least ${interval === false ? "two" : "three"} rows.`
69
69
  );
70
70
  }
71
71
  const xValues = rows.map(row => row[x]);
@@ -148,6 +148,7 @@ function fitLinearGroup(rows, {
148
148
  normalizedSxx
149
149
  });
150
150
  if (!normalizedXByDifference) model.normalizedXByDifference = false;
151
+ if (interval === false) return model;
151
152
  const residualSumSquares = stableFiniteSquareSum(
152
153
  rows.map(row => row[y] - linearPrediction(model, row[x])),
153
154
  `${label} residual sum of squares`
@@ -238,19 +239,19 @@ function polynomialResponse(design, values, size, scale = 1) {
238
239
  }
239
240
 
240
241
  function fitPolynomialGroup(rows, {
241
- x, y, group, confidenceMethod, level, degree
242
+ x, y, group, confidenceMethod, level, degree, interval
242
243
  }) {
243
244
  const count = rows.length;
244
245
  const parameterCount = degree + 1;
245
246
  const groupLabel = group === undefined ? "all" : String(group);
246
247
  const label = `Polynomial regression group "${groupLabel}"`;
247
248
  if (
248
- count < degree + 2 ||
249
+ count < (interval === false ? parameterCount : degree + 2) ||
249
250
  new Set(rows.map(row => row[x])).size < parameterCount
250
251
  ) {
251
252
  throw new Error(
252
253
  `${label} requires at least ` +
253
- `${degree + 2} rows and ${parameterCount} distinct x values.`
254
+ `${interval === false ? parameterCount : degree + 2} rows and ${parameterCount} distinct x values.`
254
255
  );
255
256
  }
256
257
  const xValues = rows.map(row => row[x]);
@@ -289,7 +290,7 @@ function fitPolynomialGroup(rows, {
289
290
  `${label} coefficient ${index}`
290
291
  ))
291
292
  : scaledCoefficients;
292
- const inverse = invertSymmetricMatrix(normal);
293
+ const inverse = interval === false ? undefined : invertSymmetricMatrix(normal);
293
294
  const fitted = design.map(basis => stableResponse
294
295
  ? restoreFiniteScale(
295
296
  dot(basis, scaledCoefficients),
@@ -298,12 +299,12 @@ function fitPolynomialGroup(rows, {
298
299
  )
299
300
  : dot(basis, normalizedCoefficients)
300
301
  );
301
- const residualSumSquares = stableFiniteSquareSum(
302
+ const residualSumSquares = interval === false ? undefined : stableFiniteSquareSum(
302
303
  fitted.map((value, index) => rows[index][y] - value),
303
304
  `${label} residual sum of squares`
304
305
  );
305
306
  const degreesOfFreedom = count - parameterCount;
306
- const residualVariance = residualSumSquares / degreesOfFreedom;
307
+ const residualVariance = interval === false ? undefined : residualSumSquares / degreesOfFreedom;
307
308
  const model = {
308
309
  count,
309
310
  degreesOfFreedom,
@@ -316,13 +317,15 @@ function fitPolynomialGroup(rows, {
316
317
  normalizedCoefficients,
317
318
  center,
318
319
  scale,
319
- inverse,
320
- residualSumSquares,
321
- residualStandardError: Math.sqrt(residualVariance),
322
- critical: confidenceCriticalValue({
323
- method: confidenceMethod,
324
- level,
325
- degreesOfFreedom
320
+ ...(inverse === undefined ? {} : { inverse }),
321
+ ...(interval === false ? {} : {
322
+ residualSumSquares,
323
+ residualStandardError: Math.sqrt(residualVariance),
324
+ critical: confidenceCriticalValue({
325
+ method: confidenceMethod,
326
+ level,
327
+ degreesOfFreedom
328
+ })
326
329
  })
327
330
  };
328
331
  if (!normalizedXByDifference) model.normalizedXByDifference = false;
@@ -345,7 +348,9 @@ function evaluatePolynomial(model, xValue) {
345
348
  model.responseScale,
346
349
  "Polynomial regression prediction"
347
350
  ),
348
- leverage: dot(basis, model.inverse.map(row => dot(row, basis)))
351
+ ...(model.inverse === undefined ? {} : {
352
+ leverage: dot(basis, model.inverse.map(row => dot(row, basis)))
353
+ })
349
354
  };
350
355
  }
351
356
 
@@ -371,7 +376,56 @@ function weightedPrediction(items, x, y, xValue, xScale = 1, yScale = 1) {
371
376
  return meanY - (variance === 0 ? 0 : covariance / variance) * meanDifference;
372
377
  }
373
378
 
374
- function fitLoessGroup(rows, { x, y, group, span }) {
379
+ function applyResidualWeights(neighbors, residualWeights) {
380
+ if (!neighbors.some(item => item.weight > 0)) {
381
+ neighbors = neighbors.map(item => ({ ...item, weight: 1 }));
382
+ }
383
+ if (residualWeights === undefined) return neighbors;
384
+ const weighted = neighbors.map(item => ({
385
+ ...item, weight: item.weight * residualWeights[item.index]
386
+ }));
387
+ return weighted.some(item => item.weight > 0) ? weighted : neighbors;
388
+ }
389
+
390
+ function loessPrediction(rows, {
391
+ x, y, xValue, xScale, yScale, neighborCount, residualWeights, scaled = false
392
+ }, label) {
393
+ const rawDistances = rows.map(row => Math.abs(row[x] - xValue));
394
+ const scaledDistances = scaled || rawDistances.some(value => !Number.isFinite(value));
395
+ const neighbors = rows
396
+ .map((row, index) => ({
397
+ row,
398
+ index,
399
+ distance: scaledDistances
400
+ ? Math.abs(row[x] / xScale - xValue / xScale)
401
+ : rawDistances[index]
402
+ }))
403
+ .sort((left, right) =>
404
+ left.distance - right.distance || left.index - right.index
405
+ )
406
+ .slice(0, neighborCount);
407
+ const radius = neighbors.at(-1).distance;
408
+ let weighted = neighbors.map(neighbor => ({
409
+ ...neighbor,
410
+ weight: radius === 0
411
+ ? 1
412
+ : (1 - (neighbor.distance / radius) ** 3) ** 3
413
+ }));
414
+ weighted = applyResidualWeights(weighted, residualWeights);
415
+ const ordinary = weightedPrediction(weighted, x, y, xValue);
416
+ return {
417
+ prediction: Number.isFinite(ordinary)
418
+ ? ordinary
419
+ : restoreFiniteScale(
420
+ weightedPrediction(weighted, x, y, xValue, xScale, yScale),
421
+ yScale,
422
+ label
423
+ ),
424
+ neighborIndices: weighted.map(item => item.index)
425
+ };
426
+ }
427
+
428
+ function fitLoessGroup(rows, { x, y, group, span, robustIterations = 0 }) {
375
429
  const groupLabel = group === undefined ? "all" : String(group);
376
430
  const label = `LOESS regression group "${groupLabel}"`;
377
431
  if (rows.length < 2 || new Set(rows.map(row => row[x])).size < 2) {
@@ -385,42 +439,42 @@ function fitLoessGroup(rows, { x, y, group, span }) {
385
439
  .sort((left, right) => left - right);
386
440
  const xScale = maximumMagnitude(rows.map(row => row[x])) || 1;
387
441
  const yScale = maximumMagnitude(rows.map(row => row[y])) || 1;
388
- const fits = xValues.map(xValue => {
389
- const rawDistances = rows.map(row => Math.abs(row[x] - xValue));
390
- const scaledDistances = rawDistances.some(value => !Number.isFinite(value));
391
- const neighbors = rows
392
- .map((row, index) => ({
393
- row,
394
- index,
395
- distance: scaledDistances
396
- ? Math.abs(row[x] / xScale - xValue / xScale)
397
- : rawDistances[index]
398
- }))
399
- .sort((left, right) =>
400
- left.distance - right.distance || left.index - right.index
401
- )
402
- .slice(0, neighborCount);
403
- const radius = neighbors.at(-1).distance;
404
- const weighted = neighbors.map(neighbor => ({
405
- ...neighbor,
406
- weight: radius === 0
407
- ? 1
408
- : (1 - (neighbor.distance / radius) ** 3) ** 3
409
- }));
410
- const ordinary = weightedPrediction(weighted, x, y, xValue);
411
- return {
412
- x: xValue,
413
- prediction: Number.isFinite(ordinary)
414
- ? ordinary
415
- : restoreFiniteScale(
416
- weightedPrediction(weighted, x, y, xValue, xScale, yScale),
417
- yScale,
418
- `${label} prediction`
419
- ),
420
- neighborIndices: weighted.map(item => item.index)
421
- };
422
- });
423
- return { count: rows.length, span, neighborCount, fits };
442
+ const fitAtObserved = residualWeights => xValues.map(xValue => ({
443
+ x: xValue,
444
+ ...loessPrediction(rows, {
445
+ x, y, xValue, xScale, yScale, neighborCount, residualWeights
446
+ }, `${label} prediction`)
447
+ }));
448
+ let fits = fitAtObserved();
449
+ let residualWeights;
450
+ for (let iteration = 0; iteration < robustIterations; iteration += 1) {
451
+ const predictions = new Map(fits.map(fit => [fit.x, fit.prediction]));
452
+ const residuals = rows.map(row => Math.abs(row[y] / yScale - predictions.get(row[x]) / yScale));
453
+ const sorted = [...residuals].sort((a, b) => a - b);
454
+ const middle = Math.floor(sorted.length / 2);
455
+ const median = sorted.length % 2 === 0
456
+ ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle];
457
+ const tolerance = 64 * Number.EPSILON;
458
+ const cutoff = 6 * median;
459
+ const nextWeights = residuals.map(residual => {
460
+ if (cutoff <= tolerance) return residual <= tolerance ? 1 : 0;
461
+ const ratio = residual / cutoff;
462
+ return ratio >= 1 ? 0 : (1 - ratio * ratio) ** 2;
463
+ });
464
+ if (nextWeights.every((weight, index) => weight === (residualWeights?.[index] ?? 1))) break;
465
+ residualWeights = nextWeights;
466
+ fits = fitAtObserved(residualWeights);
467
+ }
468
+ return { count: rows.length, span, neighborCount, fits, rows, x, y, xScale, yScale,
469
+ ...(robustIterations === 0 ? {} : { robustIterations, residualWeights: residualWeights ?? rows.map(() => 1) }) };
470
+ }
471
+
472
+ function predictLoessAt(model, xValue) {
473
+ const found = model.fits.find(fit => fit.x === xValue);
474
+ if (found !== undefined) return found.prediction;
475
+ return loessPrediction(model.rows, {
476
+ ...model, xValue, scaled: true
477
+ }, "LOESS regression prediction").prediction;
424
478
  }
425
479
 
426
480
  export function fitRegressionGroup(rows, { x, y, group, parameters }) {
@@ -430,7 +484,8 @@ export function fitRegressionGroup(rows, { x, y, group, parameters }) {
430
484
  y,
431
485
  group,
432
486
  confidenceMethod: parameters.confidenceMethod,
433
- level: parameters.level
487
+ level: parameters.level,
488
+ interval: parameters.interval
434
489
  });
435
490
  }
436
491
  if (parameters.method === "polynomial") {
@@ -440,10 +495,12 @@ export function fitRegressionGroup(rows, { x, y, group, parameters }) {
440
495
  group,
441
496
  confidenceMethod: parameters.confidenceMethod,
442
497
  level: parameters.level,
443
- degree: parameters.degree
498
+ degree: parameters.degree,
499
+ interval: parameters.interval
444
500
  });
445
501
  }
446
- return fitLoessGroup(rows, { x, y, group, span: parameters.span });
502
+ return fitLoessGroup(rows, { x, y, group, span: parameters.span,
503
+ robustIterations: parameters.robustIterations });
447
504
  }
448
505
 
449
506
  export function predictRegressionAt(model, xValue, parameters) {
@@ -454,8 +511,8 @@ export function predictRegressionAt(model, xValue, parameters) {
454
511
  ? linearPrediction(model, xValue)
455
512
  : parameters.method === "polynomial"
456
513
  ? polynomial.prediction
457
- : model.fits.find(fit => fit.x === xValue).prediction;
458
- if (parameters.method === "loess") return { prediction };
514
+ : predictLoessAt(model, xValue);
515
+ if (parameters.method === "loess" || parameters.interval === false) return { prediction };
459
516
  const leverage = parameters.method === "linear"
460
517
  ? model.normalizedSxx !== undefined
461
518
  ? 1 / model.count + (