ggaction 0.0.1
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 +27 -0
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/package.json +84 -0
- package/src/AGENTS.md +119 -0
- package/src/ChartProgram.js +10 -0
- package/src/actions/boxPlots/components.js +88 -0
- package/src/actions/boxPlots/create.js +84 -0
- package/src/actions/boxPlots/index.js +10 -0
- package/src/actions/boxPlots/materialize.js +183 -0
- package/src/actions/boxPlots/options.js +147 -0
- package/src/actions/boxPlots/resolve.js +38 -0
- package/src/actions/canvas/actions.js +144 -0
- package/src/actions/canvas/index.js +6 -0
- package/src/actions/coordinates/actions.js +85 -0
- package/src/actions/coordinates/index.js +5 -0
- package/src/actions/data/box.js +40 -0
- package/src/actions/data/create.js +33 -0
- package/src/actions/data/density.js +62 -0
- package/src/actions/data/derived.js +52 -0
- package/src/actions/data/filter.js +168 -0
- package/src/actions/data/index.js +32 -0
- package/src/actions/data/interval.js +69 -0
- package/src/actions/data/regression.js +71 -0
- package/src/actions/data/shared.js +25 -0
- package/src/actions/encodings/appearance.js +243 -0
- package/src/actions/encodings/barWidth.js +60 -0
- package/src/actions/encodings/color.js +329 -0
- package/src/actions/encodings/density.js +225 -0
- package/src/actions/encodings/histogram.js +50 -0
- package/src/actions/encodings/index.js +23 -0
- package/src/actions/encodings/offset.js +104 -0
- package/src/actions/encodings/position/policies/area.js +24 -0
- package/src/actions/encodings/position/policies/bar.js +110 -0
- package/src/actions/encodings/position/policies/common.js +17 -0
- package/src/actions/encodings/position/policies/index.js +25 -0
- package/src/actions/encodings/position/policies/line.js +74 -0
- package/src/actions/encodings/position/policies/point.js +19 -0
- package/src/actions/encodings/position/policies/rule.js +17 -0
- package/src/actions/encodings/position/resolve.js +170 -0
- package/src/actions/encodings/position.js +176 -0
- package/src/actions/encodings/ranged.js +276 -0
- package/src/actions/encodings/ruleAppearance.js +47 -0
- package/src/actions/encodings/shared.js +164 -0
- package/src/actions/encodings/strokeDash.js +145 -0
- package/src/actions/errorBands/components.js +81 -0
- package/src/actions/errorBands/create.js +159 -0
- package/src/actions/errorBands/index.js +7 -0
- package/src/actions/errorBands/resolve.js +28 -0
- package/src/actions/errorBars/components.js +62 -0
- package/src/actions/errorBars/create.js +144 -0
- package/src/actions/errorBars/index.js +7 -0
- package/src/actions/errorBars/resolve.js +25 -0
- package/src/actions/guides/axes/axes.js +204 -0
- package/src/actions/guides/axes/axis.js +131 -0
- package/src/actions/guides/axes/index.js +17 -0
- package/src/actions/guides/axes/labels.js +231 -0
- package/src/actions/guides/axes/lines.js +149 -0
- package/src/actions/guides/axes/policy.js +155 -0
- package/src/actions/guides/axes/tickGroups.js +149 -0
- package/src/actions/guides/axes/ticks.js +179 -0
- package/src/actions/guides/axes/titles.js +264 -0
- package/src/actions/guides/grids/grid.js +228 -0
- package/src/actions/guides/grids/index.js +1 -0
- package/src/actions/guides/grids/resolve.js +258 -0
- package/src/actions/guides/guides.js +138 -0
- package/src/actions/guides/index.js +11 -0
- package/src/actions/guides/legends/categorical/actions.js +329 -0
- package/src/actions/guides/legends/categorical/components.js +140 -0
- package/src/actions/guides/legends/categorical/index.js +49 -0
- package/src/actions/guides/legends/categorical/layout.js +445 -0
- package/src/actions/guides/legends/categorical/options.js +188 -0
- package/src/actions/guides/legends/categorical/recipes.js +120 -0
- package/src/actions/guides/legends/categorical/resolve.js +131 -0
- package/src/actions/guides/legends/categorical/symbols.js +280 -0
- package/src/actions/guides/legends/categorical/validation.js +27 -0
- package/src/actions/guides/legends/continuous/common.js +273 -0
- package/src/actions/guides/legends/continuous/gradient.js +363 -0
- package/src/actions/guides/legends/continuous/index.js +25 -0
- package/src/actions/guides/legends/continuous/interval.js +234 -0
- package/src/actions/guides/legends/continuous/opacity.js +382 -0
- package/src/actions/guides/legends/edit.js +314 -0
- package/src/actions/guides/legends/index.js +9 -0
- package/src/actions/guides/legends/size.js +220 -0
- package/src/actions/guides/tickValues.js +84 -0
- package/src/actions/index.js +31 -0
- package/src/actions/intervals/resolve.js +352 -0
- package/src/actions/marks/area.js +369 -0
- package/src/actions/marks/bar/create.js +46 -0
- package/src/actions/marks/bar/edit.js +90 -0
- package/src/actions/marks/bar/index.js +9 -0
- package/src/actions/marks/bar/materialize.js +130 -0
- package/src/actions/marks/index.js +13 -0
- package/src/actions/marks/line.js +248 -0
- package/src/actions/marks/point.js +420 -0
- package/src/actions/marks/rule.js +287 -0
- package/src/actions/marks/shared.js +112 -0
- package/src/actions/primitives/createGraphics.js +163 -0
- package/src/actions/primitives/editGraphics.js +208 -0
- package/src/actions/primitives/index.js +9 -0
- package/src/actions/primitives/semantic.js +168 -0
- package/src/actions/primitives/semanticValue.js +182 -0
- package/src/actions/regression/components.js +216 -0
- package/src/actions/regression/create.js +102 -0
- package/src/actions/regression/index.js +23 -0
- package/src/actions/regression/resolve.js +57 -0
- package/src/actions/scales/consumers.js +283 -0
- package/src/actions/scales/create.js +148 -0
- package/src/actions/scales/definitions.js +319 -0
- package/src/actions/scales/edit.js +290 -0
- package/src/actions/scales/index.js +11 -0
- package/src/actions/scales/materialize.js +466 -0
- package/src/actions/scales/quantitativeColor.js +45 -0
- package/src/actions/selection/actions.js +384 -0
- package/src/actions/selection/index.js +23 -0
- package/src/actions/titles/actions.js +235 -0
- package/src/actions/titles/index.js +19 -0
- package/src/actions/titles/resolve.js +321 -0
- package/src/core/ChartProgram.js +316 -0
- package/src/core/action.js +120 -0
- package/src/core/identifiers.js +28 -0
- package/src/core/immutable.js +94 -0
- package/src/core/specs.js +29 -0
- package/src/core/textMetrics.js +23 -0
- package/src/core/validation.js +54 -0
- package/src/core/vocabulary.js +58 -0
- package/src/extension.js +2 -0
- package/src/grammar/aggregate.js +259 -0
- package/src/grammar/areaSeries.js +217 -0
- package/src/grammar/bars/aggregate.js +98 -0
- package/src/grammar/bars/geometry.js +54 -0
- package/src/grammar/bars/policy.js +90 -0
- package/src/grammar/boxPlot.js +140 -0
- package/src/grammar/coordinates.js +25 -0
- package/src/grammar/curveCommands.js +356 -0
- package/src/grammar/density.js +296 -0
- package/src/grammar/filter.js +169 -0
- package/src/grammar/histogram.js +332 -0
- package/src/grammar/interval.js +343 -0
- package/src/grammar/lineSeries.js +197 -0
- package/src/grammar/markFilter.js +46 -0
- package/src/grammar/markSelection.js +326 -0
- package/src/grammar/palettes.js +208 -0
- package/src/grammar/pathCommands.js +86 -0
- package/src/grammar/pointShapes.js +164 -0
- package/src/grammar/positionCompatibility.js +32 -0
- package/src/grammar/regression.js +578 -0
- package/src/grammar/ruleAppearance.js +12 -0
- package/src/grammar/rules.js +90 -0
- package/src/grammar/scales/appearance.js +185 -0
- package/src/grammar/scales/color.js +349 -0
- package/src/grammar/scales/continuous.js +111 -0
- package/src/grammar/scales/definition.js +153 -0
- package/src/grammar/scales/discretized.js +159 -0
- package/src/grammar/scales/fields.js +133 -0
- package/src/grammar/scales/index.js +12 -0
- package/src/grammar/scales/mapping.js +27 -0
- package/src/grammar/scales/ordinal.js +221 -0
- package/src/grammar/scales/policies.js +50 -0
- package/src/grammar/scales/temporal.js +53 -0
- package/src/grammar/scales/transformed.js +236 -0
- package/src/grammar/scales/types.js +102 -0
- package/src/grammar/scales/validation.js +66 -0
- package/src/grammar/scales.js +1 -0
- package/src/grammar/schemas/concreteGraphic.js +73 -0
- package/src/grammar/schemas/graphic.js +131 -0
- package/src/grammar/schemas/graphicBounds.js +146 -0
- package/src/grammar/schemas/graphicTree.js +133 -0
- package/src/grammar/schemas/mark.js +4 -0
- package/src/grammar/schemas/semanticPath.js +174 -0
- package/src/grammar/seriesLayout.js +76 -0
- package/src/grammar/ticks.js +149 -0
- package/src/grammar/transforms.js +37 -0
- package/src/index.js +2 -0
- package/src/layout/canvas.js +114 -0
- package/src/layout/grid.js +17 -0
- package/src/layout/legend.js +51 -0
- package/src/layout/text.js +62 -0
- package/src/layout/title.js +107 -0
- package/src/materialization/bars/aggregate.js +184 -0
- package/src/materialization/bars/grouped.js +117 -0
- package/src/materialization/bars/histogram.js +163 -0
- package/src/materialization/bars/ranged.js +53 -0
- package/src/materialization/bars/resolve.js +56 -0
- package/src/materialization/dataProvenance.js +15 -0
- package/src/materialization/density.js +29 -0
- package/src/materialization/dependencies.js +134 -0
- package/src/materialization/encodings.js +50 -0
- package/src/materialization/graphicHierarchy.js +104 -0
- package/src/materialization/legends.js +20 -0
- package/src/materialization/marks.js +110 -0
- package/src/materialization/planner.js +38 -0
- package/src/materialization/selection/filter.js +40 -0
- package/src/materialization/selection/items.js +503 -0
- package/src/materialization/selection/path.js +37 -0
- package/src/materialization/selection/point.js +118 -0
- package/src/materialization/selection/policies/area.js +10 -0
- package/src/materialization/selection/policies/bar.js +10 -0
- package/src/materialization/selection/policies/index.js +40 -0
- package/src/materialization/selection/policies/line.js +10 -0
- package/src/materialization/selection/policies/point.js +10 -0
- package/src/materialization/selection/policies/rule.js +10 -0
- package/src/materialization/selection/state.js +67 -0
- package/src/materialization/selection/styles.js +183 -0
- package/src/renderers/canvas/circle.js +62 -0
- package/src/renderers/canvas/index.js +132 -0
- package/src/renderers/canvas/line.js +54 -0
- package/src/renderers/canvas/path.js +98 -0
- package/src/renderers/canvas/rect.js +57 -0
- package/src/renderers/canvas/text.js +96 -0
- package/src/renderers/canvas/validation.js +23 -0
- package/src/renderers/png.js +29 -0
- package/src/selectors/coordinates.js +13 -0
- package/src/selectors/datasets.js +13 -0
- package/src/selectors/index.js +4 -0
- package/src/selectors/layers.js +32 -0
- package/src/selectors/scales.js +21 -0
- package/src/theme/defaults.js +13 -0
- package/types/extension.d.ts +14 -0
- package/types/index.d.ts +81 -0
- package/types/png.d.ts +14 -0
- package/types/program.d.ts +1096 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { validateUserId } from "../../core/identifiers.js";
|
|
3
|
+
import { isPlainObject } from "../../core/immutable.js";
|
|
4
|
+
import { validateKeys } from "../../core/validation.js";
|
|
5
|
+
import { resolveEligibleLayer } from "../../selectors/layers.js";
|
|
6
|
+
import { transformPointHighlightChild } from "../../materialization/selection/point.js";
|
|
7
|
+
import {
|
|
8
|
+
transformPathHighlightProperties,
|
|
9
|
+
transformRuleHighlightProperties
|
|
10
|
+
} from "../../materialization/selection/path.js";
|
|
11
|
+
import {
|
|
12
|
+
resolveMarkSelection,
|
|
13
|
+
resolveSelectionCreationId,
|
|
14
|
+
resolveStoredSelection
|
|
15
|
+
} from "../../materialization/selection/state.js";
|
|
16
|
+
import {
|
|
17
|
+
findSelectionPolicy,
|
|
18
|
+
requireSelectionPolicy
|
|
19
|
+
} from "../../materialization/selection/policies/index.js";
|
|
20
|
+
import {
|
|
21
|
+
normalizeDimOthers,
|
|
22
|
+
validateUnitInterval
|
|
23
|
+
} from "../../materialization/selection/styles.js";
|
|
24
|
+
|
|
25
|
+
const SELECTOR_KEYS = Object.freeze([
|
|
26
|
+
"grain", "field", "channel", "property", "op", "value", "values", "min", "max",
|
|
27
|
+
"inclusive", "count", "groupBy", "ties"
|
|
28
|
+
]);
|
|
29
|
+
const SELECT_OPTIONS = Object.freeze(["id", "target", ...SELECTOR_KEYS]);
|
|
30
|
+
const HIGHLIGHT_OPTIONS = Object.freeze([
|
|
31
|
+
"id", "target", "select", "selection", "color", "opacity", "fill",
|
|
32
|
+
"stroke", "strokeWidth", "strokeDash", "shape", "size", "offset",
|
|
33
|
+
"dimOthers", "bringToFront"
|
|
34
|
+
]);
|
|
35
|
+
const INTERNAL_SELECTION_OPTIONS = Object.freeze(["selection", "style", "keys"]);
|
|
36
|
+
const INTERNAL_DIM_OPTIONS = Object.freeze(["selection", "opacity", "keys"]);
|
|
37
|
+
const INTERNAL_ORDER_OPTIONS = Object.freeze(["selection", "keys"]);
|
|
38
|
+
const REMATERIALIZE_OPTIONS = Object.freeze(["target", "highlights"]);
|
|
39
|
+
|
|
40
|
+
function selectorFrom(args) {
|
|
41
|
+
return Object.fromEntries(
|
|
42
|
+
SELECTOR_KEYS.flatMap(key => Object.hasOwn(args, key) ? [[key, args[key]]] : [])
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveTarget(program, target, label = "mark selection") {
|
|
47
|
+
const requested = target === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: validateUserId(target, "Mark id");
|
|
50
|
+
return resolveEligibleLayer(program, {
|
|
51
|
+
target: requested,
|
|
52
|
+
label,
|
|
53
|
+
predicate: layer => findSelectionPolicy(layer.mark?.type) !== undefined
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function hasLegendSelection(program, target, selection) {
|
|
58
|
+
const legend = program.guideConfigs.legend?.series ??
|
|
59
|
+
program.guideConfigs.legend?.color;
|
|
60
|
+
const selector = program.materializationConfigs.selections?.[selection]?.selector;
|
|
61
|
+
return legend?.target === target &&
|
|
62
|
+
selector?.field !== undefined &&
|
|
63
|
+
selector.field === legend.field;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function selectedKeys(args, resolved) {
|
|
67
|
+
if (args.keys === undefined) return resolved.keys;
|
|
68
|
+
if (!Array.isArray(args.keys) || !args.keys.every(key =>
|
|
69
|
+
typeof key === "string" && key.length > 0
|
|
70
|
+
)) {
|
|
71
|
+
throw new TypeError("Selected mark item keys must be non-empty strings.");
|
|
72
|
+
}
|
|
73
|
+
return args.keys;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const selectMarks = action(
|
|
77
|
+
{ op: "selectMarks", description: "Create one reusable selection over final mark items." },
|
|
78
|
+
function (args = {}) {
|
|
79
|
+
validateKeys(args, SELECT_OPTIONS, "selectMarks");
|
|
80
|
+
const layer = resolveTarget(this, args.target);
|
|
81
|
+
const resolved = resolveMarkSelection(this, layer.id, selectorFrom(args));
|
|
82
|
+
const id = resolveSelectionCreationId(this, args.id, layer.id);
|
|
83
|
+
return this
|
|
84
|
+
._withSelectionConfig(id, { target: layer.id, selector: resolved.selector })
|
|
85
|
+
._withContext({ currentSelection: id });
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
export const applyPointHighlight = action(
|
|
90
|
+
{ op: "applyPointHighlight", description: "Apply selected point appearance and geometry." },
|
|
91
|
+
function (args = {}) {
|
|
92
|
+
validateKeys(args, INTERNAL_SELECTION_OPTIONS, "applyPointHighlight");
|
|
93
|
+
const resolved = resolveStoredSelection(this, args.selection);
|
|
94
|
+
const keys = selectedKeys(args, resolved);
|
|
95
|
+
if (resolved.items[0]?.markType !== "point" && resolved.items.length > 0) {
|
|
96
|
+
throw new Error("applyPointHighlight requires a point selection.");
|
|
97
|
+
}
|
|
98
|
+
if (keys.length === 0) return this;
|
|
99
|
+
const selected = new Set(keys);
|
|
100
|
+
const graphic = this.graphicSpec.objects[resolved.definition.target];
|
|
101
|
+
const keyByGraphic = new Map(resolved.items.flatMap(item =>
|
|
102
|
+
item.graphicIds.map(id => [id, item.key])
|
|
103
|
+
));
|
|
104
|
+
const items = graphic.items.map(child =>
|
|
105
|
+
selected.has(keyByGraphic.get(child.id))
|
|
106
|
+
? transformPointHighlightChild({
|
|
107
|
+
type: child.type ?? graphic.type,
|
|
108
|
+
properties: child.properties
|
|
109
|
+
}, args.style)
|
|
110
|
+
: { type: child.type ?? graphic.type, properties: child.properties }
|
|
111
|
+
);
|
|
112
|
+
return this.editGraphics({
|
|
113
|
+
target: resolved.definition.target,
|
|
114
|
+
property: "items",
|
|
115
|
+
value: items
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
export const applyBarHighlight = action(
|
|
121
|
+
{ op: "applyBarHighlight", description: "Apply selected bar appearance." },
|
|
122
|
+
function (args = {}) {
|
|
123
|
+
validateKeys(args, INTERNAL_SELECTION_OPTIONS, "applyBarHighlight");
|
|
124
|
+
const resolved = resolveStoredSelection(this, args.selection);
|
|
125
|
+
const keys = selectedKeys(args, resolved);
|
|
126
|
+
if (resolved.items[0]?.markType !== "bar" && resolved.items.length > 0) {
|
|
127
|
+
throw new Error("applyBarHighlight requires a bar selection.");
|
|
128
|
+
}
|
|
129
|
+
if (keys.length === 0) return this;
|
|
130
|
+
const selected = new Set(keys);
|
|
131
|
+
const graphic = this.graphicSpec.objects[resolved.definition.target];
|
|
132
|
+
const keyByGraphic = new Map(resolved.items.flatMap(item =>
|
|
133
|
+
item.graphicIds.map(id => [id, item.key])
|
|
134
|
+
));
|
|
135
|
+
return this.editGraphics({
|
|
136
|
+
target: resolved.definition.target,
|
|
137
|
+
property: "items",
|
|
138
|
+
value: graphic.items.map(child => ({
|
|
139
|
+
type: child.type ?? graphic.type,
|
|
140
|
+
properties: selected.has(keyByGraphic.get(child.id))
|
|
141
|
+
? { ...child.properties, ...args.style }
|
|
142
|
+
: child.properties
|
|
143
|
+
}))
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
export const applyPathHighlight = action(
|
|
149
|
+
{ op: "applyPathHighlight", description: "Apply selected line or area path appearance and offset." },
|
|
150
|
+
function (args = {}) {
|
|
151
|
+
validateKeys(args, INTERNAL_SELECTION_OPTIONS, "applyPathHighlight");
|
|
152
|
+
const resolved = resolveStoredSelection(this, args.selection);
|
|
153
|
+
const keys = selectedKeys(args, resolved);
|
|
154
|
+
const markType = resolved.items[0]?.markType;
|
|
155
|
+
if (!["line", "area"].includes(markType) && resolved.items.length > 0) {
|
|
156
|
+
throw new Error("applyPathHighlight requires a line or area selection.");
|
|
157
|
+
}
|
|
158
|
+
if (keys.length === 0) return this;
|
|
159
|
+
const selected = new Set(keys);
|
|
160
|
+
const graphic = this.graphicSpec.objects[resolved.definition.target];
|
|
161
|
+
const keyByGraphic = new Map(resolved.items.flatMap(item =>
|
|
162
|
+
item.graphicIds.map(id => [id, item.key])
|
|
163
|
+
));
|
|
164
|
+
return this.editGraphics({
|
|
165
|
+
target: resolved.definition.target,
|
|
166
|
+
property: "items",
|
|
167
|
+
value: graphic.items.map(child => ({
|
|
168
|
+
type: child.type ?? graphic.type,
|
|
169
|
+
properties: selected.has(keyByGraphic.get(child.id))
|
|
170
|
+
? transformPathHighlightProperties(child.properties, args.style)
|
|
171
|
+
: child.properties
|
|
172
|
+
}))
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
export const applyRuleHighlight = action(
|
|
178
|
+
{ op: "applyRuleHighlight", description: "Apply selected rule appearance and offset." },
|
|
179
|
+
function (args = {}) {
|
|
180
|
+
validateKeys(args, INTERNAL_SELECTION_OPTIONS, "applyRuleHighlight");
|
|
181
|
+
const resolved = resolveStoredSelection(this, args.selection);
|
|
182
|
+
const keys = selectedKeys(args, resolved);
|
|
183
|
+
if (resolved.items[0]?.markType !== "rule" && resolved.items.length > 0) {
|
|
184
|
+
throw new Error("applyRuleHighlight requires a rule selection.");
|
|
185
|
+
}
|
|
186
|
+
if (keys.length === 0) return this;
|
|
187
|
+
const selected = new Set(keys);
|
|
188
|
+
const graphic = this.graphicSpec.objects[resolved.definition.target];
|
|
189
|
+
const keyByGraphic = new Map(resolved.items.flatMap(item =>
|
|
190
|
+
item.graphicIds.map(id => [id, item.key])
|
|
191
|
+
));
|
|
192
|
+
return this.editGraphics({
|
|
193
|
+
target: resolved.definition.target,
|
|
194
|
+
property: "items",
|
|
195
|
+
value: graphic.items.map(child => ({
|
|
196
|
+
type: child.type ?? graphic.type,
|
|
197
|
+
properties: selected.has(keyByGraphic.get(child.id))
|
|
198
|
+
? transformRuleHighlightProperties(child.properties, args.style)
|
|
199
|
+
: child.properties
|
|
200
|
+
}))
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
export const dimUnselectedMarkItems = action(
|
|
206
|
+
{ op: "dimUnselectedMarkItems", description: "Dim the complement of one mark selection." },
|
|
207
|
+
function (args = {}) {
|
|
208
|
+
validateKeys(args, INTERNAL_DIM_OPTIONS, "dimUnselectedMarkItems");
|
|
209
|
+
const opacity = validateUnitInterval(args.opacity, "Dim opacity");
|
|
210
|
+
const resolved = resolveStoredSelection(this, args.selection);
|
|
211
|
+
const selected = new Set(selectedKeys(args, resolved));
|
|
212
|
+
const graphic = this.graphicSpec.objects[resolved.definition.target];
|
|
213
|
+
const keyByGraphic = new Map(resolved.items.flatMap(item =>
|
|
214
|
+
item.graphicIds.map(id => [id, item.key])
|
|
215
|
+
));
|
|
216
|
+
return this.editGraphics({
|
|
217
|
+
target: resolved.definition.target,
|
|
218
|
+
property: "items",
|
|
219
|
+
value: graphic.items.map(child => ({
|
|
220
|
+
type: child.type ?? graphic.type,
|
|
221
|
+
properties: selected.has(keyByGraphic.get(child.id))
|
|
222
|
+
? child.properties
|
|
223
|
+
: { ...child.properties, opacity }
|
|
224
|
+
}))
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
export const placeSelectedMarkItemsLast = action(
|
|
230
|
+
{ op: "placeSelectedMarkItemsLast", description: "Place selected collection items after their complement." },
|
|
231
|
+
function (args = {}) {
|
|
232
|
+
validateKeys(args, INTERNAL_ORDER_OPTIONS, "placeSelectedMarkItemsLast");
|
|
233
|
+
const resolved = resolveStoredSelection(this, args.selection);
|
|
234
|
+
const keys = selectedKeys(args, resolved);
|
|
235
|
+
if (keys.length === 0 || keys.length === resolved.items.length) {
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
238
|
+
const selected = new Set(keys);
|
|
239
|
+
const graphic = this.graphicSpec.objects[resolved.definition.target];
|
|
240
|
+
const keyByGraphic = new Map(resolved.items.flatMap(item =>
|
|
241
|
+
item.graphicIds.map(id => [id, item.key])
|
|
242
|
+
));
|
|
243
|
+
const items = graphic.items.map(child => ({
|
|
244
|
+
key: keyByGraphic.get(child.id),
|
|
245
|
+
child: { type: child.type ?? graphic.type, properties: child.properties }
|
|
246
|
+
}));
|
|
247
|
+
return this.editGraphics({
|
|
248
|
+
target: resolved.definition.target,
|
|
249
|
+
property: "items",
|
|
250
|
+
value: [
|
|
251
|
+
...items.filter(item => !selected.has(item.key)),
|
|
252
|
+
...items.filter(item => selected.has(item.key))
|
|
253
|
+
].map(item => item.child)
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
export const rematerializeMarkHighlights = action(
|
|
259
|
+
{ op: "rematerializeMarkHighlights", description: "Reapply stored highlight assignments to one rematerialized mark." },
|
|
260
|
+
function (args = {}) {
|
|
261
|
+
validateKeys(args, REMATERIALIZE_OPTIONS, "rematerializeMarkHighlights");
|
|
262
|
+
validateUserId(args.target, "Highlight target id");
|
|
263
|
+
if (!Array.isArray(args.highlights)) {
|
|
264
|
+
throw new TypeError("rematerializeMarkHighlights requires highlight entries.");
|
|
265
|
+
}
|
|
266
|
+
const prepared = args.highlights.map(([id, config]) => ({
|
|
267
|
+
id,
|
|
268
|
+
config,
|
|
269
|
+
keys: resolveStoredSelection(this, config.selection).keys
|
|
270
|
+
}));
|
|
271
|
+
let next = this;
|
|
272
|
+
for (const { id, config, keys } of prepared) {
|
|
273
|
+
const policy = requireSelectionPolicy(config.markType);
|
|
274
|
+
next = next[policy.applyHighlightOp]({
|
|
275
|
+
selection: config.selection,
|
|
276
|
+
style: config.style,
|
|
277
|
+
keys
|
|
278
|
+
});
|
|
279
|
+
if (config.dimOthers !== false) {
|
|
280
|
+
next = next.dimUnselectedMarkItems({
|
|
281
|
+
selection: config.selection,
|
|
282
|
+
opacity: config.dimOthers.opacity,
|
|
283
|
+
keys
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
if (config.bringToFront) {
|
|
287
|
+
next = next.placeSelectedMarkItemsLast({
|
|
288
|
+
selection: config.selection,
|
|
289
|
+
keys
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
next = next._withHighlightConfig(id, config);
|
|
293
|
+
}
|
|
294
|
+
return prepared.some(({ config }) =>
|
|
295
|
+
hasLegendSelection(next, config.target, config.selection)
|
|
296
|
+
)
|
|
297
|
+
? next.rematerializeLegendHighlights()
|
|
298
|
+
: next;
|
|
299
|
+
}
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
export const highlightMarks = action(
|
|
303
|
+
{ op: "highlightMarks", description: "Select and emphasize final visual mark items." },
|
|
304
|
+
function (args = {}) {
|
|
305
|
+
validateKeys(args, HIGHLIGHT_OPTIONS, "highlightMarks");
|
|
306
|
+
if (args.select !== undefined && args.selection !== undefined) {
|
|
307
|
+
throw new Error("highlightMarks accepts select or selection, not both.");
|
|
308
|
+
}
|
|
309
|
+
if (args.select !== undefined && !isPlainObject(args.select)) {
|
|
310
|
+
throw new TypeError("highlightMarks select must be a plain object.");
|
|
311
|
+
}
|
|
312
|
+
if (args.id !== undefined && args.select === undefined) {
|
|
313
|
+
throw new Error("highlightMarks id is available only with inline select.");
|
|
314
|
+
}
|
|
315
|
+
const dimOthers = normalizeDimOthers(args.dimOthers);
|
|
316
|
+
const bringToFront = args.bringToFront ?? true;
|
|
317
|
+
if (typeof bringToFront !== "boolean") {
|
|
318
|
+
throw new TypeError("highlightMarks bringToFront must be a boolean.");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let next = this;
|
|
322
|
+
let resolved;
|
|
323
|
+
let layer;
|
|
324
|
+
if (args.select !== undefined) {
|
|
325
|
+
layer = resolveTarget(this, args.target, "highlight mark");
|
|
326
|
+
resolveMarkSelection(this, layer.id, args.select);
|
|
327
|
+
const style = requireSelectionPolicy(
|
|
328
|
+
layer.mark.type
|
|
329
|
+
).normalizeHighlightStyle(args);
|
|
330
|
+
next = next.selectMarks({
|
|
331
|
+
...(args.id === undefined ? {} : { id: args.id }),
|
|
332
|
+
target: layer.id,
|
|
333
|
+
...args.select
|
|
334
|
+
});
|
|
335
|
+
resolved = resolveStoredSelection(next);
|
|
336
|
+
resolved = { ...resolved, style };
|
|
337
|
+
} else {
|
|
338
|
+
resolved = resolveStoredSelection(next, args.selection);
|
|
339
|
+
if (args.target !== undefined && args.target !== resolved.definition.target) {
|
|
340
|
+
throw new Error("highlightMarks target must match its selection target.");
|
|
341
|
+
}
|
|
342
|
+
layer = resolveTarget(next, resolved.definition.target, "highlight mark");
|
|
343
|
+
resolved = {
|
|
344
|
+
...resolved,
|
|
345
|
+
style: requireSelectionPolicy(
|
|
346
|
+
layer.mark.type
|
|
347
|
+
).normalizeHighlightStyle(args)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const selection = resolved.id;
|
|
352
|
+
const keys = resolved.keys;
|
|
353
|
+
const style = resolved.style;
|
|
354
|
+
const existing = next.materializationConfigs.highlights?.[selection];
|
|
355
|
+
if (existing !== undefined) {
|
|
356
|
+
next = next._withoutMaterializationConfig(["highlights", selection]);
|
|
357
|
+
const policy = requireSelectionPolicy(layer.mark.type);
|
|
358
|
+
next = next[policy.rematerializeOp]({ id: resolved.definition.target });
|
|
359
|
+
}
|
|
360
|
+
const policy = requireSelectionPolicy(layer.mark.type);
|
|
361
|
+
next = next[policy.applyHighlightOp]({ selection, style, keys });
|
|
362
|
+
if (dimOthers !== false) {
|
|
363
|
+
next = next.dimUnselectedMarkItems({
|
|
364
|
+
selection,
|
|
365
|
+
opacity: dimOthers.opacity,
|
|
366
|
+
keys
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
if (bringToFront) {
|
|
370
|
+
next = next.placeSelectedMarkItemsLast({ selection, keys });
|
|
371
|
+
}
|
|
372
|
+
next = next._withHighlightConfig(selection, {
|
|
373
|
+
target: resolved.definition.target,
|
|
374
|
+
selection,
|
|
375
|
+
markType: layer.mark.type,
|
|
376
|
+
style,
|
|
377
|
+
dimOthers,
|
|
378
|
+
bringToFront
|
|
379
|
+
});
|
|
380
|
+
return hasLegendSelection(next, resolved.definition.target, selection)
|
|
381
|
+
? next.rematerializeLegendHighlights()
|
|
382
|
+
: next;
|
|
383
|
+
}
|
|
384
|
+
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyBarHighlight,
|
|
3
|
+
applyPathHighlight,
|
|
4
|
+
applyPointHighlight,
|
|
5
|
+
applyRuleHighlight,
|
|
6
|
+
dimUnselectedMarkItems,
|
|
7
|
+
highlightMarks,
|
|
8
|
+
placeSelectedMarkItemsLast,
|
|
9
|
+
rematerializeMarkHighlights,
|
|
10
|
+
selectMarks
|
|
11
|
+
} from "./actions.js";
|
|
12
|
+
|
|
13
|
+
export function registerSelectionActions(ProgramClass) {
|
|
14
|
+
ProgramClass.prototype.selectMarks = selectMarks;
|
|
15
|
+
ProgramClass.prototype.highlightMarks = highlightMarks;
|
|
16
|
+
ProgramClass.prototype.applyBarHighlight = applyBarHighlight;
|
|
17
|
+
ProgramClass.prototype.applyPathHighlight = applyPathHighlight;
|
|
18
|
+
ProgramClass.prototype.applyPointHighlight = applyPointHighlight;
|
|
19
|
+
ProgramClass.prototype.applyRuleHighlight = applyRuleHighlight;
|
|
20
|
+
ProgramClass.prototype.dimUnselectedMarkItems = dimUnselectedMarkItems;
|
|
21
|
+
ProgramClass.prototype.placeSelectedMarkItemsLast = placeSelectedMarkItemsLast;
|
|
22
|
+
ProgramClass.prototype.rematerializeMarkHighlights = rematerializeMarkHighlights;
|
|
23
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { action } from "../../core/action.js";
|
|
2
|
+
import { noOptions } from "../../core/validation.js";
|
|
3
|
+
import {
|
|
4
|
+
normalizeTitleEditOptions,
|
|
5
|
+
normalizeTitleOptions,
|
|
6
|
+
requireTitleConfig,
|
|
7
|
+
resolveTitleLayout
|
|
8
|
+
} from "./resolve.js";
|
|
9
|
+
import {
|
|
10
|
+
preserveGraphicPlacement,
|
|
11
|
+
resolveCanvasGraphicPlacement
|
|
12
|
+
} from "../../materialization/graphicHierarchy.js";
|
|
13
|
+
|
|
14
|
+
function hasRotation(graphic) {
|
|
15
|
+
const properties = graphic.items?.map(child => child.properties) ??
|
|
16
|
+
[graphic.properties];
|
|
17
|
+
return properties.some(item => Object.hasOwn(item, "rotation"));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ensureTextShape(program, id, component) {
|
|
21
|
+
const graphic = program.graphicSpec.objects[id];
|
|
22
|
+
if (graphic?.type !== "text") {
|
|
23
|
+
throw new Error(`${id} requires an existing text graphic.`);
|
|
24
|
+
}
|
|
25
|
+
const collection = graphic.items !== undefined;
|
|
26
|
+
const needsCollection = component.lines.length > 1;
|
|
27
|
+
const rotationMismatch = hasRotation(graphic) !== component.explicitRotation;
|
|
28
|
+
if ((collection && !needsCollection) || rotationMismatch) {
|
|
29
|
+
const placement = preserveGraphicPlacement(program, id);
|
|
30
|
+
return program
|
|
31
|
+
.editGraphics({ target: id, remove: true })
|
|
32
|
+
.createGraphics({
|
|
33
|
+
id,
|
|
34
|
+
type: "text",
|
|
35
|
+
...(needsCollection ? { length: component.lines.length } : {}),
|
|
36
|
+
...placement
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (!collection && needsCollection) {
|
|
40
|
+
return program.editGraphics({
|
|
41
|
+
target: id,
|
|
42
|
+
property: "length",
|
|
43
|
+
value: component.lines.length
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (collection && graphic.items.length !== component.lines.length) {
|
|
47
|
+
return program.editGraphics({
|
|
48
|
+
target: id,
|
|
49
|
+
property: "length",
|
|
50
|
+
value: component.lines.length
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return program;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function distributed(value, count) {
|
|
57
|
+
return count === 1 && Array.isArray(value) ? value[0] : value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function editTextGraphic(program, id, component, style) {
|
|
61
|
+
const count = component.lines.length;
|
|
62
|
+
let next = ensureTextShape(program, id, component)
|
|
63
|
+
.editGraphics({ target: id, property: "x", value: distributed(component.x, count) })
|
|
64
|
+
.editGraphics({ target: id, property: "y", value: distributed(component.y, count) })
|
|
65
|
+
.editGraphics({
|
|
66
|
+
target: id,
|
|
67
|
+
property: "text",
|
|
68
|
+
value: distributed(component.lines, count)
|
|
69
|
+
})
|
|
70
|
+
.editGraphics({ target: id, property: "fill", value: style.color })
|
|
71
|
+
.editGraphics({ target: id, property: "fontSize", value: style.fontSize })
|
|
72
|
+
.editGraphics({ target: id, property: "fontFamily", value: style.fontFamily })
|
|
73
|
+
.editGraphics({ target: id, property: "fontWeight", value: style.fontWeight })
|
|
74
|
+
.editGraphics({ target: id, property: "textAlign", value: component.textAlign })
|
|
75
|
+
.editGraphics({ target: id, property: "textBaseline", value: "middle" });
|
|
76
|
+
if (component.explicitRotation) {
|
|
77
|
+
next = next.editGraphics({
|
|
78
|
+
target: id,
|
|
79
|
+
property: "rotation",
|
|
80
|
+
value: component.rotation
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return next;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function createTextGraphic(program, id, component) {
|
|
87
|
+
return program.createGraphics({
|
|
88
|
+
id,
|
|
89
|
+
type: "text",
|
|
90
|
+
...(component.lines.length > 1 ? { length: component.lines.length } : {}),
|
|
91
|
+
...resolveCanvasGraphicPlacement(program)
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const editTitleText = action(
|
|
96
|
+
{ op: "editTitleText", description: "Rematerialize chart title text." },
|
|
97
|
+
function (args = {}) {
|
|
98
|
+
noOptions(args, "editTitleText");
|
|
99
|
+
const config = requireTitleConfig(this);
|
|
100
|
+
const layout = resolveTitleLayout(this, config);
|
|
101
|
+
return editTextGraphic(
|
|
102
|
+
this,
|
|
103
|
+
"chartTitle",
|
|
104
|
+
layout.title,
|
|
105
|
+
config.titleStyle
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
export const createTitleText = action(
|
|
111
|
+
{ op: "createTitleText", description: "Create chart title text." },
|
|
112
|
+
function (args = {}) {
|
|
113
|
+
noOptions(args, "createTitleText");
|
|
114
|
+
const config = requireTitleConfig(this);
|
|
115
|
+
if (this.graphicSpec.objects.chartTitle !== undefined) {
|
|
116
|
+
throw new Error("createTitleText requires a missing chart title graphic.");
|
|
117
|
+
}
|
|
118
|
+
const layout = resolveTitleLayout(this, config);
|
|
119
|
+
return createTextGraphic(this, "chartTitle", layout.title)
|
|
120
|
+
.editTitleText();
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
export const editSubtitleText = action(
|
|
125
|
+
{ op: "editSubtitleText", description: "Rematerialize chart subtitle text." },
|
|
126
|
+
function (args = {}) {
|
|
127
|
+
noOptions(args, "editSubtitleText");
|
|
128
|
+
const config = requireTitleConfig(this);
|
|
129
|
+
const layout = resolveTitleLayout(this, config);
|
|
130
|
+
if (layout.subtitle === undefined) {
|
|
131
|
+
throw new Error("editSubtitleText requires semantic subtitle text.");
|
|
132
|
+
}
|
|
133
|
+
return editTextGraphic(
|
|
134
|
+
this,
|
|
135
|
+
"chartSubtitle",
|
|
136
|
+
layout.subtitle,
|
|
137
|
+
config.subtitleStyle
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
export const createSubtitleText = action(
|
|
143
|
+
{ op: "createSubtitleText", description: "Create chart subtitle text." },
|
|
144
|
+
function (args = {}) {
|
|
145
|
+
noOptions(args, "createSubtitleText");
|
|
146
|
+
const config = requireTitleConfig(this);
|
|
147
|
+
if (this.graphicSpec.objects.chartSubtitle !== undefined) {
|
|
148
|
+
throw new Error("createSubtitleText requires a missing chart subtitle graphic.");
|
|
149
|
+
}
|
|
150
|
+
const layout = resolveTitleLayout(this, config);
|
|
151
|
+
if (layout.subtitle === undefined) {
|
|
152
|
+
throw new Error("createSubtitleText requires semantic subtitle text.");
|
|
153
|
+
}
|
|
154
|
+
return createTextGraphic(this, "chartSubtitle", layout.subtitle)
|
|
155
|
+
.editSubtitleText();
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
export const rematerializeTitle = action(
|
|
160
|
+
{ op: "rematerializeTitle", description: "Rematerialize chart title graphics." },
|
|
161
|
+
function (args = {}) {
|
|
162
|
+
noOptions(args, "rematerializeTitle");
|
|
163
|
+
requireTitleConfig(this);
|
|
164
|
+
if (this.graphicSpec.objects.chartTitle?.type !== "text") {
|
|
165
|
+
throw new Error("rematerializeTitle requires an existing chart title graphic.");
|
|
166
|
+
}
|
|
167
|
+
let next = this.editTitleText();
|
|
168
|
+
const hasSubtitle = next.semanticSpec.title.subtitle !== undefined;
|
|
169
|
+
const graphic = next.graphicSpec.objects.chartSubtitle;
|
|
170
|
+
if (hasSubtitle && graphic === undefined) {
|
|
171
|
+
next = next.createSubtitleText();
|
|
172
|
+
} else if (hasSubtitle) {
|
|
173
|
+
next = next.editSubtitleText();
|
|
174
|
+
} else if (graphic !== undefined) {
|
|
175
|
+
next = next.editGraphics({ target: "chartSubtitle", remove: true });
|
|
176
|
+
}
|
|
177
|
+
return next;
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
export const createTitle = action(
|
|
182
|
+
{ op: "createTitle", description: "Create a chart title and optional subtitle." },
|
|
183
|
+
function (args = {}) {
|
|
184
|
+
const options = normalizeTitleOptions(args);
|
|
185
|
+
if (Object.keys(this.semanticSpec.title).length > 0) {
|
|
186
|
+
throw new Error("createTitle requires missing semantic title state.");
|
|
187
|
+
}
|
|
188
|
+
if (
|
|
189
|
+
this.graphicSpec.objects.chartTitle !== undefined ||
|
|
190
|
+
this.graphicSpec.objects.chartSubtitle !== undefined
|
|
191
|
+
) {
|
|
192
|
+
throw new Error("createTitle requires missing chart title graphics.");
|
|
193
|
+
}
|
|
194
|
+
const { text, subtitle, ...config } = options;
|
|
195
|
+
let next = this.editSemantic({ property: "title.text", value: text });
|
|
196
|
+
if (subtitle !== undefined) {
|
|
197
|
+
next = next.editSemantic({ property: "title.subtitle", value: subtitle });
|
|
198
|
+
}
|
|
199
|
+
next = next._withTitleConfig(config);
|
|
200
|
+
resolveTitleLayout(next, config);
|
|
201
|
+
next = next.createTitleText();
|
|
202
|
+
if (subtitle !== undefined) next = next.createSubtitleText();
|
|
203
|
+
return next;
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
export const editTitle = action(
|
|
208
|
+
{ op: "editTitle", description: "Edit one stable chart title resource." },
|
|
209
|
+
function (args = {}) {
|
|
210
|
+
if (this.semanticSpec.title.text === undefined) {
|
|
211
|
+
throw new Error("editTitle requires an existing chart title.");
|
|
212
|
+
}
|
|
213
|
+
const previous = requireTitleConfig(this);
|
|
214
|
+
const normalized = normalizeTitleEditOptions(
|
|
215
|
+
args,
|
|
216
|
+
previous,
|
|
217
|
+
this.semanticSpec.title
|
|
218
|
+
);
|
|
219
|
+
let next = this;
|
|
220
|
+
if (args.text !== undefined) {
|
|
221
|
+
next = next.editSemantic({ property: "title.text", value: normalized.text });
|
|
222
|
+
}
|
|
223
|
+
if (args.subtitle === false) {
|
|
224
|
+
next = next.editSemantic({ property: "title.subtitle", remove: true });
|
|
225
|
+
} else if (args.subtitle !== undefined) {
|
|
226
|
+
next = next.editSemantic({
|
|
227
|
+
property: "title.subtitle",
|
|
228
|
+
value: normalized.subtitle
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
next = next._withTitleConfig(normalized.config);
|
|
232
|
+
resolveTitleLayout(next, normalized.config);
|
|
233
|
+
return next.rematerializeTitle();
|
|
234
|
+
}
|
|
235
|
+
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSubtitleText,
|
|
3
|
+
createTitle,
|
|
4
|
+
createTitleText,
|
|
5
|
+
editTitle,
|
|
6
|
+
editSubtitleText,
|
|
7
|
+
editTitleText,
|
|
8
|
+
rematerializeTitle
|
|
9
|
+
} from "./actions.js";
|
|
10
|
+
|
|
11
|
+
export function registerTitleActions(ProgramClass) {
|
|
12
|
+
ProgramClass.prototype.createTitle = createTitle;
|
|
13
|
+
ProgramClass.prototype.editTitle = editTitle;
|
|
14
|
+
ProgramClass.prototype.createTitleText = createTitleText;
|
|
15
|
+
ProgramClass.prototype.editTitleText = editTitleText;
|
|
16
|
+
ProgramClass.prototype.createSubtitleText = createSubtitleText;
|
|
17
|
+
ProgramClass.prototype.editSubtitleText = editSubtitleText;
|
|
18
|
+
ProgramClass.prototype.rematerializeTitle = rematerializeTitle;
|
|
19
|
+
}
|