@mathscan/math-exercise-engine 1.2.9 → 1.2.10
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/README.md +24 -0
- package/dist/{GeometryWidget-D2QyGzW-.js → GeometryWidget-DgPfqyQv.js} +2 -2
- package/dist/{GeometryWidget-D2QyGzW-.js.map → GeometryWidget-DgPfqyQv.js.map} +1 -1
- package/dist/{GeometryWidget-BRV6LLcW.mjs → GeometryWidget-gtAP_-_S.mjs} +2 -2
- package/dist/{GeometryWidget-BRV6LLcW.mjs.map → GeometryWidget-gtAP_-_S.mjs.map} +1 -1
- package/dist/{index-XAInSHXa.mjs → index-DBteHZkS.mjs} +963 -301
- package/dist/index-DBteHZkS.mjs.map +1 -0
- package/dist/{index-D-QbiBn5.js → index-tEgJE6m2.js} +773 -111
- package/dist/index-tEgJE6m2.js.map +1 -0
- package/dist/index.d.ts +9518 -3368
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +205 -191
- package/dist/style.css +128 -0
- package/package.json +1 -1
- package/dist/index-D-QbiBn5.js.map +0 -1
- package/dist/index-XAInSHXa.mjs.map +0 -1
|
@@ -101,6 +101,12 @@ const AST_TYPES = {
|
|
|
101
101
|
CHART_BAR: "ChartBar",
|
|
102
102
|
/** A single pictograph item inside a CHART[type="pictograph"] block */
|
|
103
103
|
CHART_PICTO: "ChartPicto",
|
|
104
|
+
/** A named series (color + legend label) inside a CHART[type="grouped-bar"] block */
|
|
105
|
+
CHART_SERIES: "ChartSeries",
|
|
106
|
+
/** One category cluster inside a CHART[type="grouped-bar"] block */
|
|
107
|
+
CHART_GROUP: "ChartGroup",
|
|
108
|
+
/** A single bar inside a GROUP, referencing a declared SERIES by id */
|
|
109
|
+
CHART_SERIES_BAR: "ChartSeriesBar",
|
|
104
110
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
105
111
|
// Tools
|
|
106
112
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -204,6 +210,10 @@ const JSON_TYPES = {
|
|
|
204
210
|
CHART_BAR: "chart-bar",
|
|
205
211
|
/** A single pictograph item rendered inside a chart */
|
|
206
212
|
CHART_PICTO: "chart-picto",
|
|
213
|
+
/** A single bar rendered inside a grouped-bar chart's GROUP — distinct from
|
|
214
|
+
* CHART_BAR so the JSON self-identifies which shape it is (has `seriesId`),
|
|
215
|
+
* independent of the widget type used for validation. */
|
|
216
|
+
CHART_SERIES_BAR: "chart-series-bar",
|
|
207
217
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
208
218
|
/** Inline input widget (string or numeric) */
|
|
209
219
|
INLINE_INPUT: "inline-input",
|
|
@@ -437,7 +447,7 @@ const MATCHER = chevrotain.createToken({
|
|
|
437
447
|
});
|
|
438
448
|
const GROUP = chevrotain.createToken({
|
|
439
449
|
name: "GROUP",
|
|
440
|
-
pattern: /GROUP/
|
|
450
|
+
pattern: /GROUP(?=\s*[[{])/
|
|
441
451
|
});
|
|
442
452
|
const ITEM = chevrotain.createToken({
|
|
443
453
|
name: "ITEM",
|
|
@@ -487,6 +497,18 @@ const PICTO = chevrotain.createToken({
|
|
|
487
497
|
name: "PICTO",
|
|
488
498
|
pattern: /PICTO/
|
|
489
499
|
});
|
|
500
|
+
const LABEL = chevrotain.createToken({
|
|
501
|
+
name: "LABEL",
|
|
502
|
+
pattern: /LABEL(?=\s*\{)/
|
|
503
|
+
});
|
|
504
|
+
const SERIES = chevrotain.createToken({
|
|
505
|
+
name: "SERIES",
|
|
506
|
+
pattern: /SERIES(?=\s*\[)/
|
|
507
|
+
});
|
|
508
|
+
const SERIES_BAR = chevrotain.createToken({
|
|
509
|
+
name: "SERIES_BAR",
|
|
510
|
+
pattern: /SERIES_BAR(?=\s*\[)/
|
|
511
|
+
});
|
|
490
512
|
const SEP = chevrotain.createToken({
|
|
491
513
|
name: "SEP",
|
|
492
514
|
pattern: /SEP(?![a-zA-Z0-9_])/
|
|
@@ -685,6 +707,9 @@ const allTokens = [
|
|
|
685
707
|
Y_AXIS,
|
|
686
708
|
BAR,
|
|
687
709
|
PICTO,
|
|
710
|
+
LABEL,
|
|
711
|
+
SERIES_BAR,
|
|
712
|
+
SERIES,
|
|
688
713
|
SEP,
|
|
689
714
|
CARRY,
|
|
690
715
|
INPUT,
|
|
@@ -1771,22 +1796,15 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
1771
1796
|
});
|
|
1772
1797
|
this.MANY6({
|
|
1773
1798
|
GATE: () => {
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
const t = this.LA(k).tokenType;
|
|
1777
|
-
return t === BAR || t === PICTO;
|
|
1799
|
+
const t = this.LA(1).tokenType;
|
|
1800
|
+
return t === BAR || t === PICTO || t === SERIES || t === GROUP;
|
|
1778
1801
|
},
|
|
1779
1802
|
DEF: () => {
|
|
1780
1803
|
this.OR6([
|
|
1781
|
-
{
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
return this.LA(k).tokenType === BAR;
|
|
1786
|
-
},
|
|
1787
|
-
ALT: () => this.SUBRULE(this.chartBarItem)
|
|
1788
|
-
},
|
|
1789
|
-
{ ALT: () => this.SUBRULE(this.chartPictoItem) }
|
|
1804
|
+
{ GATE: () => this.LA(1).tokenType === BAR, ALT: () => this.SUBRULE(this.chartBarItem) },
|
|
1805
|
+
{ GATE: () => this.LA(1).tokenType === PICTO, ALT: () => this.SUBRULE(this.chartPictoItem) },
|
|
1806
|
+
{ GATE: () => this.LA(1).tokenType === SERIES, ALT: () => this.SUBRULE(this.chartSeriesItem) },
|
|
1807
|
+
{ ALT: () => this.SUBRULE(this.chartGroupItem) }
|
|
1790
1808
|
]);
|
|
1791
1809
|
}
|
|
1792
1810
|
});
|
|
@@ -1907,6 +1925,92 @@ class MathExerciseParser extends chevrotain.CstParser {
|
|
|
1907
1925
|
this.OR5([{ ALT: () => this.CONSUME5(Space) }, { ALT: () => this.CONSUME5(Newline) }]);
|
|
1908
1926
|
});
|
|
1909
1927
|
});
|
|
1928
|
+
this.chartSeriesItem = this.RULE("chartSeriesItem", () => {
|
|
1929
|
+
this.MANY(() => {
|
|
1930
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1931
|
+
});
|
|
1932
|
+
this.CONSUME(SERIES);
|
|
1933
|
+
this.CONSUME(LBracket);
|
|
1934
|
+
this.SUBRULE(this.parameterList);
|
|
1935
|
+
this.CONSUME(RBracket);
|
|
1936
|
+
this.MANY2(() => {
|
|
1937
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1938
|
+
});
|
|
1939
|
+
this.CONSUME(LBrace);
|
|
1940
|
+
this.MANY3(() => {
|
|
1941
|
+
this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
|
|
1942
|
+
});
|
|
1943
|
+
this.MANY4(() => {
|
|
1944
|
+
this.OR4([
|
|
1945
|
+
{ ALT: () => this.SUBRULE(this.inlineElement) },
|
|
1946
|
+
{ ALT: () => this.CONSUME4(Newline) }
|
|
1947
|
+
]);
|
|
1948
|
+
});
|
|
1949
|
+
this.CONSUME(RBrace);
|
|
1950
|
+
this.MANY5(() => {
|
|
1951
|
+
this.OR5([{ ALT: () => this.CONSUME5(Space) }, { ALT: () => this.CONSUME5(Newline) }]);
|
|
1952
|
+
});
|
|
1953
|
+
});
|
|
1954
|
+
this.chartGroupLabel = this.RULE("chartGroupLabel", () => {
|
|
1955
|
+
this.CONSUME(LABEL);
|
|
1956
|
+
this.MANY(() => {
|
|
1957
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1958
|
+
});
|
|
1959
|
+
this.CONSUME(LBrace);
|
|
1960
|
+
this.MANY2(() => {
|
|
1961
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1962
|
+
});
|
|
1963
|
+
this.MANY3(() => {
|
|
1964
|
+
this.OR3([
|
|
1965
|
+
{ ALT: () => this.SUBRULE(this.inlineElement) },
|
|
1966
|
+
{ ALT: () => this.CONSUME3(Newline) }
|
|
1967
|
+
]);
|
|
1968
|
+
});
|
|
1969
|
+
this.CONSUME(RBrace);
|
|
1970
|
+
this.MANY4(() => {
|
|
1971
|
+
this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
|
|
1972
|
+
});
|
|
1973
|
+
});
|
|
1974
|
+
this.chartSeriesBarItem = this.RULE("chartSeriesBarItem", () => {
|
|
1975
|
+
this.MANY(() => {
|
|
1976
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1977
|
+
});
|
|
1978
|
+
this.CONSUME(SERIES_BAR);
|
|
1979
|
+
this.CONSUME(LBracket);
|
|
1980
|
+
this.SUBRULE(this.parameterList);
|
|
1981
|
+
this.CONSUME(RBracket);
|
|
1982
|
+
this.MANY2(() => {
|
|
1983
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1984
|
+
});
|
|
1985
|
+
});
|
|
1986
|
+
this.chartGroupItem = this.RULE("chartGroupItem", () => {
|
|
1987
|
+
this.MANY(() => {
|
|
1988
|
+
this.OR([{ ALT: () => this.CONSUME(Space) }, { ALT: () => this.CONSUME(Newline) }]);
|
|
1989
|
+
});
|
|
1990
|
+
this.CONSUME(GROUP);
|
|
1991
|
+
this.CONSUME(LBracket);
|
|
1992
|
+
this.SUBRULE(this.parameterList);
|
|
1993
|
+
this.CONSUME(RBracket);
|
|
1994
|
+
this.MANY2(() => {
|
|
1995
|
+
this.OR2([{ ALT: () => this.CONSUME2(Space) }, { ALT: () => this.CONSUME2(Newline) }]);
|
|
1996
|
+
});
|
|
1997
|
+
this.CONSUME(LBrace);
|
|
1998
|
+
this.MANY3(() => {
|
|
1999
|
+
this.OR3([{ ALT: () => this.CONSUME3(Space) }, { ALT: () => this.CONSUME3(Newline) }]);
|
|
2000
|
+
});
|
|
2001
|
+
this.MANY4({
|
|
2002
|
+
GATE: () => this.LA(1).tokenType === LABEL,
|
|
2003
|
+
DEF: () => this.SUBRULE(this.chartGroupLabel)
|
|
2004
|
+
});
|
|
2005
|
+
this.MANY5({
|
|
2006
|
+
GATE: () => this.LA(1).tokenType === SERIES_BAR,
|
|
2007
|
+
DEF: () => this.SUBRULE(this.chartSeriesBarItem)
|
|
2008
|
+
});
|
|
2009
|
+
this.CONSUME(RBrace);
|
|
2010
|
+
this.MANY6(() => {
|
|
2011
|
+
this.OR4([{ ALT: () => this.CONSUME4(Space) }, { ALT: () => this.CONSUME4(Newline) }]);
|
|
2012
|
+
});
|
|
2013
|
+
});
|
|
1910
2014
|
this.performSelfAnalysis();
|
|
1911
2015
|
}
|
|
1912
2016
|
/** Distinguish `{[number, number], ...}` and the valid empty form `{}` from string lists. */
|
|
@@ -3066,7 +3170,7 @@ function isSetSquareTool(value) {
|
|
|
3066
3170
|
function isToolNode(value) {
|
|
3067
3171
|
return ToolNodeSchema.safeParse(value).success;
|
|
3068
3172
|
}
|
|
3069
|
-
const CHART_TYPES = ["bar", "pictograph"];
|
|
3173
|
+
const CHART_TYPES = ["bar", "pictograph", "grouped-bar"];
|
|
3070
3174
|
const CHART_AXIS_DEFAULTS = { min: 0, max: 100, step: 10, visible: true };
|
|
3071
3175
|
const CHART_MAX_AXIS_TICKS = 200;
|
|
3072
3176
|
const CHART_MAX_PICTO_SLOTS = 60;
|
|
@@ -3146,6 +3250,63 @@ function createChartBar(id, opts) {
|
|
|
3146
3250
|
function isChartBar(value) {
|
|
3147
3251
|
return ChartBarSchema.safeParse(value).success;
|
|
3148
3252
|
}
|
|
3253
|
+
const ChartSeriesSchema = zod.z.object({
|
|
3254
|
+
type: zod.z.literal(AST_TYPES.CHART_SERIES),
|
|
3255
|
+
id: zod.z.string().min(1),
|
|
3256
|
+
color: zod.z.string().min(1),
|
|
3257
|
+
inlines: zod.z.array(DisplayInlineNodeSchema).default([])
|
|
3258
|
+
});
|
|
3259
|
+
function createChartSeries(id, opts) {
|
|
3260
|
+
return ChartSeriesSchema.parse({
|
|
3261
|
+
type: AST_TYPES.CHART_SERIES,
|
|
3262
|
+
id,
|
|
3263
|
+
color: opts.color,
|
|
3264
|
+
inlines: opts.inlines ?? []
|
|
3265
|
+
});
|
|
3266
|
+
}
|
|
3267
|
+
function isChartSeries(value) {
|
|
3268
|
+
return ChartSeriesSchema.safeParse(value).success;
|
|
3269
|
+
}
|
|
3270
|
+
const ChartSeriesBarSchema = zod.z.object({
|
|
3271
|
+
type: zod.z.literal(AST_TYPES.CHART_SERIES_BAR),
|
|
3272
|
+
id: zod.z.string().min(1),
|
|
3273
|
+
seriesId: zod.z.string().min(1),
|
|
3274
|
+
value: zod.z.number().default(0),
|
|
3275
|
+
answer: zod.z.number().optional()
|
|
3276
|
+
});
|
|
3277
|
+
function createChartSeriesBar(id, opts) {
|
|
3278
|
+
return ChartSeriesBarSchema.parse({
|
|
3279
|
+
type: AST_TYPES.CHART_SERIES_BAR,
|
|
3280
|
+
id,
|
|
3281
|
+
seriesId: opts.seriesId,
|
|
3282
|
+
value: opts.value ?? 0,
|
|
3283
|
+
answer: opts.answer
|
|
3284
|
+
});
|
|
3285
|
+
}
|
|
3286
|
+
function isChartSeriesBar(value) {
|
|
3287
|
+
return ChartSeriesBarSchema.safeParse(value).success;
|
|
3288
|
+
}
|
|
3289
|
+
const ChartGroupSchema = zod.z.object({
|
|
3290
|
+
type: zod.z.literal(AST_TYPES.CHART_GROUP),
|
|
3291
|
+
id: zod.z.string().min(1),
|
|
3292
|
+
labelInlines: zod.z.array(InlineNodeSchema).default([]),
|
|
3293
|
+
bars: zod.z.array(ChartSeriesBarSchema).default([])
|
|
3294
|
+
});
|
|
3295
|
+
function createChartGroup(id, opts = {}) {
|
|
3296
|
+
return ChartGroupSchema.parse({
|
|
3297
|
+
type: AST_TYPES.CHART_GROUP,
|
|
3298
|
+
id,
|
|
3299
|
+
labelInlines: opts.labelInlines ?? [],
|
|
3300
|
+
bars: opts.bars ?? []
|
|
3301
|
+
});
|
|
3302
|
+
}
|
|
3303
|
+
function isChartGroup(value) {
|
|
3304
|
+
return ChartGroupSchema.safeParse(value).success;
|
|
3305
|
+
}
|
|
3306
|
+
function alignGroupBarsToSeries(group, series) {
|
|
3307
|
+
const bySeriesId = new Map(group.bars.map((bar) => [bar.seriesId, bar]));
|
|
3308
|
+
return series.map((s) => bySeriesId.get(s.id));
|
|
3309
|
+
}
|
|
3149
3310
|
const ChartBlockSchema = zod.z.object({
|
|
3150
3311
|
type: zod.z.literal(AST_TYPES.CHART_BLOCK),
|
|
3151
3312
|
name: zod.z.string().optional(),
|
|
@@ -3157,7 +3318,10 @@ const ChartBlockSchema = zod.z.object({
|
|
|
3157
3318
|
xAxis: ChartAxisSchema.optional(),
|
|
3158
3319
|
yAxis: ChartAxisSchema.optional(),
|
|
3159
3320
|
bars: zod.z.array(ChartBarSchema).default([]),
|
|
3160
|
-
pictos: zod.z.array(ChartPictoSchema).default([])
|
|
3321
|
+
pictos: zod.z.array(ChartPictoSchema).default([]),
|
|
3322
|
+
legend: zod.z.boolean().default(false),
|
|
3323
|
+
series: zod.z.array(ChartSeriesSchema).default([]),
|
|
3324
|
+
groups: zod.z.array(ChartGroupSchema).default([])
|
|
3161
3325
|
}).superRefine((block, ctx) => {
|
|
3162
3326
|
validateChartBlock(block, ctx);
|
|
3163
3327
|
});
|
|
@@ -3182,10 +3346,10 @@ function validateChartBlock(block, ctx) {
|
|
|
3182
3346
|
const { valueAxis } = resolveChartAxes(block);
|
|
3183
3347
|
validateValueAxisRange(valueAxis, ctx);
|
|
3184
3348
|
if (block.chartType === "bar") {
|
|
3185
|
-
if (block.pictos.length > 0) {
|
|
3349
|
+
if (block.pictos.length > 0 || block.series.length > 0 || block.groups.length > 0) {
|
|
3186
3350
|
ctx.addIssue({
|
|
3187
3351
|
code: zod.z.ZodIssueCode.custom,
|
|
3188
|
-
message: 'CHART[type="bar"]: body must contain BAR items only, not PICTO',
|
|
3352
|
+
message: 'CHART[type="bar"]: body must contain BAR items only, not PICTO, SERIES, or GROUP',
|
|
3189
3353
|
path: ["pictos"]
|
|
3190
3354
|
});
|
|
3191
3355
|
return;
|
|
@@ -3198,16 +3362,16 @@ function validateChartBlock(block, ctx) {
|
|
|
3198
3362
|
});
|
|
3199
3363
|
return;
|
|
3200
3364
|
}
|
|
3201
|
-
validateUniqueIds(block.bars, ctx);
|
|
3365
|
+
validateUniqueIds(block.bars, ctx, (i) => ["bars", i]);
|
|
3202
3366
|
validateChartSnap(block.snap, valueAxis.step, ctx);
|
|
3203
|
-
validateBarAnswers(block.bars, valueAxis, block.snap, ctx);
|
|
3367
|
+
validateBarAnswers(block.bars, valueAxis, block.snap, ctx, (i) => ["bars", i]);
|
|
3204
3368
|
return;
|
|
3205
3369
|
}
|
|
3206
3370
|
if (block.chartType === "pictograph") {
|
|
3207
|
-
if (block.bars.length > 0) {
|
|
3371
|
+
if (block.bars.length > 0 || block.series.length > 0 || block.groups.length > 0) {
|
|
3208
3372
|
ctx.addIssue({
|
|
3209
3373
|
code: zod.z.ZodIssueCode.custom,
|
|
3210
|
-
message: 'CHART[type="pictograph"]: body must contain PICTO items only, not BAR',
|
|
3374
|
+
message: 'CHART[type="pictograph"]: body must contain PICTO items only, not BAR, SERIES, or GROUP',
|
|
3211
3375
|
path: ["bars"]
|
|
3212
3376
|
});
|
|
3213
3377
|
return;
|
|
@@ -3228,7 +3392,7 @@ function validateChartBlock(block, ctx) {
|
|
|
3228
3392
|
});
|
|
3229
3393
|
return;
|
|
3230
3394
|
}
|
|
3231
|
-
validateUniqueIds(block.pictos, ctx);
|
|
3395
|
+
validateUniqueIds(block.pictos, ctx, (i) => ["pictos", i]);
|
|
3232
3396
|
if (!axisHasNumericParams(valueAxis.axis)) {
|
|
3233
3397
|
ctx.addIssue({
|
|
3234
3398
|
code: zod.z.ZodIssueCode.custom,
|
|
@@ -3249,6 +3413,63 @@ function validateChartBlock(block, ctx) {
|
|
|
3249
3413
|
validatePictoAnswers(block.pictos, valueAxis, ctx);
|
|
3250
3414
|
return;
|
|
3251
3415
|
}
|
|
3416
|
+
if (block.chartType === "grouped-bar") {
|
|
3417
|
+
if (block.bars.length > 0 || block.pictos.length > 0) {
|
|
3418
|
+
ctx.addIssue({
|
|
3419
|
+
code: zod.z.ZodIssueCode.custom,
|
|
3420
|
+
message: 'CHART[type="grouped-bar"]: body must contain SERIES and GROUP items only, not BAR or PICTO',
|
|
3421
|
+
path: ["bars"]
|
|
3422
|
+
});
|
|
3423
|
+
return;
|
|
3424
|
+
}
|
|
3425
|
+
if (block.series.length === 0) {
|
|
3426
|
+
ctx.addIssue({
|
|
3427
|
+
code: zod.z.ZodIssueCode.custom,
|
|
3428
|
+
message: 'CHART[type="grouped-bar"]: at least one SERIES is required',
|
|
3429
|
+
path: ["series"]
|
|
3430
|
+
});
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
if (block.groups.length === 0) {
|
|
3434
|
+
ctx.addIssue({
|
|
3435
|
+
code: zod.z.ZodIssueCode.custom,
|
|
3436
|
+
message: 'CHART[type="grouped-bar"]: at least one GROUP is required',
|
|
3437
|
+
path: ["groups"]
|
|
3438
|
+
});
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
validateUniqueIds(block.series, ctx, (i) => ["series", i]);
|
|
3442
|
+
validateUniqueIds(block.groups, ctx, (i) => ["groups", i]);
|
|
3443
|
+
const allBars = block.groups.flatMap((g, gi) => g.bars.map((bar, bi) => ({ ...bar, groupIndex: gi, barIndex: bi })));
|
|
3444
|
+
const pathForFlatBar = (i) => ["groups", allBars[i].groupIndex, "bars", allBars[i].barIndex];
|
|
3445
|
+
validateUniqueIds(allBars, ctx, pathForFlatBar);
|
|
3446
|
+
const seriesIds = new Set(block.series.map((s) => s.id));
|
|
3447
|
+
block.groups.forEach((group, gi) => {
|
|
3448
|
+
const seenInGroup = /* @__PURE__ */ new Set();
|
|
3449
|
+
group.bars.forEach((bar, bi) => {
|
|
3450
|
+
if (!seriesIds.has(bar.seriesId)) {
|
|
3451
|
+
ctx.addIssue({
|
|
3452
|
+
code: zod.z.ZodIssueCode.custom,
|
|
3453
|
+
message: `CHART: GROUP "${group.id}" SERIES_BAR "${bar.id}" references unknown series "${bar.seriesId}" — no SERIES with that id is declared`,
|
|
3454
|
+
path: ["groups", gi, "bars", bi, "seriesId"]
|
|
3455
|
+
});
|
|
3456
|
+
return;
|
|
3457
|
+
}
|
|
3458
|
+
if (seenInGroup.has(bar.seriesId)) {
|
|
3459
|
+
ctx.addIssue({
|
|
3460
|
+
code: zod.z.ZodIssueCode.custom,
|
|
3461
|
+
message: `CHART: GROUP "${group.id}" references series "${bar.seriesId}" twice — each SERIES can appear at most once per GROUP`,
|
|
3462
|
+
path: ["groups", gi, "bars", bi, "seriesId"]
|
|
3463
|
+
});
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3466
|
+
seenInGroup.add(bar.seriesId);
|
|
3467
|
+
});
|
|
3468
|
+
});
|
|
3469
|
+
validateChartSnap(block.snap, valueAxis.step, ctx);
|
|
3470
|
+
validateBarAnswers(allBars, valueAxis, block.snap, ctx, pathForFlatBar);
|
|
3471
|
+
return;
|
|
3472
|
+
}
|
|
3252
3473
|
ctx.addIssue({
|
|
3253
3474
|
code: zod.z.ZodIssueCode.custom,
|
|
3254
3475
|
message: `CHART: unsupported chartType "${block.chartType}" — add a validation branch to validateChartBlock`,
|
|
@@ -3269,14 +3490,14 @@ function resolveNumericAxis(axis) {
|
|
|
3269
3490
|
function hasAmbiguousValueAxes(block) {
|
|
3270
3491
|
return axisHasNumericParams(block.xAxis) && axisHasNumericParams(block.yAxis);
|
|
3271
3492
|
}
|
|
3272
|
-
function validateUniqueIds(items, ctx) {
|
|
3493
|
+
function validateUniqueIds(items, ctx, pathFor) {
|
|
3273
3494
|
const seen = /* @__PURE__ */ new Set();
|
|
3274
3495
|
items.forEach((item, index) => {
|
|
3275
3496
|
if (seen.has(item.id)) {
|
|
3276
3497
|
ctx.addIssue({
|
|
3277
3498
|
code: zod.z.ZodIssueCode.custom,
|
|
3278
3499
|
message: `CHART: duplicate id "${item.id}" — ids must be unique within the chart`,
|
|
3279
|
-
path: [index, "id"]
|
|
3500
|
+
path: [...pathFor(index), "id"]
|
|
3280
3501
|
});
|
|
3281
3502
|
return;
|
|
3282
3503
|
}
|
|
@@ -3318,14 +3539,14 @@ function validateChartSnap(snap, step, ctx) {
|
|
|
3318
3539
|
});
|
|
3319
3540
|
}
|
|
3320
3541
|
}
|
|
3321
|
-
function validateBarAnswers(bars, valueAxis, snap, ctx) {
|
|
3542
|
+
function validateBarAnswers(bars, valueAxis, snap, ctx, pathFor) {
|
|
3322
3543
|
const gridStep = snap ?? valueAxis.step;
|
|
3323
3544
|
bars.forEach((bar, index) => {
|
|
3324
3545
|
if (bar.value < valueAxis.min || bar.value > valueAxis.max) {
|
|
3325
3546
|
ctx.addIssue({
|
|
3326
3547
|
code: zod.z.ZodIssueCode.custom,
|
|
3327
3548
|
message: `CHART: BAR "${bar.id}" value (${bar.value}) is outside axis range [${valueAxis.min}, ${valueAxis.max}] — the bar will not render correctly`,
|
|
3328
|
-
path: [
|
|
3549
|
+
path: [...pathFor(index), "value"]
|
|
3329
3550
|
});
|
|
3330
3551
|
}
|
|
3331
3552
|
if (bar.answer === void 0) return;
|
|
@@ -3333,7 +3554,7 @@ function validateBarAnswers(bars, valueAxis, snap, ctx) {
|
|
|
3333
3554
|
ctx.addIssue({
|
|
3334
3555
|
code: zod.z.ZodIssueCode.custom,
|
|
3335
3556
|
message: `CHART: BAR "${bar.id}" answer (${bar.answer}) is outside axis range [${valueAxis.min}, ${valueAxis.max}] — the student can never reach this value`,
|
|
3336
|
-
path: [
|
|
3557
|
+
path: [...pathFor(index), "answer"]
|
|
3337
3558
|
});
|
|
3338
3559
|
return;
|
|
3339
3560
|
}
|
|
@@ -3342,7 +3563,7 @@ function validateBarAnswers(bars, valueAxis, snap, ctx) {
|
|
|
3342
3563
|
ctx.addIssue({
|
|
3343
3564
|
code: zod.z.ZodIssueCode.custom,
|
|
3344
3565
|
message: `CHART: BAR "${bar.id}" answer (${bar.answer}) is not on the drag grid (min ${valueAxis.min} + multiples of ${gridStep}) — the student can never land on it. Align the answer to the step/snap grid.`,
|
|
3345
|
-
path: [
|
|
3566
|
+
path: [...pathFor(index), "answer"]
|
|
3346
3567
|
});
|
|
3347
3568
|
}
|
|
3348
3569
|
});
|
|
@@ -3404,7 +3625,10 @@ function createChartBlock(chartType, opts = {}) {
|
|
|
3404
3625
|
xAxis: opts.xAxis,
|
|
3405
3626
|
yAxis: opts.yAxis,
|
|
3406
3627
|
bars: opts.bars ?? [],
|
|
3407
|
-
pictos: opts.pictos ?? []
|
|
3628
|
+
pictos: opts.pictos ?? [],
|
|
3629
|
+
series: opts.series ?? [],
|
|
3630
|
+
groups: opts.groups ?? [],
|
|
3631
|
+
legend: opts.legend ?? false
|
|
3408
3632
|
});
|
|
3409
3633
|
}
|
|
3410
3634
|
function isChartBlock(value) {
|
|
@@ -3491,7 +3715,9 @@ const ExerciseDocumentSchema = zod.z.object({
|
|
|
3491
3715
|
};
|
|
3492
3716
|
const visitInlines = (inlines, basePath) => {
|
|
3493
3717
|
inlines.forEach((inline, i) => {
|
|
3494
|
-
if (inline.type === AST_TYPES.INLINE_INPUT || inline.type === AST_TYPES.FRACTION || inline.type === AST_TYPES.IMAGE) {
|
|
3718
|
+
if (inline.type === AST_TYPES.INLINE_INPUT || inline.type === AST_TYPES.FRACTION || inline.type === AST_TYPES.IMAGE || inline.type === AST_TYPES.VISUAL_FRACTION) {
|
|
3719
|
+
record(inline.name, [...basePath, i, "name"]);
|
|
3720
|
+
} else if (inline.type === AST_TYPES.CLOCK && inline.mode === "interactive") {
|
|
3495
3721
|
record(inline.name, [...basePath, i, "name"]);
|
|
3496
3722
|
}
|
|
3497
3723
|
});
|
|
@@ -3523,6 +3749,28 @@ const ExerciseDocumentSchema = zod.z.object({
|
|
|
3523
3749
|
pm.groups.flat().forEach((item, ii) => {
|
|
3524
3750
|
visitInlines(item.inlines, ["lines", bi, "items", ii, "inlines"]);
|
|
3525
3751
|
});
|
|
3752
|
+
} else if (block.type === AST_TYPES.CHART_BLOCK) {
|
|
3753
|
+
const chart = block;
|
|
3754
|
+
const base = ["lines", bi];
|
|
3755
|
+
chart.bars.forEach((bar, i) => {
|
|
3756
|
+
if (bar.answer !== void 0) record(bar.id, [...base, "bars", i, "id"]);
|
|
3757
|
+
visitInlines(bar.inlines, [...base, "bars", i, "inlines"]);
|
|
3758
|
+
});
|
|
3759
|
+
chart.pictos.forEach((picto, i) => {
|
|
3760
|
+
if (picto.answer !== void 0) record(picto.id, [...base, "pictos", i, "id"]);
|
|
3761
|
+
visitInlines(picto.inlines, [...base, "pictos", i, "inlines"]);
|
|
3762
|
+
});
|
|
3763
|
+
chart.groups.forEach((group, gi) => {
|
|
3764
|
+
group.bars.forEach((bar, i) => {
|
|
3765
|
+
if (bar.answer !== void 0) record(bar.id, [...base, "groups", gi, "bars", i, "id"]);
|
|
3766
|
+
});
|
|
3767
|
+
visitInlines(group.labelInlines, [...base, "groups", gi, "labelInlines"]);
|
|
3768
|
+
});
|
|
3769
|
+
chart.series.forEach((series, i) => {
|
|
3770
|
+
visitInlines(series.inlines, [...base, "series", i, "inlines"]);
|
|
3771
|
+
});
|
|
3772
|
+
if (chart.xAxis) visitInlines(chart.xAxis.inlines, [...base, "xAxis", "inlines"]);
|
|
3773
|
+
if (chart.yAxis) visitInlines(chart.yAxis.inlines, [...base, "yAxis", "inlines"]);
|
|
3526
3774
|
} else if (block.type === AST_TYPES.RADIO_OPTIONS) {
|
|
3527
3775
|
record(block.name, ["lines", bi, "name"]);
|
|
3528
3776
|
} else if (block.type === AST_TYPES.GEOMETRY_BLOCK && block.name !== void 0) {
|
|
@@ -4094,7 +4342,7 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4094
4342
|
chartBlock(ctx) {
|
|
4095
4343
|
const params = this.visit(ctx.parameterList);
|
|
4096
4344
|
const chartType = params.type;
|
|
4097
|
-
if (!chartType) throw new Error(
|
|
4345
|
+
if (!chartType) throw new Error(`CHART: type is required (${CHART_TYPES.map((t) => `"${t}"`).join(" | ")})`);
|
|
4098
4346
|
if (!CHART_TYPES.includes(chartType)) {
|
|
4099
4347
|
throw new Error(`CHART: type must be one of ${CHART_TYPES.join(", ")} (got "${chartType}")`);
|
|
4100
4348
|
}
|
|
@@ -4113,7 +4361,26 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4113
4361
|
const yAxis = ctx.chartYAxis ? this.visit(ctx.chartYAxis) : void 0;
|
|
4114
4362
|
const bars = ctx.chartBarItem ? ctx.chartBarItem.map((b) => this.visit(b)) : [];
|
|
4115
4363
|
const pictos = ctx.chartPictoItem ? ctx.chartPictoItem.map((p) => this.visit(p)) : [];
|
|
4116
|
-
|
|
4364
|
+
const series = ctx.chartSeriesItem ? ctx.chartSeriesItem.map((s) => this.visit(s)) : [];
|
|
4365
|
+
const groups = ctx.chartGroupItem ? ctx.chartGroupItem.map((g) => this.visit(g)) : [];
|
|
4366
|
+
if (params.legend !== void 0 && chartType !== "grouped-bar") {
|
|
4367
|
+
throw new Error(`CHART: "legend" is only supported for type="grouped-bar" (got "${chartType}")`);
|
|
4368
|
+
}
|
|
4369
|
+
const legend = params.legend === true || params.legend === "true";
|
|
4370
|
+
return createChartBlock(chartType, {
|
|
4371
|
+
bars,
|
|
4372
|
+
pictos,
|
|
4373
|
+
series,
|
|
4374
|
+
groups,
|
|
4375
|
+
legend,
|
|
4376
|
+
name,
|
|
4377
|
+
title,
|
|
4378
|
+
grid,
|
|
4379
|
+
scale,
|
|
4380
|
+
snap,
|
|
4381
|
+
xAxis,
|
|
4382
|
+
yAxis
|
|
4383
|
+
});
|
|
4117
4384
|
}
|
|
4118
4385
|
/** X_AXIS and Y_AXIS carry identical params — only the produced AST type differs.
|
|
4119
4386
|
* Chevrotain needs two separate grammar rules, but one visitor body serves both. */
|
|
@@ -4152,6 +4419,52 @@ class NaturalDSLVisitor extends BaseCstVisitor {
|
|
|
4152
4419
|
inlines
|
|
4153
4420
|
});
|
|
4154
4421
|
}
|
|
4422
|
+
chartSeriesItem(ctx) {
|
|
4423
|
+
const params = this.visit(ctx.parameterList);
|
|
4424
|
+
const inlines = ctx.inlineElement ? ctx.inlineElement.map((e) => this.visit(e)) : [];
|
|
4425
|
+
if (inlines.some((n) => !DisplayInlineNodeSchema.safeParse(n).success)) {
|
|
4426
|
+
throw new Error(
|
|
4427
|
+
`CHART: SERIES "${params.id}" label must be display-only — inputs, drag options, clocks and interactive fractions are not allowed in a legend label`
|
|
4428
|
+
);
|
|
4429
|
+
}
|
|
4430
|
+
if (!params.color) {
|
|
4431
|
+
throw new Error(`CHART: SERIES "${params.id}" requires "color"`);
|
|
4432
|
+
}
|
|
4433
|
+
return createChartSeries(params.id, {
|
|
4434
|
+
color: params.color,
|
|
4435
|
+
inlines
|
|
4436
|
+
});
|
|
4437
|
+
}
|
|
4438
|
+
chartGroupLabel(ctx) {
|
|
4439
|
+
return ctx.inlineElement ? ctx.inlineElement.map((e) => this.visit(e)) : [];
|
|
4440
|
+
}
|
|
4441
|
+
chartGroupItem(ctx) {
|
|
4442
|
+
const params = this.visit(ctx.parameterList);
|
|
4443
|
+
if (ctx.chartGroupLabel && ctx.chartGroupLabel.length > 1) {
|
|
4444
|
+
throw new Error(`CHART: GROUP "${params.id}" has more than one LABEL — only one is allowed`);
|
|
4445
|
+
}
|
|
4446
|
+
const labelInlines = ctx.chartGroupLabel ? this.visit(ctx.chartGroupLabel) : [];
|
|
4447
|
+
const bars = ctx.chartSeriesBarItem ? ctx.chartSeriesBarItem.map((b) => this.visit(b)) : [];
|
|
4448
|
+
return createChartGroup(params.id, { labelInlines, bars });
|
|
4449
|
+
}
|
|
4450
|
+
// `params.series` is the raw DSL attribute name (SERIES_BAR[series="..."])
|
|
4451
|
+
// — unrelated to the AST field's own `seriesId` name.
|
|
4452
|
+
chartSeriesBarItem(ctx) {
|
|
4453
|
+
const params = this.visit(ctx.parameterList);
|
|
4454
|
+
if (!params.series) {
|
|
4455
|
+
throw new Error(`CHART: SERIES_BAR "${params.id}" requires "series"`);
|
|
4456
|
+
}
|
|
4457
|
+
if (params.color !== void 0) {
|
|
4458
|
+
throw new Error(
|
|
4459
|
+
`CHART: SERIES_BAR "${params.id}" cannot set "color" — it comes from its SERIES`
|
|
4460
|
+
);
|
|
4461
|
+
}
|
|
4462
|
+
return createChartSeriesBar(params.id, {
|
|
4463
|
+
seriesId: params.series,
|
|
4464
|
+
value: params.value !== void 0 ? Number(params.value) : void 0,
|
|
4465
|
+
answer: params.answer !== void 0 ? Number(params.answer) : void 0
|
|
4466
|
+
});
|
|
4467
|
+
}
|
|
4155
4468
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
4156
4469
|
// Tools
|
|
4157
4470
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -4606,23 +4919,23 @@ const ChartAxisJSONSchema = zod.z.object({
|
|
|
4606
4919
|
});
|
|
4607
4920
|
}
|
|
4608
4921
|
});
|
|
4609
|
-
const
|
|
4610
|
-
zod.z.
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
id: zod.z.string(),
|
|
4620
|
-
type: zod.z.literal(JSON_TYPES.CHART_BAR),
|
|
4922
|
+
const ChartBarJSONBaseSchema = zod.z.object({
|
|
4923
|
+
id: zod.z.string(),
|
|
4924
|
+
type: zod.z.literal(JSON_TYPES.CHART_BAR),
|
|
4925
|
+
value: zod.z.number(),
|
|
4926
|
+
answer: zod.z.number().optional(),
|
|
4927
|
+
color: zod.z.string().optional(),
|
|
4928
|
+
label: zod.z.array(LayoutItemSchema).optional()
|
|
4929
|
+
});
|
|
4930
|
+
const ChartBarJSONSchema = zod.z.union([
|
|
4931
|
+
ChartBarJSONBaseSchema.extend({
|
|
4621
4932
|
interactive: zod.z.literal(true),
|
|
4622
|
-
|
|
4623
|
-
widgetId: zod.z.string()
|
|
4624
|
-
|
|
4625
|
-
|
|
4933
|
+
/** The widgets/answers key, equal to `id` when compiled. */
|
|
4934
|
+
widgetId: zod.z.string()
|
|
4935
|
+
}),
|
|
4936
|
+
ChartBarJSONBaseSchema.extend({
|
|
4937
|
+
interactive: zod.z.literal(false).optional(),
|
|
4938
|
+
widgetId: zod.z.undefined().optional()
|
|
4626
4939
|
})
|
|
4627
4940
|
]);
|
|
4628
4941
|
const ChartPictoJSONSchema = zod.z.object({
|
|
@@ -4636,21 +4949,55 @@ const ChartPictoJSONSchema = zod.z.object({
|
|
|
4636
4949
|
),
|
|
4637
4950
|
label: zod.z.array(LayoutItemSchema).optional()
|
|
4638
4951
|
});
|
|
4639
|
-
const
|
|
4952
|
+
const ChartSeriesJSONSchema = zod.z.object({
|
|
4953
|
+
id: zod.z.string(),
|
|
4954
|
+
color: zod.z.string(),
|
|
4955
|
+
label: zod.z.array(LayoutItemSchema).optional()
|
|
4956
|
+
});
|
|
4957
|
+
const ChartSeriesBarJSONSchema = zod.z.object({
|
|
4958
|
+
id: zod.z.string(),
|
|
4959
|
+
type: zod.z.literal(JSON_TYPES.CHART_SERIES_BAR),
|
|
4960
|
+
value: zod.z.number(),
|
|
4961
|
+
answer: zod.z.number().optional(),
|
|
4962
|
+
color: zod.z.string(),
|
|
4963
|
+
seriesId: zod.z.string()
|
|
4964
|
+
});
|
|
4965
|
+
const ChartGroupJSONSchema = zod.z.object({
|
|
4966
|
+
id: zod.z.string(),
|
|
4967
|
+
label: zod.z.array(LayoutItemSchema).optional(),
|
|
4968
|
+
bars: zod.z.array(ChartSeriesBarJSONSchema)
|
|
4969
|
+
});
|
|
4970
|
+
const ChartItemBaseSchema = zod.z.object({
|
|
4640
4971
|
id: zod.z.string(),
|
|
4641
4972
|
type: zod.z.literal(JSON_TYPES.CHART),
|
|
4642
4973
|
name: zod.z.string().optional(),
|
|
4643
4974
|
title: zod.z.string().optional(),
|
|
4644
|
-
chartType: zod.z.enum(CHART_TYPES),
|
|
4645
4975
|
orientation: zod.z.enum(["vertical", "horizontal"]).default("vertical"),
|
|
4646
4976
|
grid: zod.z.boolean().default(false),
|
|
4647
4977
|
scale: zod.z.number().positive().default(1),
|
|
4648
4978
|
snap: zod.z.number().positive().optional(),
|
|
4649
4979
|
xAxis: ChartAxisJSONSchema.optional(),
|
|
4650
|
-
yAxis: ChartAxisJSONSchema.optional()
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4980
|
+
yAxis: ChartAxisJSONSchema.optional()
|
|
4981
|
+
});
|
|
4982
|
+
const ChartItemBarSchema = ChartItemBaseSchema.extend({
|
|
4983
|
+
chartType: zod.z.literal("bar"),
|
|
4984
|
+
bars: zod.z.array(ChartBarJSONSchema)
|
|
4985
|
+
});
|
|
4986
|
+
const ChartItemPictographSchema = ChartItemBaseSchema.extend({
|
|
4987
|
+
chartType: zod.z.literal("pictograph"),
|
|
4988
|
+
pictos: zod.z.array(ChartPictoJSONSchema)
|
|
4989
|
+
});
|
|
4990
|
+
const ChartItemGroupedBarSchema = ChartItemBaseSchema.extend({
|
|
4991
|
+
chartType: zod.z.literal("grouped-bar"),
|
|
4992
|
+
series: zod.z.array(ChartSeriesJSONSchema),
|
|
4993
|
+
groups: zod.z.array(ChartGroupJSONSchema),
|
|
4994
|
+
legend: zod.z.boolean().default(false)
|
|
4995
|
+
});
|
|
4996
|
+
const ChartItemSchema = zod.z.discriminatedUnion("chartType", [
|
|
4997
|
+
ChartItemBarSchema,
|
|
4998
|
+
ChartItemPictographSchema,
|
|
4999
|
+
ChartItemGroupedBarSchema
|
|
5000
|
+
]).superRefine((item, ctx) => {
|
|
4654
5001
|
if (item.chartType === "bar" && item.bars.length === 0) {
|
|
4655
5002
|
ctx.addIssue({
|
|
4656
5003
|
code: zod.z.ZodIssueCode.custom,
|
|
@@ -4658,18 +5005,63 @@ const ChartItemSchema = zod.z.object({
|
|
|
4658
5005
|
path: ["bars"]
|
|
4659
5006
|
});
|
|
4660
5007
|
}
|
|
4661
|
-
if (item.chartType === "pictograph"
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
5008
|
+
if (item.chartType === "pictograph") {
|
|
5009
|
+
if (item.pictos.length === 0) {
|
|
5010
|
+
ctx.addIssue({
|
|
5011
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5012
|
+
message: 'CHART[type="pictograph"]: at least one PICTO is required',
|
|
5013
|
+
path: ["pictos"]
|
|
5014
|
+
});
|
|
5015
|
+
}
|
|
5016
|
+
if (item.snap !== void 0) {
|
|
5017
|
+
ctx.addIssue({
|
|
5018
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5019
|
+
message: 'CHART[type="pictograph"]: snap is not supported for pictograph charts',
|
|
5020
|
+
path: ["snap"]
|
|
5021
|
+
});
|
|
5022
|
+
}
|
|
4667
5023
|
}
|
|
4668
|
-
if (item.chartType === "
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
5024
|
+
if (item.chartType === "grouped-bar") {
|
|
5025
|
+
if (item.series.length === 0) {
|
|
5026
|
+
ctx.addIssue({
|
|
5027
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5028
|
+
message: 'CHART[type="grouped-bar"]: at least one SERIES is required',
|
|
5029
|
+
path: ["series"]
|
|
5030
|
+
});
|
|
5031
|
+
}
|
|
5032
|
+
if (item.groups.length === 0) {
|
|
5033
|
+
ctx.addIssue({
|
|
5034
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5035
|
+
message: 'CHART[type="grouped-bar"]: at least one GROUP is required',
|
|
5036
|
+
path: ["groups"]
|
|
5037
|
+
});
|
|
5038
|
+
}
|
|
5039
|
+
validateUniqueIds(item.series, ctx, (i) => ["series", i]);
|
|
5040
|
+
validateUniqueIds(item.groups, ctx, (i) => ["groups", i]);
|
|
5041
|
+
const allBars = item.groups.flatMap((g, gi) => g.bars.map((bar, bi) => ({ id: bar.id, gi, bi })));
|
|
5042
|
+
validateUniqueIds(allBars, ctx, (i) => ["groups", allBars[i].gi, "bars", allBars[i].bi]);
|
|
5043
|
+
const seriesIds = new Set(item.series.map((s) => s.id));
|
|
5044
|
+
item.groups.forEach((group, gi) => {
|
|
5045
|
+
const seenInGroup = /* @__PURE__ */ new Set();
|
|
5046
|
+
group.bars.forEach((bar, bi) => {
|
|
5047
|
+
if (!seriesIds.has(bar.seriesId)) {
|
|
5048
|
+
ctx.addIssue({
|
|
5049
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5050
|
+
message: `CHART[type="grouped-bar"]: GROUP "${group.id}" bar "${bar.id}" references unknown seriesId "${bar.seriesId}" — no SERIES with that id is declared`,
|
|
5051
|
+
path: ["groups", gi, "bars", bi, "seriesId"]
|
|
5052
|
+
});
|
|
5053
|
+
return;
|
|
5054
|
+
}
|
|
5055
|
+
if (seenInGroup.has(bar.seriesId)) {
|
|
5056
|
+
ctx.addIssue({
|
|
5057
|
+
code: zod.z.ZodIssueCode.custom,
|
|
5058
|
+
message: `CHART[type="grouped-bar"]: GROUP "${group.id}" has two bars for seriesId "${bar.seriesId}" — each series can appear at most once per group`,
|
|
5059
|
+
path: ["groups", gi, "bars", bi, "seriesId"]
|
|
5060
|
+
});
|
|
5061
|
+
return;
|
|
5062
|
+
}
|
|
5063
|
+
seenInGroup.add(bar.seriesId);
|
|
5064
|
+
});
|
|
4673
5065
|
});
|
|
4674
5066
|
}
|
|
4675
5067
|
});
|
|
@@ -10768,6 +11160,8 @@ function collectAllInlines(blocks) {
|
|
|
10768
11160
|
for (const picto of chart.pictos) result.push(...picto.inlines);
|
|
10769
11161
|
if (chart.xAxis) result.push(...chart.xAxis.inlines);
|
|
10770
11162
|
if (chart.yAxis) result.push(...chart.yAxis.inlines);
|
|
11163
|
+
for (const series of chart.series) result.push(...series.inlines);
|
|
11164
|
+
for (const group of chart.groups) result.push(...group.labelInlines);
|
|
10771
11165
|
}
|
|
10772
11166
|
}
|
|
10773
11167
|
return result;
|
|
@@ -10802,27 +11196,19 @@ function transformChart(block, idGenerator, inlineDragIds) {
|
|
|
10802
11196
|
const chartAnswers = {};
|
|
10803
11197
|
const bars = block.bars.map((bar) => {
|
|
10804
11198
|
const label = toLabel(bar.inlines);
|
|
10805
|
-
|
|
10806
|
-
chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
|
|
10807
|
-
chartAnswers[bar.id] = bar.answer;
|
|
10808
|
-
return {
|
|
10809
|
-
id: bar.id,
|
|
10810
|
-
type: JSON_TYPES.CHART_BAR,
|
|
10811
|
-
interactive: true,
|
|
10812
|
-
value: bar.value,
|
|
10813
|
-
widgetId: bar.id,
|
|
10814
|
-
...bar.color !== void 0 && { color: bar.color },
|
|
10815
|
-
...label !== void 0 && { label }
|
|
10816
|
-
};
|
|
10817
|
-
}
|
|
10818
|
-
return {
|
|
11199
|
+
const common = {
|
|
10819
11200
|
id: bar.id,
|
|
10820
11201
|
type: JSON_TYPES.CHART_BAR,
|
|
10821
|
-
interactive: false,
|
|
10822
11202
|
value: bar.value,
|
|
10823
11203
|
...bar.color !== void 0 && { color: bar.color },
|
|
10824
11204
|
...label !== void 0 && { label }
|
|
10825
11205
|
};
|
|
11206
|
+
if (bar.answer !== void 0) {
|
|
11207
|
+
chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
|
|
11208
|
+
chartAnswers[bar.id] = bar.answer;
|
|
11209
|
+
return { ...common, interactive: true, widgetId: bar.id, answer: bar.answer };
|
|
11210
|
+
}
|
|
11211
|
+
return { ...common, interactive: false };
|
|
10826
11212
|
});
|
|
10827
11213
|
const pictos = block.pictos.map((picto) => {
|
|
10828
11214
|
const label = toLabel(picto.inlines);
|
|
@@ -10839,21 +11225,62 @@ function transformChart(block, idGenerator, inlineDragIds) {
|
|
|
10839
11225
|
...label !== void 0 && { label }
|
|
10840
11226
|
};
|
|
10841
11227
|
});
|
|
10842
|
-
const
|
|
11228
|
+
const series = block.series.map((s) => {
|
|
11229
|
+
const label = toLabel(s.inlines);
|
|
11230
|
+
return {
|
|
11231
|
+
id: s.id,
|
|
11232
|
+
color: s.color,
|
|
11233
|
+
...label !== void 0 && { label }
|
|
11234
|
+
};
|
|
11235
|
+
});
|
|
11236
|
+
const groups = block.groups.map((group) => {
|
|
11237
|
+
const slots = alignGroupBarsToSeries(group, block.series);
|
|
11238
|
+
const groupBars = slots.map((bar, i) => {
|
|
11239
|
+
if (bar === void 0) return void 0;
|
|
11240
|
+
const series2 = block.series[i];
|
|
11241
|
+
if (bar.answer !== void 0) {
|
|
11242
|
+
chartWidgets[bar.id] = { type: JSON_TYPES.CHART_BAR, value: bar.value };
|
|
11243
|
+
chartAnswers[bar.id] = bar.answer;
|
|
11244
|
+
}
|
|
11245
|
+
return {
|
|
11246
|
+
id: bar.id,
|
|
11247
|
+
type: JSON_TYPES.CHART_SERIES_BAR,
|
|
11248
|
+
value: bar.value,
|
|
11249
|
+
...bar.answer !== void 0 && { answer: bar.answer },
|
|
11250
|
+
color: series2.color,
|
|
11251
|
+
seriesId: series2.id
|
|
11252
|
+
};
|
|
11253
|
+
}).filter((bar) => bar !== void 0);
|
|
11254
|
+
const label = toLabel(group.labelInlines);
|
|
11255
|
+
return {
|
|
11256
|
+
id: group.id,
|
|
11257
|
+
...label !== void 0 && { label },
|
|
11258
|
+
bars: groupBars
|
|
11259
|
+
};
|
|
11260
|
+
});
|
|
11261
|
+
const base = {
|
|
10843
11262
|
id: idGenerator.generate(JSON_TYPES.CHART),
|
|
10844
11263
|
type: JSON_TYPES.CHART,
|
|
10845
11264
|
...block.name !== void 0 && { name: block.name },
|
|
10846
11265
|
...block.title !== void 0 && { title: block.title },
|
|
10847
|
-
chartType: block.chartType,
|
|
10848
11266
|
orientation,
|
|
10849
11267
|
grid: block.grid,
|
|
10850
11268
|
scale: block.scale,
|
|
10851
11269
|
...block.snap !== void 0 && { snap: block.snap },
|
|
10852
11270
|
...xAxis !== void 0 && { xAxis },
|
|
10853
|
-
...yAxis !== void 0 && { yAxis }
|
|
10854
|
-
bars,
|
|
10855
|
-
pictos
|
|
11271
|
+
...yAxis !== void 0 && { yAxis }
|
|
10856
11272
|
};
|
|
11273
|
+
let item;
|
|
11274
|
+
if (block.chartType === "bar") {
|
|
11275
|
+
item = { ...base, chartType: "bar", bars };
|
|
11276
|
+
} else if (block.chartType === "pictograph") {
|
|
11277
|
+
item = { ...base, chartType: "pictograph", pictos };
|
|
11278
|
+
} else if (block.chartType === "grouped-bar") {
|
|
11279
|
+
item = { ...base, chartType: "grouped-bar", series, groups, legend: block.legend };
|
|
11280
|
+
} else {
|
|
11281
|
+
const unhandled = block.chartType;
|
|
11282
|
+
throw new Error(`CHART: unsupported chart type "${String(unhandled)}"`);
|
|
11283
|
+
}
|
|
10857
11284
|
return { item, chartWidgets, chartAnswers };
|
|
10858
11285
|
}
|
|
10859
11286
|
function transformNumberLine(numberLine, idGenerator, inlineDragIds) {
|
|
@@ -17518,12 +17945,17 @@ const GridSymmetry = ({
|
|
|
17518
17945
|
}
|
|
17519
17946
|
) });
|
|
17520
17947
|
};
|
|
17948
|
+
function interactiveBarKey(bar) {
|
|
17949
|
+
if (bar.answer === void 0 && bar.interactive !== true) return void 0;
|
|
17950
|
+
return bar.widgetId ?? bar.id;
|
|
17951
|
+
}
|
|
17521
17952
|
function getChartClassName(item) {
|
|
17522
17953
|
const isHorizontal = item.orientation === "horizontal";
|
|
17523
17954
|
return [
|
|
17524
17955
|
"chart",
|
|
17525
17956
|
isHorizontal ? "chart--horizontal" : "",
|
|
17526
17957
|
item.chartType === "pictograph" ? "chart--pictograph" : "",
|
|
17958
|
+
item.chartType === "grouped-bar" ? "chart--grouped-bar" : "",
|
|
17527
17959
|
item.xAxis?.visible === false ? "chart--no-xaxis" : "",
|
|
17528
17960
|
item.yAxis?.visible === false ? "chart--no-yaxis" : ""
|
|
17529
17961
|
].filter(Boolean).join(" ");
|
|
@@ -17534,7 +17966,8 @@ function resolveItemValue(id, defaultValue2, values) {
|
|
|
17534
17966
|
return isNaN(parsed) ? defaultValue2 : parsed;
|
|
17535
17967
|
}
|
|
17536
17968
|
function resolveValue(bar, values) {
|
|
17537
|
-
|
|
17969
|
+
const widgetKey = interactiveBarKey(bar);
|
|
17970
|
+
return widgetKey !== void 0 ? resolveItemValue(widgetKey, bar.value, values) : bar.value;
|
|
17538
17971
|
}
|
|
17539
17972
|
function clamp(v, min, max) {
|
|
17540
17973
|
return Math.min(max, Math.max(min, v));
|
|
@@ -17593,6 +18026,8 @@ const CHART_METRICS = {
|
|
|
17593
18026
|
gapWidth: 1,
|
|
17594
18027
|
axisWidth: 2.35,
|
|
17595
18028
|
barWidth: 3.35,
|
|
18029
|
+
// Fixed narrow width so several series fit in one cluster; not derived from count.
|
|
18030
|
+
groupedBarWidth: 1.8,
|
|
17596
18031
|
fontSize: 0.9,
|
|
17597
18032
|
labelFontSize: 0.98,
|
|
17598
18033
|
handleSize: 1.15,
|
|
@@ -17645,6 +18080,41 @@ function computePictographChartStyle(scale, columnCount, totalSlots, orientation
|
|
|
17645
18080
|
"--chart-height": slotSpan
|
|
17646
18081
|
};
|
|
17647
18082
|
}
|
|
18083
|
+
function computeGroupedChartStyle(scale, groupCount, seriesCount, orientation = "vertical", isInteractive = false) {
|
|
18084
|
+
const visualScale = clamp(scale, CHART_METRICS.scaleMin, CHART_METRICS.scaleMax);
|
|
18085
|
+
const barWidth = (isInteractive ? CHART_METRICS.barWidth : CHART_METRICS.groupedBarWidth) * visualScale;
|
|
18086
|
+
const barGap = CHART_METRICS.gapWidth * visualScale * 0.5;
|
|
18087
|
+
const groupGap = CHART_METRICS.gapWidth * visualScale * 2;
|
|
18088
|
+
const seriesPerGroup = Math.max(1, seriesCount);
|
|
18089
|
+
const groupWidth = seriesPerGroup * barWidth + Math.max(0, seriesPerGroup - 1) * barGap;
|
|
18090
|
+
const trackLength = groupCount * groupWidth + Math.max(0, groupCount - 1) * groupGap + groupGap * 2;
|
|
18091
|
+
const cssVars = {
|
|
18092
|
+
"--chart-axis-width": `${CHART_METRICS.axisWidth * visualScale}rem`,
|
|
18093
|
+
"--chart-bar-width": `${barWidth}rem`,
|
|
18094
|
+
// A bar slot inside a group is just the bar; reuse the plain bar-chart var.
|
|
18095
|
+
"--chart-bar-slot-width": `${barWidth}rem`,
|
|
18096
|
+
"--chart-bar-gap": `${barGap}rem`,
|
|
18097
|
+
"--chart-group-gap": `${groupGap}rem`,
|
|
18098
|
+
"--chart-group-width": `${groupWidth}rem`,
|
|
18099
|
+
"--chart-font-size": `${CHART_METRICS.fontSize * visualScale}rem`,
|
|
18100
|
+
"--chart-label-font-size": `${CHART_METRICS.labelFontSize * visualScale}rem`,
|
|
18101
|
+
"--chart-handle-size": `${CHART_METRICS.handleSize * visualScale}rem`
|
|
18102
|
+
};
|
|
18103
|
+
if (orientation === "horizontal") {
|
|
18104
|
+
const valueWidth = CHART_METRICS.chartHeight * visualScale;
|
|
18105
|
+
return {
|
|
18106
|
+
...cssVars,
|
|
18107
|
+
"--chart-height": `${trackLength}rem`,
|
|
18108
|
+
"--chart-h-value-width": `${valueWidth}rem`,
|
|
18109
|
+
"--chart-content-width": `${valueWidth}rem`
|
|
18110
|
+
};
|
|
18111
|
+
}
|
|
18112
|
+
return {
|
|
18113
|
+
...cssVars,
|
|
18114
|
+
"--chart-height": `${CHART_METRICS.chartHeight * visualScale}rem`,
|
|
18115
|
+
"--chart-content-width": `${trackLength}rem`
|
|
18116
|
+
};
|
|
18117
|
+
}
|
|
17648
18118
|
function computeHorizontalChartStyle(scale, barCount) {
|
|
17649
18119
|
const { trackLength, visualScale, cssVars } = computeBaseChartVars(scale, barCount);
|
|
17650
18120
|
const valueWidth = CHART_METRICS.chartHeight * visualScale;
|
|
@@ -17696,6 +18166,7 @@ const DraggableBar = ({
|
|
|
17696
18166
|
const axisMax = axis.max ?? CHART_AXIS_DEFAULTS.max;
|
|
17697
18167
|
const axisStep = axis.step ?? CHART_AXIS_DEFAULTS.step;
|
|
17698
18168
|
const snapStep = snap ?? axisStep;
|
|
18169
|
+
const widgetKey = bar.widgetId ?? bar.id;
|
|
17699
18170
|
const fillFromPointer = React.useCallback(
|
|
17700
18171
|
(clientY, clientX) => {
|
|
17701
18172
|
const track = trackRef.current;
|
|
@@ -17713,10 +18184,10 @@ const DraggableBar = ({
|
|
|
17713
18184
|
if (snapped !== lastSnap.current) {
|
|
17714
18185
|
lastSnap.current = snapped;
|
|
17715
18186
|
playSound("bar-step");
|
|
17716
|
-
onChange(
|
|
18187
|
+
onChange(widgetKey, String(snapped));
|
|
17717
18188
|
}
|
|
17718
18189
|
},
|
|
17719
|
-
[axisMin, axisMax, snapStep,
|
|
18190
|
+
[axisMin, axisMax, snapStep, widgetKey, onChange, orientation, playSound]
|
|
17720
18191
|
);
|
|
17721
18192
|
const onPointerDown = React.useCallback(
|
|
17722
18193
|
(e) => {
|
|
@@ -17749,8 +18220,8 @@ const DraggableBar = ({
|
|
|
17749
18220
|
if (!dragging.current) return;
|
|
17750
18221
|
dragging.current = false;
|
|
17751
18222
|
setIsDragging(false);
|
|
17752
|
-
onChange(
|
|
17753
|
-
}, [
|
|
18223
|
+
onChange(widgetKey, String(preDragValue.current));
|
|
18224
|
+
}, [widgetKey, onChange]);
|
|
17754
18225
|
const onKeyDown = React.useCallback(
|
|
17755
18226
|
(e) => {
|
|
17756
18227
|
if (exerciseLocked) return;
|
|
@@ -17759,22 +18230,22 @@ const DraggableBar = ({
|
|
|
17759
18230
|
if (isInc) {
|
|
17760
18231
|
e.preventDefault();
|
|
17761
18232
|
playSound("bar-step");
|
|
17762
|
-
onChange(
|
|
18233
|
+
onChange(widgetKey, String(nextStepValue(currentValue, axis, 1, snap)));
|
|
17763
18234
|
} else if (isDec) {
|
|
17764
18235
|
e.preventDefault();
|
|
17765
18236
|
playSound("bar-step");
|
|
17766
|
-
onChange(
|
|
18237
|
+
onChange(widgetKey, String(nextStepValue(currentValue, axis, -1, snap)));
|
|
17767
18238
|
} else if (e.key === "Home") {
|
|
17768
18239
|
e.preventDefault();
|
|
17769
18240
|
playSound("bar-step");
|
|
17770
|
-
onChange(
|
|
18241
|
+
onChange(widgetKey, String(axisMin));
|
|
17771
18242
|
} else if (e.key === "End") {
|
|
17772
18243
|
e.preventDefault();
|
|
17773
18244
|
playSound("bar-step");
|
|
17774
|
-
onChange(
|
|
18245
|
+
onChange(widgetKey, String(axisMax));
|
|
17775
18246
|
}
|
|
17776
18247
|
},
|
|
17777
|
-
[axisMin, axisMax,
|
|
18248
|
+
[axisMin, axisMax, widgetKey, currentValue, axis, snap, onChange, playSound, exerciseLocked]
|
|
17778
18249
|
);
|
|
17779
18250
|
const fill = toFill(currentValue, axis);
|
|
17780
18251
|
const isAtMin = currentValue <= axisMin;
|
|
@@ -17850,7 +18321,7 @@ const StaticBar = ({
|
|
|
17850
18321
|
}
|
|
17851
18322
|
) });
|
|
17852
18323
|
};
|
|
17853
|
-
const EMPTY_SET$
|
|
18324
|
+
const EMPTY_SET$4 = /* @__PURE__ */ new Set();
|
|
17854
18325
|
const XAxisLabel = ({
|
|
17855
18326
|
bar,
|
|
17856
18327
|
widgets,
|
|
@@ -17865,14 +18336,14 @@ const XAxisLabel = ({
|
|
|
17865
18336
|
item: subItem,
|
|
17866
18337
|
widgets,
|
|
17867
18338
|
values,
|
|
17868
|
-
droppedInputs: EMPTY_SET$
|
|
18339
|
+
droppedInputs: EMPTY_SET$4,
|
|
17869
18340
|
validationDetails,
|
|
17870
18341
|
onChange
|
|
17871
18342
|
},
|
|
17872
18343
|
subItem.id
|
|
17873
18344
|
)) }) });
|
|
17874
18345
|
};
|
|
17875
|
-
const EMPTY_SET$
|
|
18346
|
+
const EMPTY_SET$3 = /* @__PURE__ */ new Set();
|
|
17876
18347
|
const BarChart = ({
|
|
17877
18348
|
item,
|
|
17878
18349
|
widgets,
|
|
@@ -17880,6 +18351,7 @@ const BarChart = ({
|
|
|
17880
18351
|
validationDetails,
|
|
17881
18352
|
onChange
|
|
17882
18353
|
}) => {
|
|
18354
|
+
if (item.chartType !== "bar") return null;
|
|
17883
18355
|
const { bars, grid } = item;
|
|
17884
18356
|
const isHorizontal = item.orientation === "horizontal";
|
|
17885
18357
|
const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
|
|
@@ -17893,7 +18365,7 @@ const BarChart = ({
|
|
|
17893
18365
|
item: subItem,
|
|
17894
18366
|
widgets,
|
|
17895
18367
|
values,
|
|
17896
|
-
droppedInputs: EMPTY_SET$
|
|
18368
|
+
droppedInputs: EMPTY_SET$3,
|
|
17897
18369
|
validationDetails,
|
|
17898
18370
|
onChange
|
|
17899
18371
|
},
|
|
@@ -17903,9 +18375,10 @@ const BarChart = ({
|
|
|
17903
18375
|
const fillAxis = valueAxis ?? CHART_AXIS_DEFAULTS;
|
|
17904
18376
|
const renderBar = (bar, orient = "vertical") => {
|
|
17905
18377
|
const currentValue = resolveValue(bar, values);
|
|
17906
|
-
const
|
|
18378
|
+
const widgetKey = interactiveBarKey(bar);
|
|
18379
|
+
const vStatus = widgetKey !== void 0 ? validationDetails?.[widgetKey] : void 0;
|
|
17907
18380
|
const validationStatus = vStatus?.isCorrect === true ? "correct" : vStatus?.isCorrect === false ? "incorrect" : null;
|
|
17908
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-bar-wrapper", children:
|
|
18381
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-bar-wrapper", children: widgetKey !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
17909
18382
|
DraggableBar,
|
|
17910
18383
|
{
|
|
17911
18384
|
bar,
|
|
@@ -17938,7 +18411,7 @@ const BarChart = ({
|
|
|
17938
18411
|
item: subItem,
|
|
17939
18412
|
widgets,
|
|
17940
18413
|
values,
|
|
17941
|
-
droppedInputs: EMPTY_SET$
|
|
18414
|
+
droppedInputs: EMPTY_SET$3,
|
|
17942
18415
|
validationDetails,
|
|
17943
18416
|
onChange
|
|
17944
18417
|
},
|
|
@@ -18143,7 +18616,7 @@ const PictographGrid = ({
|
|
|
18143
18616
|
))
|
|
18144
18617
|
] });
|
|
18145
18618
|
};
|
|
18146
|
-
const EMPTY_SET = /* @__PURE__ */ new Set();
|
|
18619
|
+
const EMPTY_SET$2 = /* @__PURE__ */ new Set();
|
|
18147
18620
|
const PictographChart = ({
|
|
18148
18621
|
item,
|
|
18149
18622
|
widgets,
|
|
@@ -18151,7 +18624,8 @@ const PictographChart = ({
|
|
|
18151
18624
|
validationDetails,
|
|
18152
18625
|
onChange
|
|
18153
18626
|
}) => {
|
|
18154
|
-
|
|
18627
|
+
if (item.chartType !== "pictograph") return null;
|
|
18628
|
+
const { pictos, grid } = item;
|
|
18155
18629
|
const isHorizontal = item.orientation === "horizontal";
|
|
18156
18630
|
const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
|
|
18157
18631
|
const categoryAxis = isHorizontal ? item.yAxis : item.xAxis;
|
|
@@ -18164,7 +18638,7 @@ const PictographChart = ({
|
|
|
18164
18638
|
item: subItem,
|
|
18165
18639
|
widgets,
|
|
18166
18640
|
values,
|
|
18167
|
-
droppedInputs: EMPTY_SET,
|
|
18641
|
+
droppedInputs: EMPTY_SET$2,
|
|
18168
18642
|
validationDetails,
|
|
18169
18643
|
onChange
|
|
18170
18644
|
},
|
|
@@ -18202,7 +18676,7 @@ const PictographChart = ({
|
|
|
18202
18676
|
item: subItem,
|
|
18203
18677
|
widgets,
|
|
18204
18678
|
values,
|
|
18205
|
-
droppedInputs: EMPTY_SET,
|
|
18679
|
+
droppedInputs: EMPTY_SET$2,
|
|
18206
18680
|
validationDetails,
|
|
18207
18681
|
onChange
|
|
18208
18682
|
},
|
|
@@ -18223,7 +18697,7 @@ const PictographChart = ({
|
|
|
18223
18697
|
item: subItem,
|
|
18224
18698
|
widgets,
|
|
18225
18699
|
values,
|
|
18226
|
-
droppedInputs: EMPTY_SET,
|
|
18700
|
+
droppedInputs: EMPTY_SET$2,
|
|
18227
18701
|
validationDetails,
|
|
18228
18702
|
onChange
|
|
18229
18703
|
},
|
|
@@ -18242,9 +18716,183 @@ const PictographChart = ({
|
|
|
18242
18716
|
xAxisTitle
|
|
18243
18717
|
] }) }) });
|
|
18244
18718
|
};
|
|
18719
|
+
const GroupSlot = ({
|
|
18720
|
+
group,
|
|
18721
|
+
series,
|
|
18722
|
+
axis,
|
|
18723
|
+
snap,
|
|
18724
|
+
orientation = "vertical",
|
|
18725
|
+
values,
|
|
18726
|
+
validationDetails,
|
|
18727
|
+
onChange
|
|
18728
|
+
}) => {
|
|
18729
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-group", children: series.map((s) => {
|
|
18730
|
+
const bar = group.bars.find((b) => b.seriesId === s.id);
|
|
18731
|
+
if (!bar) {
|
|
18732
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-group-bar-gap", "aria-hidden": "true" }, s.id);
|
|
18733
|
+
}
|
|
18734
|
+
const currentValue = resolveValue(bar, values);
|
|
18735
|
+
const vStatus = bar.answer !== void 0 ? validationDetails?.[bar.id] : void 0;
|
|
18736
|
+
const validationStatus = vStatus?.isCorrect === true ? "correct" : vStatus?.isCorrect === false ? "incorrect" : null;
|
|
18737
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-bar-wrapper", children: bar.answer !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
18738
|
+
DraggableBar,
|
|
18739
|
+
{
|
|
18740
|
+
bar: { ...bar, answer: bar.answer },
|
|
18741
|
+
currentValue,
|
|
18742
|
+
axis,
|
|
18743
|
+
snap,
|
|
18744
|
+
validationStatus,
|
|
18745
|
+
orientation,
|
|
18746
|
+
onChange
|
|
18747
|
+
}
|
|
18748
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
18749
|
+
StaticBar,
|
|
18750
|
+
{
|
|
18751
|
+
bar,
|
|
18752
|
+
currentValue,
|
|
18753
|
+
axis,
|
|
18754
|
+
validationStatus,
|
|
18755
|
+
orientation
|
|
18756
|
+
}
|
|
18757
|
+
) }, bar.id);
|
|
18758
|
+
}) });
|
|
18759
|
+
};
|
|
18760
|
+
const EMPTY_SET$1 = /* @__PURE__ */ new Set();
|
|
18761
|
+
const ChartLegend = ({
|
|
18762
|
+
series,
|
|
18763
|
+
widgets,
|
|
18764
|
+
values,
|
|
18765
|
+
validationDetails,
|
|
18766
|
+
onChange
|
|
18767
|
+
}) => {
|
|
18768
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-legend", children: series.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "chart-legend-item", children: [
|
|
18769
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "chart-legend-swatch", "aria-hidden": "true", style: { backgroundColor: s.color } }),
|
|
18770
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "chart-legend-label", children: s.label?.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18771
|
+
LayoutRenderer,
|
|
18772
|
+
{
|
|
18773
|
+
item: subItem,
|
|
18774
|
+
widgets,
|
|
18775
|
+
values,
|
|
18776
|
+
droppedInputs: EMPTY_SET$1,
|
|
18777
|
+
validationDetails,
|
|
18778
|
+
onChange
|
|
18779
|
+
},
|
|
18780
|
+
subItem.id
|
|
18781
|
+
)) })
|
|
18782
|
+
] }, s.id)) });
|
|
18783
|
+
};
|
|
18784
|
+
const EMPTY_SET = /* @__PURE__ */ new Set();
|
|
18785
|
+
const GroupedBarChart = ({
|
|
18786
|
+
item,
|
|
18787
|
+
widgets,
|
|
18788
|
+
values,
|
|
18789
|
+
validationDetails,
|
|
18790
|
+
onChange
|
|
18791
|
+
}) => {
|
|
18792
|
+
if (item.chartType !== "grouped-bar") return null;
|
|
18793
|
+
const { groups, series, grid } = item;
|
|
18794
|
+
const isHorizontal = item.orientation === "horizontal";
|
|
18795
|
+
const isInteractive = groups.some((g) => g.bars.some((bar) => bar.answer !== void 0));
|
|
18796
|
+
const valueAxis = isHorizontal ? item.xAxis : item.yAxis;
|
|
18797
|
+
const categoryAxis = isHorizontal ? item.yAxis : item.xAxis;
|
|
18798
|
+
const chartClass = getChartClassName(item);
|
|
18799
|
+
const fillAxis = valueAxis ?? CHART_AXIS_DEFAULTS;
|
|
18800
|
+
const renderAxisTitle = (title) => {
|
|
18801
|
+
if (!title?.length) return null;
|
|
18802
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: title.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18803
|
+
LayoutRenderer,
|
|
18804
|
+
{
|
|
18805
|
+
item: subItem,
|
|
18806
|
+
widgets,
|
|
18807
|
+
values,
|
|
18808
|
+
droppedInputs: EMPTY_SET,
|
|
18809
|
+
validationDetails,
|
|
18810
|
+
onChange
|
|
18811
|
+
},
|
|
18812
|
+
subItem.id
|
|
18813
|
+
)) });
|
|
18814
|
+
};
|
|
18815
|
+
const renderGroupSlots = (orient) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-groups", children: groups.map((group) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18816
|
+
GroupSlot,
|
|
18817
|
+
{
|
|
18818
|
+
group,
|
|
18819
|
+
series,
|
|
18820
|
+
axis: fillAxis,
|
|
18821
|
+
snap: item.snap,
|
|
18822
|
+
orientation: orient,
|
|
18823
|
+
values,
|
|
18824
|
+
validationDetails,
|
|
18825
|
+
onChange
|
|
18826
|
+
},
|
|
18827
|
+
group.id
|
|
18828
|
+
)) });
|
|
18829
|
+
const legend = item.legend ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
18830
|
+
ChartLegend,
|
|
18831
|
+
{
|
|
18832
|
+
series,
|
|
18833
|
+
widgets,
|
|
18834
|
+
values,
|
|
18835
|
+
validationDetails,
|
|
18836
|
+
onChange
|
|
18837
|
+
}
|
|
18838
|
+
) : null;
|
|
18839
|
+
if (isHorizontal) {
|
|
18840
|
+
const chartStyle2 = computeGroupedChartStyle(item.scale, groups.length, series.length, "horizontal", isInteractive);
|
|
18841
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-scroll", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chartClass, dir: "ltr", style: chartStyle2, children: [
|
|
18842
|
+
item.title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-title", children: item.title }),
|
|
18843
|
+
categoryAxis?.visible && categoryAxis.title?.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-title chart-axis-title--y-h", children: renderAxisTitle(categoryAxis.title) }) : null,
|
|
18844
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-y--categories", children: groups.map((group) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-bar-y-label", children: group.label?.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18845
|
+
LayoutRenderer,
|
|
18846
|
+
{
|
|
18847
|
+
item: subItem,
|
|
18848
|
+
widgets,
|
|
18849
|
+
values,
|
|
18850
|
+
droppedInputs: EMPTY_SET,
|
|
18851
|
+
validationDetails,
|
|
18852
|
+
onChange
|
|
18853
|
+
},
|
|
18854
|
+
subItem.id
|
|
18855
|
+
)) }, group.id)) }),
|
|
18856
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "chart-plot", children: [
|
|
18857
|
+
grid && /* @__PURE__ */ jsxRuntime.jsx(Grid, { axis: fillAxis, orientation: "horizontal" }),
|
|
18858
|
+
renderGroupSlots("horizontal")
|
|
18859
|
+
] }),
|
|
18860
|
+
valueAxis && valueAxis.visible && /* @__PURE__ */ jsxRuntime.jsx(XAxis, { axis: valueAxis, title: renderAxisTitle(valueAxis.title) }),
|
|
18861
|
+
legend
|
|
18862
|
+
] }) }) });
|
|
18863
|
+
}
|
|
18864
|
+
const chartStyle = computeGroupedChartStyle(item.scale, groups.length, series.length, "vertical", isInteractive);
|
|
18865
|
+
const hasAnyGroupLabel = groups.some((g) => g.label && g.label.length > 0);
|
|
18866
|
+
const groupLabelRow = hasAnyGroupLabel ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-x", children: groups.map((group) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-tick-x", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-tick-x-inner", children: group.label?.map((subItem) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
18867
|
+
LayoutRenderer,
|
|
18868
|
+
{
|
|
18869
|
+
item: subItem,
|
|
18870
|
+
widgets,
|
|
18871
|
+
values,
|
|
18872
|
+
droppedInputs: EMPTY_SET,
|
|
18873
|
+
validationDetails,
|
|
18874
|
+
onChange
|
|
18875
|
+
},
|
|
18876
|
+
subItem.id
|
|
18877
|
+
)) }) }, group.id)) }) : null;
|
|
18878
|
+
const xAxisTitle = categoryAxis?.visible && categoryAxis.title?.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-title chart-axis-title--x-category", children: renderAxisTitle(categoryAxis.title) }) : null;
|
|
18879
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-container", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-scroll", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: chartClass, dir: "ltr", style: chartStyle, children: [
|
|
18880
|
+
item.title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-title", children: item.title }),
|
|
18881
|
+
valueAxis?.visible && valueAxis.title?.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "chart-axis-title chart-axis-title--y", children: renderAxisTitle(valueAxis.title) }) : null,
|
|
18882
|
+
valueAxis && valueAxis.visible && /* @__PURE__ */ jsxRuntime.jsx(YAxis, { axis: valueAxis }),
|
|
18883
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "chart-plot", children: [
|
|
18884
|
+
grid && /* @__PURE__ */ jsxRuntime.jsx(Grid, { axis: fillAxis }),
|
|
18885
|
+
renderGroupSlots("vertical")
|
|
18886
|
+
] }),
|
|
18887
|
+
groupLabelRow,
|
|
18888
|
+
xAxisTitle,
|
|
18889
|
+
legend
|
|
18890
|
+
] }) }) });
|
|
18891
|
+
};
|
|
18245
18892
|
const CHART_TYPE_COMPONENTS = {
|
|
18246
18893
|
bar: BarChart,
|
|
18247
|
-
pictograph: PictographChart
|
|
18894
|
+
pictograph: PictographChart,
|
|
18895
|
+
"grouped-bar": GroupedBarChart
|
|
18248
18896
|
// line: LineChart,
|
|
18249
18897
|
// pie: PieChart,
|
|
18250
18898
|
};
|
|
@@ -18927,7 +19575,7 @@ function isGeometryWidget(widget) {
|
|
|
18927
19575
|
return widget.type === JSON_TYPES.GEOMETRY;
|
|
18928
19576
|
}
|
|
18929
19577
|
const GeometryWidget = React.lazy(async () => ({
|
|
18930
|
-
default: (await Promise.resolve().then(() => require("./GeometryWidget-
|
|
19578
|
+
default: (await Promise.resolve().then(() => require("./GeometryWidget-DgPfqyQv.js"))).GeometryWidget
|
|
18931
19579
|
}));
|
|
18932
19580
|
const WidgetRenderer = ({
|
|
18933
19581
|
widget,
|
|
@@ -21716,10 +22364,16 @@ exports.ChartBarJSONSchema = ChartBarJSONSchema;
|
|
|
21716
22364
|
exports.ChartBarSchema = ChartBarSchema;
|
|
21717
22365
|
exports.ChartBarWidgetJSONSchema = ChartBarWidgetJSONSchema;
|
|
21718
22366
|
exports.ChartBlockSchema = ChartBlockSchema;
|
|
22367
|
+
exports.ChartGroupJSONSchema = ChartGroupJSONSchema;
|
|
22368
|
+
exports.ChartGroupSchema = ChartGroupSchema;
|
|
21719
22369
|
exports.ChartItemSchema = ChartItemSchema;
|
|
21720
22370
|
exports.ChartPictoJSONSchema = ChartPictoJSONSchema;
|
|
21721
22371
|
exports.ChartPictoSchema = ChartPictoSchema;
|
|
21722
22372
|
exports.ChartPictoWidgetJSONSchema = ChartPictoWidgetJSONSchema;
|
|
22373
|
+
exports.ChartSeriesBarJSONSchema = ChartSeriesBarJSONSchema;
|
|
22374
|
+
exports.ChartSeriesBarSchema = ChartSeriesBarSchema;
|
|
22375
|
+
exports.ChartSeriesJSONSchema = ChartSeriesJSONSchema;
|
|
22376
|
+
exports.ChartSeriesSchema = ChartSeriesSchema;
|
|
21723
22377
|
exports.ClockInlineSchema = ClockInlineSchema;
|
|
21724
22378
|
exports.ClockItemSchema = ClockItemSchema;
|
|
21725
22379
|
exports.ColorTextInlineSchema = ColorTextInlineSchema;
|
|
@@ -21834,6 +22488,7 @@ exports.VopSeparatorSchema = VopSeparatorSchema;
|
|
|
21834
22488
|
exports.WidgetJSONSchema = WidgetJSONSchema;
|
|
21835
22489
|
exports.WidgetRefSchema = WidgetRefSchema;
|
|
21836
22490
|
exports.WidgetRenderer = WidgetRenderer;
|
|
22491
|
+
exports.alignGroupBarsToSeries = alignGroupBarsToSeries;
|
|
21837
22492
|
exports.compile = compile;
|
|
21838
22493
|
exports.compileWithAST = compileWithAST;
|
|
21839
22494
|
exports.computeGridSymmetryCellsToFill = computeGridSymmetryCellsToFill;
|
|
@@ -21842,7 +22497,10 @@ exports.createBoldText = createBoldText;
|
|
|
21842
22497
|
exports.createChartAxis = createChartAxis;
|
|
21843
22498
|
exports.createChartBar = createChartBar;
|
|
21844
22499
|
exports.createChartBlock = createChartBlock;
|
|
22500
|
+
exports.createChartGroup = createChartGroup;
|
|
21845
22501
|
exports.createChartPicto = createChartPicto;
|
|
22502
|
+
exports.createChartSeries = createChartSeries;
|
|
22503
|
+
exports.createChartSeriesBar = createChartSeriesBar;
|
|
21846
22504
|
exports.createColorText = createColorText;
|
|
21847
22505
|
exports.createDisplayClock = createDisplayClock;
|
|
21848
22506
|
exports.createDragOptions = createDragOptions;
|
|
@@ -21895,9 +22553,12 @@ exports.isChartAxis = isChartAxis;
|
|
|
21895
22553
|
exports.isChartBar = isChartBar;
|
|
21896
22554
|
exports.isChartBarWidgetJSON = isChartBarWidgetJSON;
|
|
21897
22555
|
exports.isChartBlock = isChartBlock;
|
|
22556
|
+
exports.isChartGroup = isChartGroup;
|
|
21898
22557
|
exports.isChartItem = isChartItem;
|
|
21899
22558
|
exports.isChartPicto = isChartPicto;
|
|
21900
22559
|
exports.isChartPictoWidgetJSON = isChartPictoWidgetJSON;
|
|
22560
|
+
exports.isChartSeries = isChartSeries;
|
|
22561
|
+
exports.isChartSeriesBar = isChartSeriesBar;
|
|
21901
22562
|
exports.isClock = isClock;
|
|
21902
22563
|
exports.isClockItem = isClockItem;
|
|
21903
22564
|
exports.isColorText = isColorText;
|
|
@@ -21984,7 +22645,8 @@ exports.validateExpression = validateExpression;
|
|
|
21984
22645
|
exports.validateInputFormats = validateInputFormats;
|
|
21985
22646
|
exports.validateMixed = validateMixed;
|
|
21986
22647
|
exports.validateSimple = validateSimple;
|
|
22648
|
+
exports.validateUniqueIds = validateUniqueIds;
|
|
21987
22649
|
exports.validateVisualFractionCapacity = validateVisualFractionCapacity;
|
|
21988
22650
|
exports.validateWidgetJSON = validateWidgetJSON;
|
|
21989
22651
|
exports.validateWidgetJSONSafe = validateWidgetJSONSafe;
|
|
21990
|
-
//# sourceMappingURL=index-
|
|
22652
|
+
//# sourceMappingURL=index-tEgJE6m2.js.map
|