ggaction 0.0.17 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -0
- package/README.md +6 -2
- package/knowledge/action-card.schema.json +1 -1
- package/knowledge/action-cards.json +1 -1
- package/knowledge/intent-taxonomy.json +1 -1
- package/knowledge/mcp-resources.json +1 -1
- package/knowledge/task-resolver.js +1 -1
- package/package.json +3 -3
- package/src/BasicChartProgram.js +1 -1
- package/src/accessibility.js +3 -2
- package/src/actions/boxPlots/create.js +14 -4
- package/src/actions/boxPlots/edit.js +39 -33
- package/src/actions/boxPlots/materialize.js +7 -3
- package/src/actions/boxPlots/options.js +37 -11
- package/src/actions/canvas/actions.js +70 -95
- package/src/actions/canvas/fitting.js +20 -1
- package/src/actions/canvas/index.js +3 -0
- package/src/actions/canvas/plotBorder.js +28 -0
- package/src/actions/charts/area.js +2 -2
- package/src/actions/charts/bar.js +34 -7
- package/src/actions/charts/ecdf.js +4 -18
- package/src/actions/charts/endpoints.js +4 -18
- package/src/actions/charts/heatmap.js +8 -7
- package/src/actions/charts/index.js +2 -0
- package/src/actions/charts/interval-regression.js +2 -2
- package/src/actions/charts/line.js +1 -1
- package/src/actions/charts/pie.js +3 -47
- package/src/actions/charts/radial.js +34 -12
- package/src/actions/charts/scatter.js +16 -2
- package/src/actions/charts/shared.js +2 -2
- package/src/actions/charts/text.js +35 -0
- package/src/actions/composition/actions.js +6 -2
- package/src/actions/data/box.js +2 -1
- package/src/actions/data/derived.js +11 -1
- package/src/actions/data/edit.js +3 -1
- package/src/actions/data/index.js +4 -0
- package/src/actions/data/regression.js +25 -16
- package/src/actions/data/regressionStatistics.js +73 -0
- package/src/actions/data/shared.js +8 -4
- package/src/actions/data/stack.js +3 -1
- package/src/actions/distributions/revision.js +24 -18
- package/src/actions/encodings/appearance.js +37 -29
- package/src/actions/encodings/barWidth.js +36 -11
- package/src/actions/encodings/channels.js +0 -1
- package/src/actions/encodings/color/continuous.js +1 -1
- package/src/actions/encodings/color/index.js +1 -1
- package/src/actions/encodings/color/policy.js +10 -3
- package/src/actions/encodings/density/resolve.js +1 -6
- package/src/actions/encodings/position/policies/arc.js +12 -10
- package/src/actions/encodings/position/policies/area.js +3 -3
- package/src/actions/encodings/position/policies/bar.js +15 -6
- package/src/actions/encodings/position/policies/line.js +17 -6
- package/src/actions/encodings/position/resolve.js +3 -2
- package/src/actions/encodings/ranged.js +17 -0
- package/src/actions/encodings/remove.js +14 -8
- package/src/actions/encodings/seriesLayout.js +1 -0
- package/src/actions/encodings/shared.js +26 -24
- package/src/actions/encodings/text.js +6 -3
- package/src/actions/errorBands/create.js +2 -2
- package/src/actions/errorBands/edit.js +2 -1
- package/src/actions/facets/actions.js +176 -247
- package/src/actions/facets/guides.js +2 -1
- package/src/actions/gradientPlots/resolve.js +2 -17
- package/src/actions/guides/applicability.js +1 -1
- package/src/actions/guides/axes/axes.js +30 -11
- package/src/actions/guides/axes/axis.js +72 -10
- package/src/actions/guides/axes/components.js +13 -13
- package/src/actions/guides/axes/edit.js +36 -24
- package/src/actions/guides/axes/labels.js +155 -71
- package/src/actions/guides/axes/lines.js +27 -24
- package/src/actions/guides/axes/parallel/lifecycle.js +1 -1
- package/src/actions/guides/axes/parallel/policy.js +8 -7
- package/src/actions/guides/axes/remove.js +13 -8
- package/src/actions/guides/axes/tickGroups.js +11 -7
- package/src/actions/guides/axes/ticks.js +36 -24
- package/src/actions/guides/axes/titles.js +107 -69
- package/src/actions/guides/basic.js +2 -0
- package/src/actions/guides/facadeAxes.js +33 -24
- package/src/actions/guides/facadeLegend.js +12 -4
- package/src/actions/guides/grids/grid.js +8 -2
- package/src/actions/guides/grids/resolve.js +102 -58
- package/src/actions/guides/legends/blocks.js +46 -20
- package/src/actions/guides/legends/categorical/actions.js +95 -19
- package/src/actions/guides/legends/categorical/components.js +15 -18
- package/src/actions/guides/legends/categorical/index.js +8 -0
- package/src/actions/guides/legends/categorical/layout.js +45 -20
- package/src/actions/guides/legends/categorical/options.js +39 -22
- package/src/actions/guides/legends/categorical/recipes.js +7 -3
- package/src/actions/guides/legends/categorical/resolve.js +11 -4
- package/src/actions/guides/legends/categorical/symbols.js +38 -18
- package/src/actions/guides/legends/categorical/validation.js +1 -6
- package/src/actions/guides/legends/colorSize.js +110 -0
- package/src/actions/guides/legends/combine.js +37 -0
- package/src/actions/guides/legends/continuous/colorSymbols.js +1 -0
- package/src/actions/guides/legends/continuous/common.js +1 -447
- package/src/actions/guides/legends/continuous/gradient.js +73 -46
- package/src/actions/guides/legends/continuous/interval.js +4 -4
- package/src/actions/guides/legends/continuous/opacity.js +33 -21
- package/src/actions/guides/legends/creation.js +2 -0
- package/src/actions/guides/legends/edit.js +1 -604
- package/src/actions/guides/legends/focused.js +8 -7
- package/src/actions/guides/legends/group.js +1 -0
- package/src/actions/guides/legends/lane.js +1 -516
- package/src/actions/guides/legends/lifecycle.js +2 -1
- package/src/actions/guides/legends/remove.js +9 -3
- package/src/actions/guides/legends/sampling.js +1 -1
- package/src/actions/guides/legends/size.js +65 -47
- package/src/actions/guides/legends/strokeWidth.js +13 -29
- package/src/actions/guides/legends/target.js +2 -79
- package/src/actions/guides/legends/transition.js +44 -11
- package/src/actions/guides/polar/axes/labels.js +4 -2
- package/src/actions/guides/polar/axes/titles.js +7 -5
- package/src/actions/guides/polar/resolve.js +6 -14
- package/src/actions/guides/tickValues.js +8 -5
- package/src/actions/index.js +16 -16
- package/src/actions/marks/arc/actions.js +3 -10
- package/src/actions/marks/area/actions.js +8 -5
- package/src/actions/marks/area/materialize.js +8 -9
- package/src/actions/marks/bar/create.js +10 -4
- package/src/actions/marks/bar/edit.js +29 -14
- package/src/actions/marks/bar/materialize.js +15 -7
- package/src/actions/marks/line/actions.js +11 -4
- package/src/actions/marks/line/materialize.js +9 -5
- package/src/actions/marks/point/edit.js +1 -1
- package/src/actions/marks/point/materialize.js +5 -1
- package/src/actions/marks/rect/actions.js +3 -3
- package/src/actions/marks/remove.js +12 -10
- package/src/actions/marks/rule/actions.js +7 -4
- package/src/actions/marks/text/actions.js +17 -5
- package/src/actions/marks/text/layout.js +3 -0
- package/src/actions/primitives/createGraphics.js +3 -2
- package/src/actions/primitives/editGraphics.js +15 -11
- package/src/actions/primitives/graphicProperties.js +1 -6
- package/src/actions/primitives/semanticAction.js +27 -1
- package/src/actions/primitives/semanticValidation/guide.js +11 -1
- package/src/actions/primitives/semanticValidation/layer.js +8 -0
- package/src/actions/regression/create.js +1 -1
- package/src/actions/regression/edit.js +18 -6
- package/src/actions/regression/resolve.js +2 -6
- package/src/actions/scales/channels.js +9 -4
- package/src/actions/scales/consumers/families.js +16 -5
- package/src/actions/scales/create.js +10 -5
- package/src/actions/scales/definitions.js +1 -425
- package/src/actions/scales/edit.js +3 -3
- package/src/actions/scales/editPolicy.js +12 -3
- package/src/actions/scales/materialize.js +1 -1
- package/src/actions/scales/patch.js +1 -1
- package/src/actions/scales/preview.js +21 -1
- package/src/actions/scales/quantitativeColor.js +8 -3
- package/src/actions/selection/actions.js +1 -13
- package/src/actions/theme/reconcile.js +126 -45
- package/src/actions/theme/state.js +12 -0
- package/src/actions/titles/actions.js +5 -18
- package/src/actions/titles/resolve.js +41 -19
- package/src/actions/violinPlots/edit.js +3 -20
- package/src/core/compositionState.js +6 -3
- package/src/core/font.js +9 -2
- package/src/core/graphicProperties.js +1 -0
- package/src/core/optionRequirements.js +1 -0
- package/src/core/programState.js +4 -0
- package/src/core/resourceReferences.js +5 -2
- package/src/core/textMetricProfile.js +3 -2
- package/src/core/textMetrics.js +41 -4
- package/src/core/validation.js +35 -0
- package/src/core/vocabulary.js +7 -0
- package/src/grammar/aggregate.js +4 -18
- package/src/grammar/arcs.js +1 -386
- package/src/grammar/areaPaint.js +10 -0
- package/src/grammar/areaSeries.js +18 -10
- package/src/grammar/axisTextPlacement.js +1 -0
- package/src/grammar/bars/geometry.js +30 -3
- package/src/grammar/bars/policy.js +12 -1
- package/src/grammar/bin.js +2 -12
- package/src/grammar/bin2d.js +1 -6
- package/src/grammar/boxPlot.js +56 -9
- package/src/grammar/categoryOrder.js +1 -6
- package/src/grammar/colorSizeLegend.js +39 -0
- package/src/grammar/complete.js +15 -19
- package/src/grammar/computed.js +115 -26
- package/src/grammar/curveCommands.js +29 -16
- package/src/grammar/datasetSchema.js +13 -9
- package/src/grammar/displayLabels.js +36 -0
- package/src/grammar/ecdf.js +2 -9
- package/src/grammar/facets/dependencies.js +6 -2
- package/src/grammar/facets/guides.js +58 -30
- package/src/grammar/facets/index.js +2 -5
- package/src/grammar/facets/legendIntent.js +65 -0
- package/src/grammar/fold.js +2 -12
- package/src/grammar/gradientProfile.js +1 -6
- package/src/grammar/guideDash.js +1 -0
- package/src/grammar/impute.js +21 -48
- package/src/grammar/interval.js +2 -9
- package/src/grammar/legendBlocks.js +1 -0
- package/src/grammar/legendBorder.js +9 -0
- package/src/grammar/legendClip.js +19 -0
- package/src/grammar/legendCombinations.js +45 -0
- package/src/grammar/legendGradient.js +27 -0
- package/src/grammar/legendGroups.js +1 -0
- package/src/grammar/legendOpacity.js +6 -0
- package/src/grammar/legendTitleStyle.js +22 -0
- package/src/grammar/lineSeries.js +32 -17
- package/src/grammar/markLabels.js +5 -2
- package/src/grammar/normalize.js +4 -54
- package/src/grammar/numeric.js +1 -290
- package/src/grammar/parallelCoordinates.js +1 -6
- package/src/grammar/parseTime.js +41 -0
- package/src/grammar/pathCommands.js +11 -4
- package/src/grammar/pointShapes.js +16 -2
- package/src/grammar/polar.js +1 -184
- package/src/grammar/positionCompatibility.js +3 -3
- package/src/grammar/regression/derive.js +16 -2
- package/src/grammar/regression/models.js +142 -54
- package/src/grammar/regression/parameters.js +23 -4
- package/src/grammar/regression/statistics.js +64 -0
- package/src/grammar/roundedRect.js +42 -54
- package/src/grammar/scales/appearance.js +1 -206
- package/src/grammar/scales/color.js +1 -382
- package/src/grammar/scales/colorConsumers.js +5 -2
- package/src/grammar/scales/definition.js +1 -197
- package/src/grammar/scales/discretized.js +1 -195
- package/src/grammar/scales/size.js +1 -456
- package/src/grammar/scales/temporal.js +2 -13
- package/src/grammar/scales/transformed.js +1 -383
- package/src/grammar/scales/types.js +1 -1
- package/src/grammar/scales/validation.js +1 -66
- package/src/grammar/schemas/concreteGraphic.js +14 -0
- package/src/grammar/schemas/graphic.js +6 -0
- package/src/grammar/schemas/graphicBounds.js +31 -7
- package/src/grammar/schemas/semanticPath.js +19 -3
- package/src/grammar/stack.js +2 -10
- package/src/grammar/statisticalReference.js +1 -6
- package/src/grammar/summary.js +4 -27
- package/src/grammar/text.js +19 -0
- package/src/grammar/timeUnit.js +22 -11
- package/src/grammar/transformKeys.js +47 -0
- package/src/grammar/transformTopology.js +1 -0
- package/src/grammar/transforms.js +15 -2
- package/src/grammar/valueFormat.js +17 -5
- package/src/grammar/weightedStatistics.js +1 -6
- package/src/grammar/window.js +4 -58
- package/src/inspectionDescriptors.js +1 -1
- package/src/layout/canvas.js +1 -110
- package/src/layout/facets.js +1 -244
- package/src/layout/guideCollisions.js +9 -1
- package/src/layout/labels.js +1 -0
- package/src/layout/legend.js +13 -0
- package/src/layout/legendGroup.js +1 -0
- package/src/layout/legendItems.js +59 -15
- package/src/layout/legendLane.js +1 -441
- package/src/layout/legendText.js +1 -0
- package/src/layout/title.js +35 -13
- package/src/materialization/bars/aggregate.js +11 -5
- package/src/materialization/bars/histogram.js +4 -1
- package/src/materialization/bars/ranged.js +19 -6
- package/src/materialization/bars/resolve.js +22 -1
- package/src/materialization/compositionSnapshot.js +2 -7
- package/src/materialization/coordinateBounds.js +21 -0
- package/src/materialization/dependencies.js +4 -44
- package/src/materialization/facetGuides/edit.js +30 -0
- package/src/materialization/facetGuides/layout.js +1 -0
- package/src/materialization/facetGuides/owner.js +125 -0
- package/src/materialization/facetGuides/ownerLayout.js +71 -0
- package/src/materialization/facetGuides/placement.js +71 -14
- package/src/materialization/facetGuides/preparation.js +22 -7
- package/src/materialization/facetHeaders.js +1 -347
- package/src/materialization/facets.js +1 -185
- package/src/materialization/graphicHierarchy.js +5 -1
- package/src/materialization/graphicTranslation.js +73 -0
- package/src/materialization/guides/layout.js +1 -57
- package/src/materialization/guides/resources.js +1 -123
- package/src/materialization/layout.js +53 -0
- package/src/materialization/legendBorder.js +28 -0
- package/src/materialization/legendClip.js +76 -0
- package/src/materialization/legendGroups.js +1 -0
- package/src/materialization/legendText.js +1 -0
- package/src/materialization/legends.js +2 -2
- package/src/materialization/marks/capabilities.js +5 -6
- package/src/materialization/rect.js +1 -1
- package/src/materialization/scaleGuideDependencies.js +14 -15
- package/src/materialization/scales/map.js +6 -0
- package/src/materialization/scales/policies/arc.js +8 -7
- package/src/materialization/scales/policies/bar.js +19 -7
- package/src/materialization/scales/policies/series.js +2 -1
- package/src/materialization/scales/resolve.js +7 -3
- package/src/materialization/selection/items/arc.js +1 -1
- package/src/materialization/selection/items/bar.js +7 -6
- package/src/materialization/selection/items/path.js +7 -6
- package/src/materialization/selection/items/rect.js +2 -2
- package/src/materialization/text.js +13 -4
- package/src/materialization/textLines.js +15 -0
- package/src/materialization/typography.js +10 -2
- package/src/persistence/codec.js +37 -6
- package/src/persistence/validation.js +220 -0
- package/src/persistence.js +19 -6
- package/src/renderers/canvas/circle.js +6 -3
- package/src/renderers/canvas/fill.js +7 -1
- package/src/renderers/canvas/index.js +22 -11
- package/src/renderers/canvas/isolated.js +166 -0
- package/src/renderers/canvas/limits.js +3 -0
- package/src/renderers/canvas/line.js +1 -0
- package/src/renderers/canvas/path.js +1 -1
- package/src/renderers/canvas/rect.js +11 -8
- package/src/renderers/canvas/stroke.js +2 -1
- package/src/renderers/canvas/text.js +5 -3
- package/src/renderers/pdf.js +2 -2
- package/src/renderers/png.js +2 -2
- package/src/renderers/svg.js +14 -4
- package/src/selectors/axes.js +1 -0
- package/src/selectors/graphicCapacity.js +38 -0
- package/src/selectors/layers.js +19 -0
- package/src/theme/defaults.js +8 -2
- package/src/version.js +1 -1
- package/types/basic.d.ts +3 -0
- package/types/index.d.ts +12 -0
- package/types/program.d.ts +536 -129
|
@@ -202,10 +202,10 @@ function validateFacetSpec(compositionSpec) {
|
|
|
202
202
|
!isPlainObject(guides) ||
|
|
203
203
|
Object.keys(guides).some(key => !["axes", "legend"].includes(key)) ||
|
|
204
204
|
!["each", "outer"].includes(guides.axes) ||
|
|
205
|
-
![false, "shared"].includes(guides.legend)
|
|
205
|
+
(![false, "shared"].includes(guides.legend) && !isPlainObject(guides.legend))
|
|
206
206
|
) {
|
|
207
207
|
throw new Error(
|
|
208
|
-
'compositionSpec.facet.guides requires axes "each" or "outer" and legend false
|
|
208
|
+
'compositionSpec.facet.guides requires axes "each" or "outer" and legend false, "shared", or parent legend options.'
|
|
209
209
|
);
|
|
210
210
|
}
|
|
211
211
|
}
|
|
@@ -272,7 +272,7 @@ export function ownCompositionSpec(compositionSpec, children) {
|
|
|
272
272
|
}
|
|
273
273
|
const allowed = [
|
|
274
274
|
"id", "type", "direction", "children", "columns", "gap", "align",
|
|
275
|
-
"padding", "facet"
|
|
275
|
+
"padding", "facet", "spacing"
|
|
276
276
|
];
|
|
277
277
|
const unknown = Object.keys(compositionSpec).find(key => !allowed.includes(key));
|
|
278
278
|
if (unknown !== undefined) {
|
|
@@ -282,6 +282,9 @@ export function ownCompositionSpec(compositionSpec, children) {
|
|
|
282
282
|
throw new TypeError("compositionSpec.id must be a non-empty string.");
|
|
283
283
|
}
|
|
284
284
|
const facet = compositionSpec.type === "facet";
|
|
285
|
+
if (compositionSpec.spacing !== undefined && (!facet || !["plot", "canvas"].includes(compositionSpec.spacing))) {
|
|
286
|
+
throw new Error("compositionSpec.spacing requires a facet and canvas or plot spacing.");
|
|
287
|
+
}
|
|
285
288
|
if (compositionSpec.type !== undefined && !facet) {
|
|
286
289
|
throw new Error(`Unknown compositionSpec type "${compositionSpec.type}".`);
|
|
287
290
|
}
|
package/src/core/font.js
CHANGED
|
@@ -5,8 +5,15 @@ export function normalizeRendererFontWeight(fontWeight) {
|
|
|
5
5
|
return Math.min(900, Math.max(100, Math.round(fontWeight / 100) * 100));
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
export function
|
|
9
|
-
|
|
8
|
+
export function validateFontStyle(value, label = "Text") {
|
|
9
|
+
if (!["normal", "italic"].includes(value)) {
|
|
10
|
+
throw new Error(`${label} fontStyle must be normal or italic.`);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function textMetricKey({ text, fontFamily, fontSize, fontWeight, fontStyle = "normal" }) {
|
|
16
|
+
return JSON.stringify([text, fontFamily, fontSize, fontWeight, fontStyle]);
|
|
10
17
|
}
|
|
11
18
|
|
|
12
19
|
export function textMetricFontWeight(value = "normal") {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function n(e,t,i){for(const[r,c]of Object.entries(i))e=e.editGraphics({target:t,property:r,value:c});return e}export{n as editGraphicProperties};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The method's value-producing contract is shared by runtime validation and
|
|
2
2
|
// authoring knowledge; editing can inherit these values from stored state.
|
|
3
3
|
export const IMPUTE_REQUIRED_OPTIONS = Object.freeze({
|
|
4
|
+
mean: Object.freeze([]),
|
|
4
5
|
constant: Object.freeze(["value"]),
|
|
5
6
|
forward: Object.freeze(["sortBy"]),
|
|
6
7
|
backward: Object.freeze(["sortBy"]),
|
package/src/core/programState.js
CHANGED
|
@@ -44,6 +44,10 @@ export function releaseNamedResourceOwnership(program, { kind, id, dataOwner })
|
|
|
44
44
|
materializationConfigs,
|
|
45
45
|
["calculations", "datasets", dataOwner?.current ?? id]
|
|
46
46
|
).value;
|
|
47
|
+
materializationConfigs = removeMaterializationConfig(
|
|
48
|
+
materializationConfigs,
|
|
49
|
+
["regressionFits", dataOwner?.current ?? id]
|
|
50
|
+
).value;
|
|
47
51
|
const context = program.context.currentData === id
|
|
48
52
|
? freezeOwned({ ...program.context, currentData: undefined })
|
|
49
53
|
: program.context;
|
|
@@ -97,7 +97,7 @@ export function collectSemanticScaleReferences(program) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
collectKnownKeys(program.semanticSpec.guides, [], (key, value, path) => {
|
|
100
|
-
if (
|
|
100
|
+
if (["scale", "colorScale", "sizeScale"].includes(key)) {
|
|
101
101
|
append(references, reference(
|
|
102
102
|
"scale", value, "semanticGuide", "guides", path
|
|
103
103
|
));
|
|
@@ -199,13 +199,16 @@ export function collectMarkConfigReferences(program) {
|
|
|
199
199
|
|
|
200
200
|
export function collectGuideConfigReferences(program) {
|
|
201
201
|
const references = [];
|
|
202
|
+
for (const target of Object.keys(program.guideConfigs?.legendGroups ?? {})) {
|
|
203
|
+
append(references, reference("mark", target, "legend", target, ["legendGroups", target]));
|
|
204
|
+
}
|
|
202
205
|
for (const [family, configs] of Object.entries(program.guideConfigs ?? {})) {
|
|
203
206
|
collectKnownKeys(configs, [], (key, value, path, parentPath) => {
|
|
204
207
|
const ownerId = parentPath.length === 0 ? family : parentPath.join(".");
|
|
205
208
|
const relativePath = parentPath.length === 0
|
|
206
209
|
? path
|
|
207
210
|
: path.slice(parentPath.length);
|
|
208
|
-
if (
|
|
211
|
+
if (["scale", "colorScale", "sizeScale"].includes(key)) {
|
|
209
212
|
append(references, reference(
|
|
210
213
|
"scale", value, family, ownerId, relativePath
|
|
211
214
|
));
|
|
@@ -2,7 +2,7 @@ import { cloneAndFreeze } from "./immutable.js";
|
|
|
2
2
|
import { validateUserId } from "./identifiers.js";
|
|
3
3
|
import { validateOptionObject, validatePositiveFinite, validateNonNegativeFinite, validateNonEmptyString } from "./validation.js";
|
|
4
4
|
|
|
5
|
-
import { textMetricKey } from "./font.js";
|
|
5
|
+
import { textMetricKey, validateFontStyle } from "./font.js";
|
|
6
6
|
|
|
7
7
|
export function normalizeTextMetricProfile(profile) {
|
|
8
8
|
validateOptionObject(profile, ["schemaVersion", "id", "measurements"], "Text metrics profile");
|
|
@@ -11,7 +11,7 @@ export function normalizeTextMetricProfile(profile) {
|
|
|
11
11
|
if (!Array.isArray(profile.measurements)) throw new TypeError("Text metrics measurements must be an array.");
|
|
12
12
|
const seen = new Set();
|
|
13
13
|
for (const measurement of profile.measurements) {
|
|
14
|
-
validateOptionObject(measurement, ["text", "fontFamily", "fontSize", "fontWeight", "width"], "Text measurement");
|
|
14
|
+
validateOptionObject(measurement, ["text", "fontFamily", "fontSize", "fontWeight", "fontStyle", "width"], "Text measurement");
|
|
15
15
|
if (typeof measurement.text !== "string") throw new TypeError("Text measurement text must be a string.");
|
|
16
16
|
validateNonEmptyString(measurement.fontFamily, "Text measurement fontFamily");
|
|
17
17
|
validatePositiveFinite(measurement.fontSize, "Text measurement fontSize");
|
|
@@ -20,6 +20,7 @@ export function normalizeTextMetricProfile(profile) {
|
|
|
20
20
|
measurement.fontWeight > 900 || measurement.fontWeight % 100 !== 0) {
|
|
21
21
|
throw new RangeError("Text measurement fontWeight must be 100–900 in steps of 100.");
|
|
22
22
|
}
|
|
23
|
+
if (measurement.fontStyle !== undefined) validateFontStyle(measurement.fontStyle, "Text measurement");
|
|
23
24
|
const key = textMetricKey(measurement);
|
|
24
25
|
if (seen.has(key)) throw new Error("Duplicate text measurement combination.");
|
|
25
26
|
seen.add(key);
|
package/src/core/textMetrics.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateGeneratedItemLimit } from "./validation.js";
|
|
1
2
|
import { DEFAULT_FONT_FAMILY, textMetricFontWeight, textMetricKey } from "./font.js";
|
|
2
3
|
|
|
3
4
|
import { isOwned } from "./immutable.js";
|
|
@@ -13,7 +14,7 @@ function measuredWidth(profile, text, style) {
|
|
|
13
14
|
if (isOwned(profile)) measurementIndexes.set(profile, index);
|
|
14
15
|
}
|
|
15
16
|
return index.get(textMetricKey({ text, fontFamily: style.fontFamily ?? DEFAULT_FONT_FAMILY,
|
|
16
|
-
fontSize: style.fontSize, fontWeight: textMetricFontWeight(style.fontWeight) }));
|
|
17
|
+
fontSize: style.fontSize, fontWeight: textMetricFontWeight(style.fontWeight), fontStyle: style.fontStyle }));
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
function codePointWidth(codePoint) {
|
|
@@ -43,7 +44,7 @@ export function textBoundsFitCanvas(bounds, canvas) {
|
|
|
43
44
|
|
|
44
45
|
export function measureTextWidth(
|
|
45
46
|
text,
|
|
46
|
-
{ fontSize, fontFamily, fontWeight } = {},
|
|
47
|
+
{ fontSize, fontFamily, fontWeight, fontStyle } = {},
|
|
47
48
|
profile
|
|
48
49
|
) {
|
|
49
50
|
if (typeof text !== "string") {
|
|
@@ -52,7 +53,7 @@ export function measureTextWidth(
|
|
|
52
53
|
if (!Number.isFinite(fontSize) || fontSize <= 0) {
|
|
53
54
|
throw new RangeError("Text measurement requires a positive fontSize.");
|
|
54
55
|
}
|
|
55
|
-
const measured = measuredWidth(profile, text, { fontSize, fontFamily, fontWeight });
|
|
56
|
+
const measured = measuredWidth(profile, text, { fontSize, fontFamily, fontWeight, fontStyle });
|
|
56
57
|
if (measured !== undefined) return measured;
|
|
57
58
|
let joined = false;
|
|
58
59
|
let width = 0;
|
|
@@ -91,9 +92,11 @@ export function resolveTextBounds({
|
|
|
91
92
|
x,
|
|
92
93
|
y,
|
|
93
94
|
text,
|
|
95
|
+
lines,
|
|
94
96
|
fontSize,
|
|
95
97
|
fontFamily,
|
|
96
98
|
fontWeight,
|
|
99
|
+
fontStyle,
|
|
97
100
|
textAlign = "left",
|
|
98
101
|
textBaseline = "alphabetic",
|
|
99
102
|
rotation = 0
|
|
@@ -101,7 +104,19 @@ export function resolveTextBounds({
|
|
|
101
104
|
if (![x, y, rotation].every(Number.isFinite)) {
|
|
102
105
|
throw new TypeError("Text bounds require finite x, y, and rotation values.");
|
|
103
106
|
}
|
|
104
|
-
|
|
107
|
+
if (lines !== undefined) {
|
|
108
|
+
const cosine = Math.cos(rotation), sine = Math.sin(rotation);
|
|
109
|
+
const bounds = lines.map(line => resolveTextBounds({
|
|
110
|
+
x: x + line.x * cosine - line.y * sine, y: y + line.x * sine + line.y * cosine,
|
|
111
|
+
text: line.text, fontSize, fontFamily, fontWeight, fontStyle, textAlign, textBaseline, rotation
|
|
112
|
+
}, profile));
|
|
113
|
+
const union = bounds.reduce((result, bound) => ({
|
|
114
|
+
left: Math.min(result.left, bound.left), right: Math.max(result.right, bound.right),
|
|
115
|
+
top: Math.min(result.top, bound.top), bottom: Math.max(result.bottom, bound.bottom)
|
|
116
|
+
}), { left: Infinity, right: -Infinity, top: Infinity, bottom: -Infinity });
|
|
117
|
+
return Object.freeze(requireFiniteBounds(union, "Text"));
|
|
118
|
+
}
|
|
119
|
+
const width = measureTextWidth(text, { fontSize, fontFamily, fontWeight, fontStyle }, profile);
|
|
105
120
|
const [left, right] = textAlign === "center"
|
|
106
121
|
? [-width / 2, width / 2]
|
|
107
122
|
: ["right", "end"].includes(textAlign) ? [-width, 0] : [0, width];
|
|
@@ -126,3 +141,25 @@ export function resolveTextBounds({
|
|
|
126
141
|
};
|
|
127
142
|
return Object.freeze(requireFiniteBounds(bounds, "Text"));
|
|
128
143
|
}
|
|
144
|
+
|
|
145
|
+
export function unionBounds(bounds) {
|
|
146
|
+
return bounds.reduce((union, item) => ({
|
|
147
|
+
left: Math.min(union.left, item.left),
|
|
148
|
+
right: Math.max(union.right, item.right),
|
|
149
|
+
top: Math.min(union.top, item.top),
|
|
150
|
+
bottom: Math.max(union.bottom, item.bottom)
|
|
151
|
+
}), { left: Infinity, right: -Infinity, top: Infinity, bottom: -Infinity });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Resolve explicit hard breaks once for text marks and guide layout.
|
|
155
|
+
export function resolveTextLines(text, config) {
|
|
156
|
+
const parts = text.split(/\r\n|\r|\n/u);
|
|
157
|
+
if (parts.length === 1 && config.blockAlign !== "middle") return undefined;
|
|
158
|
+
validateGeneratedItemLimit(parts.length, "Text line count");
|
|
159
|
+
const lineHeight = config.lineHeight ?? config.fontSize * 1.2;
|
|
160
|
+
const bounds = resolveTextBounds({ x: 0, y: 0, text: "", fontSize: config.fontSize,
|
|
161
|
+
textBaseline: config.baseline, textAlign: config.align });
|
|
162
|
+
const offset = config.blockAlign === "middle"
|
|
163
|
+
? -(bounds.top + bounds.bottom + (parts.length - 1) * lineHeight) / 2 : 0;
|
|
164
|
+
return parts.map((text, index) => ({ text, x: 0, y: offset + index * lineHeight }));
|
|
165
|
+
}
|
package/src/core/validation.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { annotateError } from "./diagnostics.js";
|
|
2
2
|
import { isPlainObject } from "./immutable.js";
|
|
3
|
+
import { getMarkGraphicTypes } from "./vocabulary.js";
|
|
3
4
|
|
|
4
5
|
export const MAX_GENERATED_ITEMS = 10_000;
|
|
5
6
|
export const MAX_WORK_ITEMS = 10_000_000;
|
|
@@ -30,6 +31,14 @@ export function validateWorkLimit(value, label) {
|
|
|
30
31
|
return validateGeneratedItemLimit(value, label, MAX_WORK_ITEMS);
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
export function validateGraphicItemLimit(value, capacity) {
|
|
35
|
+
const point = capacity.pointOwned && getMarkGraphicTypes("point").includes(capacity.graphicType);
|
|
36
|
+
const label = point ? "Point mark item count" : capacity.label;
|
|
37
|
+
const maximum = point ? 50_000 : MAX_GENERATED_ITEMS;
|
|
38
|
+
if (point) validateGeneratedItemLimit(capacity.rawCount, label, maximum);
|
|
39
|
+
return validateGeneratedItemLimit(value, label, maximum);
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
export function validateKeys(value, supported, label) {
|
|
34
43
|
for (const key of Object.keys(value)) {
|
|
35
44
|
if (!supported.includes(key)) {
|
|
@@ -98,3 +107,29 @@ export function validateNonNegativeFinite(value, label) {
|
|
|
98
107
|
}
|
|
99
108
|
return value;
|
|
100
109
|
}
|
|
110
|
+
|
|
111
|
+
export function requireStringValue(value, label) {
|
|
112
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
113
|
+
throw new TypeError(`${label} must be a non-empty string.`);
|
|
114
|
+
}
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function rejectUnknownProperties(value, supported, label) {
|
|
119
|
+
const unknown = Object.keys(value).find(key => !supported.includes(key));
|
|
120
|
+
if (unknown !== undefined) throw new Error(`Unknown ${label} property "${unknown}".`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function validateFontWeight(value, label, numberType = "number") {
|
|
124
|
+
if (typeof value !== "string" && !Number.isFinite(value)) {
|
|
125
|
+
throw new TypeError(`${label} must be a string or ${numberType}.`);
|
|
126
|
+
}
|
|
127
|
+
return value;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function validateTextStyle(config, label) {
|
|
131
|
+
validateNonEmptyString(config.color, `${label} color`);
|
|
132
|
+
validatePositiveFinite(config.fontSize, `${label} fontSize`);
|
|
133
|
+
validateNonEmptyString(config.fontFamily, `${label} fontFamily`);
|
|
134
|
+
validateFontWeight(config.fontWeight, `${label} fontWeight`);
|
|
135
|
+
}
|
package/src/core/vocabulary.js
CHANGED
|
@@ -220,6 +220,7 @@ export const LEGEND_CHANNELS = Object.freeze([
|
|
|
220
220
|
]);
|
|
221
221
|
|
|
222
222
|
export const LEGEND_CONFIG_KINDS = Object.freeze([
|
|
223
|
+
"colorSize",
|
|
223
224
|
"series",
|
|
224
225
|
"color",
|
|
225
226
|
"stroke",
|
|
@@ -235,3 +236,9 @@ export const LEGEND_CONFIG_KINDS = Object.freeze([
|
|
|
235
236
|
export function includesVocabulary(vocabulary, value) {
|
|
236
237
|
return vocabulary.includes(value);
|
|
237
238
|
}
|
|
239
|
+
|
|
240
|
+
export const PLOT_LEGEND_POSITIONS = Object.freeze(["top-left", "top-right", "bottom-left", "bottom-right"]);
|
|
241
|
+
export const LEGEND_POSITIONS = Object.freeze(["left", "right", "top", "bottom", ...PLOT_LEGEND_POSITIONS]);
|
|
242
|
+
export const LEGEND_BLOCK_LAYOUTS = Object.freeze(["row", "column", "wrap"]);
|
|
243
|
+
|
|
244
|
+
export const AXIS_RESOURCE_CHANNELS = Object.freeze(["x", "y", "theta", "radius", "parallel"]);
|
package/src/grammar/aggregate.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { requireStringValue as nonEmptyString } from "../core/validation.js";
|
|
2
|
+
import { isGroupValue as isNominal } from "./transformKeys.js";
|
|
1
3
|
import { isPlainObject } from "../core/immutable.js";
|
|
2
4
|
import {
|
|
3
|
-
|
|
5
|
+
quantileSorted,
|
|
4
6
|
numericExtent,
|
|
5
7
|
requireFiniteResult,
|
|
6
8
|
stableFiniteDeviation,
|
|
@@ -25,11 +27,6 @@ const PARAMETERIZED_OPERATIONS = [
|
|
|
25
27
|
"quantile", "first", "last", "ciLower", "ciUpper"
|
|
26
28
|
];
|
|
27
29
|
|
|
28
|
-
function nonEmptyString(value, label) {
|
|
29
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
30
|
-
throw new TypeError(`${label} must be a non-empty string.`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
30
|
|
|
34
31
|
export function isScalarAggregate(value) {
|
|
35
32
|
return typeof value === "string" && SCALAR_OPERATIONS.has(value);
|
|
@@ -137,10 +134,6 @@ function isMissing(value) {
|
|
|
137
134
|
(typeof value === "number" && Number.isNaN(value));
|
|
138
135
|
}
|
|
139
136
|
|
|
140
|
-
function isNominal(value) {
|
|
141
|
-
return typeof value === "string" || typeof value === "boolean" ||
|
|
142
|
-
(typeof value === "number" && Number.isFinite(value));
|
|
143
|
-
}
|
|
144
137
|
|
|
145
138
|
export function validateAggregateFieldValues(rows, field, fieldType) {
|
|
146
139
|
if (!Array.isArray(rows)) {
|
|
@@ -165,14 +158,7 @@ function finiteValues(values) {
|
|
|
165
158
|
|
|
166
159
|
function quantile(values, probability) {
|
|
167
160
|
const ordered = [...values].sort((left, right) => left - right);
|
|
168
|
-
|
|
169
|
-
const lower = Math.floor(position);
|
|
170
|
-
const upper = Math.ceil(position);
|
|
171
|
-
return interpolateNumber(
|
|
172
|
-
ordered[lower],
|
|
173
|
-
ordered[upper],
|
|
174
|
-
position - lower
|
|
175
|
-
);
|
|
161
|
+
return quantileSorted(ordered, probability);
|
|
176
162
|
}
|
|
177
163
|
|
|
178
164
|
function comparableOrderKey(value) {
|