pict-section-form 1.0.138 → 1.0.139

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.
@@ -0,0 +1,181 @@
1
+ # Chart Input Type
2
+
3
+ Chart input types wrap one of a few charting libraries. This means the tech is
4
+ compatible with chart.js, c3 and d3 natively and more can be easily added.
5
+
6
+ ## Defaults
7
+
8
+ By default charts use `chart.js` and are `bar` charts. They will try to cast
9
+ an array or object into a meaningful data set.
10
+
11
+ This means minimum viable config is something like the following:
12
+
13
+ ```json
14
+ {
15
+ "Scope": "ChartDemo",
16
+
17
+ "Descriptors":
18
+ {
19
+ "Chart.Display":
20
+ {
21
+ "Name": "MVP Chart",
22
+ "Hash": "MVPChart",
23
+ "DataType": "Object",
24
+ "PictForm":
25
+ {
26
+ "InputType": "Chart",
27
+ "ChartDataAddress": "City.Populations"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ And AppData has something like this:
35
+
36
+ ```json
37
+ {
38
+ "City":
39
+ {
40
+ "Populations":
41
+ {
42
+ "Seattle": 324230,
43
+ "Lynnwood": 2349,
44
+ "Burien": 1500,
45
+ "Tacoma": 23498,
46
+ "Olympia": 17984,
47
+ "Redmond": 8700,
48
+ "Kirkland": 9723,
49
+ "Bellevue": 11001
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ You will end up with a bar graph that has the X axis as cities, y axis as
56
+ populations and the series will be named "City.Populations" as such:
57
+
58
+
59
+
60
+ ## Data Addresses, Raw Objects and Configurable Programmability _(oh my!)_
61
+
62
+ There is a three tiered system to how the chart configuration is generated.
63
+ Because modern charting libraries rely almost entirely on configuration to
64
+ define behavior these days, we can manage the displays this way and only
65
+ rely on the pict-section-form library to broker data back-and-forth.
66
+
67
+ ### Purely Raw Data
68
+
69
+ You can hard code any chart right into the form. There are three places
70
+ for raw form config:
71
+
72
+ * `ChartLabelsRaw` - the "labels" object for the chart
73
+ * `ChartDatasetsRaw` - the "data" object for the chart
74
+ * `ChartJSOptionsCorePrototype` - the chart.js config base
75
+
76
+ The input provider will use the `ChartJSOptionsCorePrototype` and then
77
+ decorate in the `labels` and `data` objects from their raw entries.
78
+
79
+ Chaining always does the following:
80
+
81
+ 1. If there is a `Raw`
82
+
83
+ ```json
84
+ {
85
+ "SimpleGraphExampleRawData":
86
+ {
87
+ Name: "OrderCaloryGraph",
88
+ Hash: "OrderCaloryGraph",
89
+
90
+ DataType: "Object",
91
+ PictForm:
92
+ {
93
+ Section: "Chart",
94
+ Group: "SimpleChart",
95
+
96
+ Row: 1,
97
+ Width: 12,
98
+
99
+ InputType: "Chart",
100
+
101
+ ChartLabelsRaw: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
102
+
103
+ ChartDatasetsRaw: [
104
+ {
105
+ label: 'Awesomeness',
106
+ data: [ 1500, 1200, 800, 1700, 900, 2000 ]
107
+ }],
108
+
109
+ ChartJSOptionsCorePrototype:
110
+ {
111
+ type: 'bar'
112
+ }
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### Purely Raw Data
119
+
120
+ ```json
121
+ {
122
+ "SimpleGraphExampleRawData":
123
+ {
124
+ Name: "OrderCaloryGraph",
125
+ Hash: "OrderCaloryGraph",
126
+
127
+ DataType: "Object",
128
+ PictForm:
129
+ {
130
+ Section: "Chart",
131
+ Group: "SimpleChart",
132
+ Row: 1,
133
+ Width: 12,
134
+ InputType: "Chart",
135
+
136
+ ChartType: "bar",
137
+
138
+ // This allows you to scope data for the chart separately from appdata
139
+ ChartDataScope: "Form",
140
+
141
+ ChartDataAddress: "FruitData.FruityVice",
142
+
143
+ ChartLabelsAddress: "FruitData.FruityVice",
144
+ ChartLabelsRaw: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
145
+
146
+ ChartDatasetsAddress: "FruitData.FruityVice",
147
+ ChartDatasetsConfig:
148
+ {
149
+ Calories:
150
+ {
151
+ ChartLabel: "Calories",
152
+ DataSolver: "Data = {~D:Record.nutritions.calories~}",
153
+ DataTemplate: "{~D:Record.SolverResult~}", // Could also be something from the solver postfix stack
154
+ DataCorePrototype:
155
+ {
156
+ borderWidth: 1
157
+ }
158
+ }
159
+ },
160
+ ChartDatasetsRaw: [
161
+ {
162
+ label: 'Awesomeness',
163
+ data: [ 1500, 1200, 800, 1700, 900, 2000 ]
164
+ }],
165
+
166
+ // Do anything you want here!!
167
+ ChartJSOptionsCorePrototype:
168
+ {
169
+ type: 'bar',
170
+ options: {
171
+ scales: {
172
+ y: {
173
+ beginAtZero: true
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ ```
@@ -24,45 +24,45 @@ module.exports.default_configuration.pict_configuration = {
24
24
  Scope: "SuperComplexTabularForm",
25
25
 
26
26
  PickLists:
27
- [
28
- {
29
- Hash: "Families",
30
- ListAddress: "AppData.FruitMetaLists.Families",
31
- ListSourceAddress: "FruitData.FruityVice[]",
32
- TextTemplate: "{~D:Record.family~}",
33
- IDTemplate: "{~D:Record.family~}",
34
- Unique: true,
35
- Sorted: true,
36
- UpdateFrequency: "Once",
37
- },
38
- {
39
- Hash: "Orders",
40
- ListAddress: "AppData.FruitMetaLists.Orders",
41
- ListSourceAddress: "FruitData.FruityVice[]",
42
- TextTemplate: "{~D:Record.order~}",
43
- IDTemplate: "{~D:Record.order~}",
44
- Unique: true,
45
- UpdateFrequency: "Once",
46
- },
47
- {
48
- Hash: "Genuses",
49
- ListAddress: "AppData.FruitMetaLists.Genuses",
50
- ListSourceAddress: "FruitData.FruityVice[]",
51
- TextTemplate: "{~D:Record.genus~}",
52
- IDTemplate: "{~D:Record.genus~}",
53
- Sorted: true,
54
- UpdateFrequency: "Always",
55
- },
56
- {
57
- Hash: "Books",
58
- ListAddress: "AppData.AuthorsBooks",
59
- ListSourceAddress: "Books[]",
60
- TextTemplate: "{~D:Record.Title~}",
61
- IDTemplate: "{~D:Record.IDBook~}",
62
- Sorted: true,
63
- UpdateFrequency: "Always",
64
- }
65
- ],
27
+ [
28
+ {
29
+ Hash: "Families",
30
+ ListAddress: "AppData.FruitMetaLists.Families",
31
+ ListSourceAddress: "FruitData.FruityVice[]",
32
+ TextTemplate: "{~D:Record.family~}",
33
+ IDTemplate: "{~D:Record.family~}",
34
+ Unique: true,
35
+ Sorted: true,
36
+ UpdateFrequency: "Once",
37
+ },
38
+ {
39
+ Hash: "Orders",
40
+ ListAddress: "AppData.FruitMetaLists.Orders",
41
+ ListSourceAddress: "FruitData.FruityVice[]",
42
+ TextTemplate: "{~D:Record.order~}",
43
+ IDTemplate: "{~D:Record.order~}",
44
+ Unique: true,
45
+ UpdateFrequency: "Once",
46
+ },
47
+ {
48
+ Hash: "Genuses",
49
+ ListAddress: "AppData.FruitMetaLists.Genuses",
50
+ ListSourceAddress: "FruitData.FruityVice[]",
51
+ TextTemplate: "{~D:Record.genus~}",
52
+ IDTemplate: "{~D:Record.genus~}",
53
+ Sorted: true,
54
+ UpdateFrequency: "Always",
55
+ },
56
+ {
57
+ Hash: "Books",
58
+ ListAddress: "AppData.AuthorsBooks",
59
+ ListSourceAddress: "Books[]",
60
+ TextTemplate: "{~D:Record.Title~}",
61
+ IDTemplate: "{~D:Record.IDBook~}",
62
+ Sorted: true,
63
+ UpdateFrequency: "Always",
64
+ }
65
+ ],
66
66
 
67
67
  Sections: [
68
68
  {
@@ -70,27 +70,27 @@ module.exports.default_configuration.pict_configuration = {
70
70
  Name: "Fruit-based Recipe",
71
71
 
72
72
  Solvers:
73
- [
74
- "TotalFruitCalories = SUM(FruitNutritionCalories)",
75
- "AverageFruitCalories = MEAN(FruitNutritionCalories)",
76
- {
77
- Ordinal: 99,
78
- Expression: "AverageFatPercent = MEAN(FruitPercentTotalFat)",
79
- },
80
- "RecipeCounterSurfaceArea = RecipeCounterWidth * RecipeCounterDepth",
81
- "RecipeCounterVolume = RecipeCounterSurfaceArea * RecipeVerticalClearance",
82
- `InspirationLink = CONCAT("https://www.google.com/search?q=", RecipeName, " recipe")`,
83
- 'cumulativeSummationResult = cumulativeSummation(getvalue("AppData.FruitData.FruityVice"), "nutritions.calories", "SummedCalories")'
84
- ],
73
+ [
74
+ "TotalFruitCalories = SUM(FruitNutritionCalories)",
75
+ "AverageFruitCalories = MEAN(FruitNutritionCalories)",
76
+ {
77
+ Ordinal: 99,
78
+ Expression: "AverageFatPercent = MEAN(FruitPercentTotalFat)",
79
+ },
80
+ "RecipeCounterSurfaceArea = RecipeCounterWidth * RecipeCounterDepth",
81
+ "RecipeCounterVolume = RecipeCounterSurfaceArea * RecipeVerticalClearance",
82
+ `InspirationLink = CONCAT("https://www.google.com/search?q=", RecipeName, " recipe")`,
83
+ 'cumulativeSummationResult = cumulativeSummation(getvalue("AppData.FruitData.FruityVice"), "nutritions.calories", "SummedCalories")'
84
+ ],
85
85
 
86
86
  MetaTemplates:
87
- [
88
- {
89
- //onclick="{~D:Record.Macro.DataRequestFunction~}"
90
- "HashPostfix": "-Template-Wrap-Prefix",
91
- "Template": "<h1>Rectangular Area Solver Micro-app</h1><div><a href=\"#\" onclick=\"{~Pict~}.PictApplication.solve()\">[ solve ]</a> <a href=\"#\" onclick=\"{~P~}.views.PictFormMetacontroller.showSupportViewInlineEditor()\">[ debug ]</a></div><hr />"
92
- }
93
- ],
87
+ [
88
+ {
89
+ //onclick="{~D:Record.Macro.DataRequestFunction~}"
90
+ "HashPostfix": "-Template-Wrap-Prefix",
91
+ "Template": "<h1>Rectangular Area Solver Micro-app</h1><div><a href=\"#\" onclick=\"{~Pict~}.PictApplication.solve()\">[ solve ]</a> <a href=\"#\" onclick=\"{~P~}.views.PictFormMetacontroller.showSupportViewInlineEditor()\">[ debug ]</a></div><hr />"
92
+ }
93
+ ],
94
94
 
95
95
  Groups: [
96
96
  {
@@ -101,13 +101,13 @@ module.exports.default_configuration.pict_configuration = {
101
101
  {
102
102
  Hash: "StatisticsTabs",
103
103
  Name: "Select a Statistics Section",
104
- // ShowTitle: true
104
+ // ShowTitle: true
105
105
  },
106
106
  {
107
107
  Hash: "Statistics",
108
108
  Name: "Statistics",
109
109
  Layout: "Vertical",
110
- // ShowTitle: true
110
+ // ShowTitle: true
111
111
  },
112
112
  {
113
113
  Hash: "FruitStatistics",
@@ -157,18 +157,18 @@ module.exports.default_configuration.pict_configuration = {
157
157
  ],
158
158
 
159
159
  PickLists:
160
- [
161
- {
162
- Hash: "Families",
163
- ListAddress: "AppData.FruitMetaLists.Families",
164
- ListSourceAddress: "FruitData.FruityVice[]",
165
- TextTemplate: "{~D:Record.family~}",
166
- IDTemplate: "{~D:Record.family~}",
167
- Unique: true,
168
- Sorted: true,
169
- UpdateFrequency: "Once",
170
- },
171
- ],
160
+ [
161
+ {
162
+ Hash: "Families",
163
+ ListAddress: "AppData.FruitMetaLists.Families",
164
+ ListSourceAddress: "FruitData.FruityVice[]",
165
+ TextTemplate: "{~D:Record.family~}",
166
+ IDTemplate: "{~D:Record.family~}",
167
+ Unique: true,
168
+ Sorted: true,
169
+ UpdateFrequency: "Once",
170
+ },
171
+ ],
172
172
  RecordSetAddress: "FruitData.FruityVice",
173
173
  RecordManifest: "FruitEditor",
174
174
  },
@@ -209,12 +209,12 @@ module.exports.default_configuration.pict_configuration = {
209
209
  Name: "Recipe Type",
210
210
  Hash: "RecipeType",
211
211
  DataType: "String",
212
- PictForm:
213
- {
214
- Section:"Recipe",
215
- Group:"Recipe",
216
- Row: 1
217
- },
212
+ PictForm:
213
+ {
214
+ Section: "Recipe",
215
+ Group: "Recipe",
216
+ Row: 1
217
+ },
218
218
  },
219
219
  RecipeDescription: {
220
220
  Name: "Description",
@@ -244,7 +244,8 @@ module.exports.default_configuration.pict_configuration = {
244
244
  Name: "Last Prepared",
245
245
  Hash: "LastPrepared",
246
246
  DataType: "DateTime",
247
- PictForm: { Section: "Recipe", Group: "Recipe", Row: 4,
247
+ PictForm: {
248
+ Section: "Recipe", Group: "Recipe", Row: 4,
248
249
  "Providers": ["Pict-Input-DateTime"]
249
250
  },
250
251
  },
@@ -254,28 +255,28 @@ module.exports.default_configuration.pict_configuration = {
254
255
  Hash: "IDAuthor",
255
256
  DataType: "Number",
256
257
  PictForm: {
257
- Section: "Book", Group: "Author", Row: 1, Width: 1,
258
+ Section: "Book", Group: "Author", Row: 1, Width: 1,
258
259
  // This performs an entity bundle request whenever a value is selected.
259
260
  Providers: ["Pict-Input-EntityBundleRequest", "Pict-Input-AutofillTriggerGroup"],
260
261
  EntitiesBundle: [
261
- {
262
- "Entity": "Author",
263
- "Filter": "FBV~IDAuthor~EQ~{~D:Record.Value~}",
264
- "Destination": "AppData.CurrentAuthor",
265
- // This marshals a single record
266
- "SingleRecord": true
267
- },
268
- {
269
- "Entity": "BookAuthorJoin",
270
- "Filter": "FBV~IDAuthor~EQ~{~D:AppData.CurrentAuthor.IDAuthor~}",
271
- "Destination": "AppData.BookAuthorJoins"
272
- },
273
- {
274
- "Entity": "Book",
275
- "Filter": "FBL~IDBook~INN~{~PJU:,^IDBook^AppData.BookAuthorJoins~}",
276
- "Destination": "AppData.Books"
277
- }
278
- ],
262
+ {
263
+ "Entity": "Author",
264
+ "Filter": "FBV~IDAuthor~EQ~{~D:Record.Value~}",
265
+ "Destination": "AppData.CurrentAuthor",
266
+ // This marshals a single record
267
+ "SingleRecord": true
268
+ },
269
+ {
270
+ "Entity": "BookAuthorJoin",
271
+ "Filter": "FBV~IDAuthor~EQ~{~D:AppData.CurrentAuthor.IDAuthor~}",
272
+ "Destination": "AppData.BookAuthorJoins"
273
+ },
274
+ {
275
+ "Entity": "Book",
276
+ "Filter": "FBL~IDBook~INN~{~PJU:,^IDBook^AppData.BookAuthorJoins~}",
277
+ "Destination": "AppData.Books"
278
+ }
279
+ ],
279
280
  EntityBundleTriggerGroup: "BookTriggerGroup"
280
281
  }
281
282
  },
@@ -288,11 +289,11 @@ module.exports.default_configuration.pict_configuration = {
288
289
  // This performs an entity bundle request whenever a value is selected.
289
290
  Providers: ["Pict-Input-AutofillTriggerGroup"],
290
291
  AutofillTriggerGroup:
291
- {
292
- TriggerGroupHash: "BookTriggerGroup",
293
- TriggerAddress: "AppData.CurrentAuthor.Name",
294
- MarshalEmptyValues: true
295
- }
292
+ {
293
+ TriggerGroupHash: "BookTriggerGroup",
294
+ TriggerAddress: "AppData.CurrentAuthor.Name",
295
+ MarshalEmptyValues: true
296
+ }
296
297
  }
297
298
  },
298
299
 
@@ -310,15 +311,178 @@ module.exports.default_configuration.pict_configuration = {
310
311
  Section: "Book",
311
312
  Group: "Book",
312
313
  Row: 1, Width: 1,
313
- InputType:"Option",
314
+ InputType: "Option",
314
315
  SelectOptionsPickList: "Books",
315
316
  // This performs an entity bundle request whenever a value is selected.
316
317
  Providers: ["Pict-Input-Select", "Pict-Input-AutofillTriggerGroup"],
317
318
  AutofillTriggerGroup:
319
+ {
320
+ TriggerGroupHash: "BookTriggerGroup",
321
+ SelectOptionsRefresh: true
322
+ }
323
+ }
324
+ },
325
+
326
+ "SimpleGraphExampleRawDataOne":
327
+ {
328
+ Name: "SimpleGraphExampleOne",
329
+ Hash: "SimpleGraphExampleOne",
330
+
331
+ DataType: "Object",
332
+ PictForm:
333
+ {
334
+ Section: "Chart",
335
+ Group: "SimpleChart",
336
+
337
+ Row: 1,
338
+ Width: 3,
339
+
340
+ InputType: "Chart",
341
+
342
+ ChartLabelsRaw: ['Red', 'Green', 'Yellow', 'Grey', 'Blue'],
343
+
344
+ ChartDatasetsRaw: [{
345
+ label: 'My First Dataset',
346
+ data: [11, 16, 7, 3, 14],
347
+ backgroundColor:
348
+ [
349
+ 'rgb(255, 99, 132)',
350
+ 'rgb(75, 192, 192)',
351
+ 'rgb(255, 205, 86)',
352
+ 'rgb(201, 203, 207)',
353
+ 'rgb(54, 162, 235)'
354
+ ]
355
+ }],
356
+
357
+ ChartJSOptionsCorePrototype:
358
+ {
359
+ type: 'polarArea'
360
+ }
361
+ }
362
+ },
363
+
364
+ "SimpleGraphExampleRawDataTwo":
365
+ {
366
+ Name: "SimpleGraphExampleTwo",
367
+ Hash: "SimpleGraphExampleTwo",
368
+
369
+ DataType: "Object",
370
+ PictForm:
371
+ {
372
+ Section: "Chart",
373
+ Group: "SimpleChart",
374
+
375
+ Row: 2,
376
+ Width: 6,
377
+
378
+ InputType: "Chart",
379
+
380
+ ChartType: "bar",
381
+ ChartLabelsSolver: `objectkeystoarray(aggregationhistogrambyobject(FruitGrid, "name", "nutritions.calories"))`,
382
+ ChartDataSolvers:
383
+ [
384
+ {
385
+ Label: 'Calories',
386
+ DataSolver: `objectvaluestoarray(aggregationhistogrambyobject(FruitGrid, "name", "nutritions.calories"))`
387
+ }
388
+ ]
389
+ }
390
+ },
391
+
392
+ "SimpleGraphExampleRawDataThree":
393
+ {
394
+ Name: "SimpleGraphExampleThree",
395
+ Hash: "SimpleGraphExampleThree",
396
+
397
+ DataType: "Object",
398
+ PictForm:
399
+ {
400
+ Section: "Chart",
401
+ Group: "SimpleChart",
402
+
403
+ Row: 2,
404
+ Width: 6,
405
+
406
+ InputType: "Chart",
407
+
408
+ ChartType: "bar",
409
+ ChartLabelsSolver: `objectkeystoarray(aggregationhistogrambyobject(FruitGrid, "name", "nutritions.fat"))`,
410
+ ChartDataSolvers:
411
+ [
412
+ {
413
+ Label: 'Fat',
414
+ DataSolver: `objectvaluestoarray(aggregationhistogrambyobject(FruitGrid, "name", "nutritions.fat"))`
415
+ },
416
+ {
417
+ Label: 'Carbohydrates',
418
+ DataSolver: `objectvaluestoarray(aggregationhistogrambyobject(FruitGrid, "name", "nutritions.carbohydrates"))`
419
+ },
420
+ {
421
+ Label: 'Protein',
422
+ DataSolver: `objectvaluestoarray(aggregationhistogrambyobject(FruitGrid, "name", "nutritions.protein"))`
423
+ },
424
+ {
425
+ Label: 'Sugars',
426
+ DataSolver: `objectvaluestoarray(aggregationhistogrambyobject(FruitGrid, "name", "nutritions.sugar"))`
427
+ }
428
+ ]
429
+ }
430
+ },
431
+
432
+ "WideGraphExample": {
433
+ Name: "WideGraphExample",
434
+ Hash: "WideGraphExample",
435
+ DataType: "Object",
436
+ PictForm:
437
+ {
438
+ Section: "Chart",
439
+ Group: "SimpleChart",
440
+ Row: 3,
441
+ Width: 12,
442
+ InputType: "Chart",
443
+
444
+ //ChartType: "Bar",
445
+
446
+ // This allows you to scope data for the chart separately from appdata
447
+ ChartDataScope: "Form",
448
+
449
+ ChartDataAddress: "FruitData.FruityVice",
450
+
451
+ ChartLabelsAddress: "FruitData.FruityVice",
452
+ ChartLabelsRaw: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
453
+
454
+ ChartDatasetsAddress: "FruitData.FruityVice",
455
+ ChartDatasetsConfig:
456
+ {
457
+ Calories:
318
458
  {
319
- TriggerGroupHash: "BookTriggerGroup",
320
- SelectOptionsRefresh: true
459
+ ChartLabel: "Calories",
460
+ DataSolver: "Data = {~D:Record.nutritions.calories~}",
461
+ DataTemplate: "{~D:Record.SolverResult~}", // Could also be something from the solver postfix stack
462
+ DataCorePrototype:
463
+ {
464
+ borderWidth: 1
465
+ }
321
466
  }
467
+ },
468
+ ChartDatasetsRaw: [
469
+ {
470
+ label: 'Awesomeness',
471
+ data: [1500, 1200, 800, 1700, 900, 2000]
472
+ }],
473
+
474
+ // Do anything you want here!!
475
+ ChartJSOptionsCorePrototype:
476
+ {
477
+ type: 'bar',
478
+ options: {
479
+ scales: {
480
+ y: {
481
+ beginAtZero: true
482
+ }
483
+ }
484
+ }
485
+ }
322
486
  }
323
487
  },
324
488
 
@@ -328,7 +492,7 @@ module.exports.default_configuration.pict_configuration = {
328
492
  DataType: "String",
329
493
  PictForm: {
330
494
  Section: "Recipe",
331
- Group: "StatisticsTabs",
495
+ Group: "StatisticsTabs",
332
496
  InputType: "TabGroupSelector",
333
497
  // The default when there is no state is the first entry here.
334
498
  // If you want to set a default, you can just do it in the state address though.
@@ -356,11 +520,12 @@ module.exports.default_configuration.pict_configuration = {
356
520
  Hash: "RecipeFeeds",
357
521
  DataType: "PreciseNumber",
358
522
  Default: "1",
359
- PictForm: { Section: "Recipe", Group: "Statistics", Row: 1, Width: 1,
523
+ PictForm: {
524
+ Section: "Recipe", Group: "Statistics", Row: 1, Width: 1,
360
525
  "ExtraDescription": "How many people does this recipe feed?",
361
- "InputType":"Option",
526
+ "InputType": "Option",
362
527
  "Providers": ["CustomDataProvider", "Pict-Input-Select"],
363
- "SelectOptions": [{"id":"few", "text":"Few"}, {"id":"some", "text":"Some"}, {"id":"many", "text":"Many"}]
528
+ "SelectOptions": [{ "id": "few", "text": "Few" }, { "id": "some", "text": "Some" }, { "id": "many", "text": "Many" }]
364
529
  },
365
530
  },
366
531
  "Recipe.TotalCalories": {
@@ -373,7 +538,7 @@ module.exports.default_configuration.pict_configuration = {
373
538
  Group: "Statistics",
374
539
  Row: 1,
375
540
  Width: 1
376
- },
541
+ },
377
542
  },
378
543
 
379
544
  "Recipe.CounterWidth": {
@@ -622,9 +787,11 @@ module.exports.default_configuration.pict_configuration = {
622
787
  Name: "Family",
623
788
  Hash: "Family",
624
789
  DataType: "String",
625
- PictForm: { Section: "FruitGrid", Group: "FruitGrid", "InputType":"Option",
790
+ PictForm: {
791
+ Section: "FruitGrid", Group: "FruitGrid", "InputType": "Option",
626
792
  "Providers": ["Pict-Input-Select"],
627
- "SelectOptionsPickList": "Families"}
793
+ "SelectOptionsPickList": "Families"
794
+ }
628
795
  },
629
796
  order: {
630
797
  Name: "Order",
@@ -642,13 +809,13 @@ module.exports.default_configuration.pict_configuration = {
642
809
  Name: "Last Watered",
643
810
  Hash: "LastWatered",
644
811
  DataType: "DateTime",
645
- PictForm: { Section: "FruitGrid", Group: "FruitGrid", "InputType":"DateTime", "Providers": ["Pict-Input-DateTime"]},
812
+ PictForm: { Section: "FruitGrid", Group: "FruitGrid", "InputType": "DateTime", "Providers": ["Pict-Input-DateTime"] },
646
813
  },
647
814
  "nutritions.calories": {
648
815
  Name: "Calories",
649
816
  Hash: "Calories",
650
817
  DataType: "Number",
651
- PictForm: { Section: "FruitGrid", Group: "FruitGrid"},
818
+ PictForm: { Section: "FruitGrid", Group: "FruitGrid" },
652
819
  },
653
820
  "SummedCalories": {
654
821
  Name: "Summed Calories (cumulative)",
@@ -696,11 +863,11 @@ module.exports.default_configuration.pict_configuration = {
696
863
  // This performs an entity bundle request whenever a value is selected.
697
864
  Providers: ["Pict-Input-AutofillTriggerGroup"],
698
865
  AutofillTriggerGroup:
699
- {
700
- TriggerGroupHash: "BookTriggerGroup",
701
- TriggerAddress: "AppData.CurrentAuthor.Name",
702
- MarshalEmptyValues: true
703
- }
866
+ {
867
+ TriggerGroupHash: "BookTriggerGroup",
868
+ TriggerAddress: "AppData.CurrentAuthor.Name",
869
+ MarshalEmptyValues: true
870
+ }
704
871
  }
705
872
  },
706
873
  },
@@ -4,6 +4,7 @@
4
4
  <title>Complex Table.</title>
5
5
  <style id="PICT-CSS"></style>
6
6
  <script src="./pict.js" type="text/javascript"></script>
7
+ <script src="./chart.umd.js"></script>
7
8
  <script type="text/javascript">Pict.safeOnDocumentReady(() => { Pict.safeLoadPictApplication(ComplexTabularApplication, 0)});</script>
8
9
  <!-- CSS placed here to simulate an external collection where we can leverage existing class names/patterns -->
9
10
  <style type="text/css">
@@ -9,7 +9,6 @@
9
9
  },
10
10
  "author": "steven",
11
11
  "license": "MIT",
12
- "devDependencies": {},
13
12
  "copyFilesSettings": {
14
13
  "whenFileExists": "overwrite"
15
14
  },
@@ -21,6 +20,13 @@
21
20
  {
22
21
  "from": "../../node_modules/pict/dist/*",
23
22
  "to": "./dist/"
23
+ },
24
+ {
25
+ "from": "./node_modules/chart.js/dist/chart.umd*",
26
+ "to": "./dist/"
24
27
  }
25
- ]
28
+ ],
29
+ "devDependencies": {
30
+ "chart.js": "^4.5.1"
31
+ }
26
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pict-section-form",
3
- "version": "1.0.138",
3
+ "version": "1.0.139",
4
4
  "description": "Pict dynamic form sections",
5
5
  "main": "source/Pict-Section-Form.js",
6
6
  "directories": {
@@ -31,7 +31,7 @@
31
31
  "browser-env": "^3.3.0",
32
32
  "eslint": "^9.38.0",
33
33
  "jquery": "^3.7.1",
34
- "pict": "^1.0.314",
34
+ "pict": "^1.0.318",
35
35
  "pict-application": "^1.0.29",
36
36
  "pict-service-commandlineutility": "^1.0.15",
37
37
  "quackage": "^1.0.42",
@@ -16,6 +16,7 @@ const libInputHTML = require('./inputs/Pict-Provider-Input-HTML.js');
16
16
  const libInputPreciseNumber = require('./inputs/Pict-Provider-Input-PreciseNumber.js');
17
17
  const libInputLink = require('./inputs/Pict-Provider-Input-Link.js');
18
18
  const libInputTemplatedEntityLookup = require('./inputs/Pict-Provider-Input-TemplatedEntityLookup.js');
19
+ const libInputChart = require('./inputs/Pict-Provider-Input-Chart.js');
19
20
 
20
21
  /** @type {Record<string, any>} */
21
22
  const _DefaultProviderConfiguration = (
@@ -74,7 +75,22 @@ class PictDynamicSolver extends libPictProvider
74
75
  this.pict.addProviderSingleton('Pict-Input-PreciseNumber', libInputPreciseNumber.default_configuration, libInputPreciseNumber);
75
76
  this.pict.addProviderSingleton('Pict-Input-TemplatedEntityLookup', libInputTemplatedEntityLookup.default_configuration, libInputTemplatedEntityLookup);
76
77
  this.pict.addProviderSingleton('Pict-Input-Link', libInputLink.default_configuration, libInputLink);
77
- this.pict.addProviderSingleton('Pict-Input-Link', libInputLink.default_configuration, libInputLink);
78
+ this.pict.addProviderSingleton('Pict-Input-Chart', libInputChart.default_configuration, libInputChart);
79
+ }
80
+
81
+ runSolver(pSolverExpression)
82
+ {
83
+ let tmpViewMarshalDestinationObject = this.pict.resolveStateFromAddress(this.pict.views.PictFormMetacontroller.viewMarshalDestination);
84
+
85
+ if ((typeof(tmpViewMarshalDestinationObject) !== 'object') || (tmpViewMarshalDestinationObject === null))
86
+ {
87
+ tmpViewMarshalDestinationObject = this.pict.AppData;
88
+ }
89
+
90
+ let tmpResultsObject = {};
91
+ let tmpSolutionValue = this.fable.ExpressionParser.solve(pSolverExpression, tmpViewMarshalDestinationObject, tmpResultsObject,this.pict.manifest);
92
+
93
+ return tmpSolutionValue;
78
94
  }
79
95
 
80
96
  /**
@@ -248,6 +248,18 @@ Glug glug glug Oo... -->
248
248
  <!-- InputType Link {~D:Record.Hash~} {~D:Record.DataType~} -->
249
249
  <input type="hidden" {~D:Record.Macro.InputFullProperties~} value="">
250
250
  <a id="INPUT-FOR-{~D:Record.Macro.RawHTMLID~}">{~D:Record.Name~}</a>
251
+ `
252
+ },
253
+ {
254
+ "HashPostfix": "-Template-Input-InputType-Chart",
255
+ "DefaultInputExtensions": ["Pict-Input-Chart"],
256
+ "Template": /*HTML*/`
257
+ <!-- InputType Chart {~D:Record.Hash~} {~D:Record.DataType~} -->
258
+ <div style="width:{~DWAF:Record.PictForm.QuantizedWidth:100~}%; display:inline-block;">
259
+ <input type="hidden" id="CONFIG-FOR-{~D:Record.Macro.RawHTMLID~}" value="">
260
+ <h3><span>{~D:Record.Name~}:</span></h3>
261
+ <div style="width:100%;"><canvas id="CANVAS-FOR-{~D:Record.Macro.RawHTMLID~}" {~D:Record.Macro.InputFullProperties~}></canvas></div>
262
+ </div>
251
263
  `
252
264
  },
253
265
  {
@@ -0,0 +1,264 @@
1
+ const libPictSectionInputExtension = require('../Pict-Provider-InputExtension.js');
2
+
3
+ /**
4
+ * CustomInputHandler class.
5
+ *
6
+ * @class
7
+ * @extends libPictSectionInputExtension
8
+ * @memberof providers.inputs
9
+ */
10
+ class CustomInputHandler extends libPictSectionInputExtension
11
+ {
12
+ constructor(pFable, pOptions, pServiceHash)
13
+ {
14
+ super(pFable, pOptions, pServiceHash);
15
+
16
+ /** @type {import('pict')} */
17
+ this.pict;
18
+ /** @type {import('pict')} */
19
+ this.fable;
20
+ /** @type {any} */
21
+ this.log;
22
+
23
+ this.currentChartObjects = {};
24
+ }
25
+
26
+ getInputChartConfiguration(pView, pInput, pValue)
27
+ {
28
+ let tmpView = pView;
29
+ let tmpInput = pInput;
30
+ let tmpValue = pValue;
31
+
32
+ if (!('PictForm' in tmpInput))
33
+ {
34
+ return false;
35
+ }
36
+
37
+ let tmpPictform = pInput.PictForm;
38
+
39
+ let tmpChartConfiguration = (typeof(tmpPictform.ChartJSOptionsCorePrototype) === 'object') ? tmpPictform.ChartJSOptionsCorePrototype : {};
40
+
41
+ // Vet the most important two properties for defaults
42
+ if (!('type' in tmpChartConfiguration))
43
+ {
44
+ tmpChartConfiguration.type = (typeof(tmpPictform.ChartType) === 'string') ? tmpPictform.ChartType : 'bar';
45
+ }
46
+ if (!('data' in tmpChartConfiguration))
47
+ {
48
+ tmpChartConfiguration.data = {};
49
+ }
50
+
51
+ // See if there is a data solution configuration
52
+
53
+ // Start with Raw / defaults for labels and data
54
+ if (!('labels' in tmpChartConfiguration))
55
+ {
56
+ tmpChartConfiguration.data.labels = [];
57
+
58
+ if (Array.isArray(tmpPictform.ChartLabelsRaw))
59
+ {
60
+ tmpChartConfiguration.data.labels = tmpPictform.ChartLabelsRaw;
61
+ }
62
+ // Now do the configuration-based population behaviors
63
+
64
+ if (`ChartLabelsSolver` in tmpPictform)
65
+ {
66
+ let tmpSolvedLabels = this.pict.providers.DynamicSolver.runSolver(tmpPictform.ChartLabelsSolver);
67
+ if (Array.isArray(tmpSolvedLabels))
68
+ {
69
+ // TODO: This may need to get complex for multiple sets of labels?
70
+ tmpChartConfiguration.data.labels = tmpSolvedLabels;
71
+ }
72
+ }
73
+ }
74
+ if (!('datasets' in tmpChartConfiguration))
75
+ {
76
+ tmpChartConfiguration.data.datasets = [];
77
+
78
+ if (Array.isArray(tmpPictform.ChartDatasetsRaw))
79
+ {
80
+ tmpChartConfiguration.data.datasets = tmpChartConfiguration.data.datasets.concat(tmpPictform.ChartDatasetsRaw);
81
+ }
82
+
83
+ // Now see if there are any non-raw datasets to add
84
+ if (`ChartDataSolvers` in tmpPictform)
85
+ {
86
+ for (let i = 0; i < tmpPictform.ChartDataSolvers.length; i++)
87
+ {
88
+ let tmpDatasetSolverConfig = tmpPictform.ChartDataSolvers[i];
89
+ // TODO: Cache and check if it's changed before making it initialize data in the control again
90
+ let tmpDataSetSolved = this.pict.providers.DynamicSolver.runSolver(tmpDatasetSolverConfig.DataSolver);
91
+ if (!'Label' in tmpDatasetSolverConfig)
92
+ {
93
+ tmpDatasetSolverConfig.Label = `Dataset ${i+1}`;
94
+ }
95
+ let tmpNewDataset = {
96
+ label: tmpDatasetSolverConfig.Label,
97
+ data: (Array.isArray(tmpDataSetSolved)) ? tmpDataSetSolved : []
98
+ };
99
+ tmpChartConfiguration.data.datasets.push(tmpNewDataset);
100
+ }
101
+ }
102
+ }
103
+
104
+ return tmpChartConfiguration;
105
+ }
106
+
107
+ initializeChartVisualization(pView, pGroup, pRow, pInput, pValue, pHTMLSelector)
108
+ {
109
+ // Stuff the config into a hidden element for reference
110
+ let tmpConfigStorageLocation = `#CONFIG-FOR-${pInput.Macro.RawHTMLID}`;
111
+ let tmpChartConfiguration = this.getInputChartConfiguration(pView, pInput, pValue);
112
+ this.pict.ContentAssignment.assignContent(tmpConfigStorageLocation, JSON.stringify(tmpChartConfiguration));
113
+
114
+ let tmpChartCanvasElementSelector = `#CANVAS-FOR-${pInput.Macro.RawHTMLID}`;
115
+ let tmpChartCanvasElement = this.pict.ContentAssignment.getElement(tmpChartCanvasElementSelector);
116
+ if ((!Array.isArray(tmpChartCanvasElement)) || (tmpChartCanvasElement.length < 1))
117
+ {
118
+ this.log.warn(`Chart canvas element not found for input ${tmpChartCanvasElementSelector}`);
119
+ return false;
120
+ }
121
+ tmpChartCanvasElement = tmpChartCanvasElement[0]
122
+
123
+ // Check if there is a window.Chart which is the Chart.js library
124
+ if (typeof(window.Chart) !== 'function')
125
+ {
126
+ this.log.warn(`Chart.js library not loaded for input ${tmpChartCanvasElementSelector}`);
127
+ }
128
+ else
129
+ {
130
+ this.currentChartObjects[`Object-For-${pInput.Macro.RawHTMLID}`] = new window.Chart(tmpChartCanvasElement, tmpChartConfiguration);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Initializes the input element for the Pict provider select input.
136
+ *
137
+ * @param {Object} pView - The view object.
138
+ * @param {Object} pGroup - The group object.
139
+ * @param {Object} pRow - The row object.
140
+ * @param {Object} pInput - The input object.
141
+ * @param {any} pValue - The input value.
142
+ * @param {string} pHTMLSelector - The HTML selector.
143
+ * @param {string} pTransactionGUID - The transaction GUID for the event dispatch.
144
+ * @returns {boolean} - Returns true if the input element is successfully initialized, false otherwise.
145
+ */
146
+ onInputInitialize(pView, pGroup, pRow, pInput, pValue, pHTMLSelector, pTransactionGUID)
147
+ {
148
+ this.initializeChartVisualization(pView, pGroup, pRow, pInput, pValue, pHTMLSelector);
149
+ return super.onInputInitialize(pView, pGroup, pRow, pInput, pValue, pHTMLSelector, pTransactionGUID);
150
+ }
151
+
152
+ /**
153
+ * Initializes a tabular input element.
154
+ *
155
+ * @param {Object} pView - The view object.
156
+ * @param {Object} pGroup - The group object.
157
+ * @param {Object} pInput - The input object.
158
+ * @param {any} pValue - The input value.
159
+ * @param {string} pHTMLSelector - The HTML selector.
160
+ * @param {number} pRowIndex - The index of the row.
161
+ * @param {string} pTransactionGUID - The transaction GUID for the event dispatch.
162
+ * @returns {any} - The result of the initialization.
163
+ */
164
+ onInputInitializeTabular(pView, pGroup, pInput, pValue, pHTMLSelector, pRowIndex, pTransactionGUID)
165
+ {
166
+ return super.onInputInitializeTabular(pView, pGroup, pInput, pValue, pHTMLSelector, pRowIndex, pTransactionGUID);
167
+ }
168
+
169
+ /**
170
+ * Handles the change event for the data in the select input.
171
+ *
172
+ * @param {Object} pView - The view object.
173
+ * @param {Object} pInput - The input object.
174
+ * @param {any} pValue - The new value of the input.
175
+ * @param {string} pHTMLSelector - The HTML selector of the input.
176
+ * @param {string} pTransactionGUID - The transaction GUID for the event dispatch.
177
+ * @returns {any} - The result of the super.onDataChange method.
178
+ */
179
+ onDataChange(pView, pInput, pValue, pHTMLSelector, pTransactionGUID)
180
+ {
181
+ return super.onDataChange(pView, pInput, pValue, pHTMLSelector, pTransactionGUID);
182
+ }
183
+
184
+ /**
185
+ * Handles the change event for tabular data.
186
+ *
187
+ * @param {Object} pView - The view object.
188
+ * @param {Object} pInput - The input object.
189
+ * @param {any} pValue - The new value.
190
+ * @param {string} pHTMLSelector - The HTML selector.
191
+ * @param {number} pRowIndex - The index of the row.
192
+ * @param {string} pTransactionGUID - The transaction GUID for the event dispatch.
193
+ * @returns {any} - The result of the super method.
194
+ */
195
+ onDataChangeTabular(pView, pInput, pValue, pHTMLSelector, pRowIndex, pTransactionGUID)
196
+ {
197
+ return super.onDataChangeTabular(pView, pInput, pValue, pHTMLSelector, pRowIndex, pTransactionGUID);
198
+ }
199
+
200
+ /**
201
+ * Marshals data to the form for the given input.
202
+ *
203
+ * @param {Object} pView - The view object.
204
+ * @param {Object} pGroup - The group object.
205
+ * @param {Object} pRow - The row object.
206
+ * @param {Object} pInput - The input object.
207
+ * @param {any} pValue - The value to be marshaled.
208
+ * @param {string} pHTMLSelector - The HTML selector.
209
+ * @param {string} pTransactionGUID - The transaction GUID for the event dispatch.
210
+ * @returns {boolean} - Returns true if the value is successfully marshaled to the form, otherwise false.
211
+ */
212
+ onDataMarshalToForm(pView, pGroup, pRow, pInput, pValue, pHTMLSelector, pTransactionGUID)
213
+ {
214
+ return super.onDataMarshalToForm(pView, pGroup, pRow, pInput, pValue, pHTMLSelector, pTransactionGUID);
215
+ }
216
+
217
+ /**
218
+ * Marshals data to a form in tabular format.
219
+ *
220
+ * @param {Object} pView - The view object.
221
+ * @param {Object} pGroup - The group object.
222
+ * @param {Object} pInput - The input object.
223
+ * @param {any} pValue - The value parameter.
224
+ * @param {string} pHTMLSelector - The HTML selector parameter.
225
+ * @param {number} pRowIndex - The row index parameter.
226
+ * @param {string} pTransactionGUID - The transaction GUID for the event dispatch.
227
+ * @returns {any} - The result of the data marshaling.
228
+ */
229
+ onDataMarshalToFormTabular(pView, pGroup, pInput, pValue, pHTMLSelector, pRowIndex, pTransactionGUID)
230
+ {
231
+ return super.onDataMarshalToFormTabular(pView, pGroup, pInput, pValue, pHTMLSelector, pRowIndex, pTransactionGUID);
232
+ }
233
+
234
+ /**
235
+ * Handles the data request event for a select input in the PictProviderInputSelect class.
236
+ *
237
+ * @param {Object} pView - The view object.
238
+ * @param {Object} pInput - The input object.
239
+ * @param {any} pValue - The value object.
240
+ * @param {string} pHTMLSelector - The HTML selector object.
241
+ * @returns {any} - The result of the onDataRequest method.
242
+ */
243
+ onDataRequest(pView, pInput, pValue, pHTMLSelector)
244
+ {
245
+ return super.onDataRequest(pView, pInput, pValue, pHTMLSelector);
246
+ }
247
+
248
+ /**
249
+ * Handles the data request event for a tabular input.
250
+ *
251
+ * @param {Object} pView - The view object.
252
+ * @param {Object} pInput - The input object.
253
+ * @param {any} pValue - The value object.
254
+ * @param {string} pHTMLSelector - The HTML selector.
255
+ * @param {number} pRowIndex - The row index.
256
+ * @returns {any} - The result of the data request.
257
+ */
258
+ onDataRequestTabular(pView, pInput, pValue, pHTMLSelector, pRowIndex)
259
+ {
260
+ return super.onDataRequestTabular(pView, pInput, pValue, pHTMLSelector, pRowIndex);
261
+ }
262
+ }
263
+
264
+ module.exports = CustomInputHandler;
@@ -37,7 +37,9 @@ class RecordLayout extends libPictSectionGroupLayout
37
37
  tmpTemplate += tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView, `-Template-Group-Prefix`, `getGroup("${pGroup.GroupIndex}")`);
38
38
  for (let j = 0; j < pGroup.Rows.length; j++)
39
39
  {
40
- let tmpRow = pGroup.Rows[j];
40
+ // TODO: Validate that the row exists? Bootstrap seems to have it here.
41
+ let tmpRow = pGroup.Rows[j]
42
+ tmpRow.WidthTotalRaw = 0;
41
43
 
42
44
  tmpTemplate += tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView, `-Template-Row-Prefix`, `getGroup("${pGroup.GroupIndex}")`);
43
45
 
@@ -48,9 +50,56 @@ class RecordLayout extends libPictSectionGroupLayout
48
50
  tmpInput.PictForm.InputIndex = k;
49
51
  tmpInput.PictForm.GroupIndex = pGroup.GroupIndex;
50
52
  tmpInput.PictForm.RowIndex = j;
53
+ let tmpInputWidth = 1;
54
+ try
55
+ {
56
+ tmpInputWidth = Math.abs(parseFloat(tmpInput.PictForm.Width));
57
+ }
58
+ catch (pParseError)
59
+ {
60
+ tmpInputWidth = 1;
61
+ }
62
+ if (!tmpInputWidth || isNaN(tmpInputWidth) || tmpInputWidth <= 0)
63
+ {
64
+ tmpInputWidth = 1;
65
+ }
66
+ tmpInput.PictForm.RawWidth = tmpInputWidth;
67
+ tmpRow.WidthTotalRaw += tmpInputWidth;
68
+
69
+ //tmpTemplate += tmpMetatemplateGenerator.getInputMetatemplateTemplateReference(pView, tmpInput.DataType, tmpInput.PictForm.InputType, `getInput("${pGroup.GroupIndex}","${j}","${k}")`);
70
+ }
71
+ // Now that we've gotten all the raw widths for the row, quantize them properly.
72
+ // Default by quantizing to 99 percentage units wide
73
+ let tmpGroupQuantizedWidth = 95;
74
+ if ('WidthQuantization' in pGroup)
75
+ {
76
+ try
77
+ {
78
+ tmpGroupQuantizedWidth = Math.abs(parseInt(pGroup.WidthQuantization));
79
+ if (!tmpGroupQuantizedWidth || isNaN(tmpGroupQuantizedWidth) || tmpGroupQuantizedWidth <= 0)
80
+ {
81
+ tmpGroupQuantizedWidth = 95;
82
+ }
83
+ }
84
+ catch(pParseError)
85
+ {
86
+ // TODO: UGH THIS IS NaN at the moment.......
87
+ tmpGroupQuantizedWidth = 95;
88
+ }
89
+ }
90
+ else if ('WidthQuantization' in this.pict.PictApplication.options)
91
+ {
92
+ tmpGroupQuantizedWidth = this.pict.PictApplication.options.WidthQuantization;
93
+ }
94
+ for (let k = 0; k < tmpRow.Inputs.length; k++)
95
+ {
96
+ let tmpInput = tmpRow.Inputs[k];
97
+ tmpInput.PictForm.QuantizedWidth = Math.round((tmpInput.PictForm.RawWidth / tmpRow.WidthTotalRaw) * tmpGroupQuantizedWidth);
98
+ //this.fable.log.trace(`Quantized input width for Group ${pGroup.GroupIndex} Row ${j} Input ${k} (${tmpInput.Name}) to ${tmpInput.PictForm.QuantizedWidth} (Raw: ${tmpInput.PictForm.RawWidth} / Total Raw: ${tmpRow.WidthTotalRaw} / Group Quantization: ${tmpGroupQuantizedWidth})`);
51
99
 
52
100
  tmpTemplate += tmpMetatemplateGenerator.getInputMetatemplateTemplateReference(pView, tmpInput.DataType, tmpInput.PictForm.InputType, `getInput("${pGroup.GroupIndex}","${j}","${k}")`);
53
101
  }
102
+
54
103
  tmpTemplate += tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView, `-Template-Row-Postfix`, `getGroup("${pGroup.GroupIndex}")`);
55
104
  }
56
105
  tmpTemplate += tmpMetatemplateGenerator.getMetatemplateTemplateReference(pView, `-Template-Group-Postfix`, `getGroup("${pGroup.GroupIndex}")`);
@@ -903,7 +903,7 @@ class PictFormMetacontroller extends libPictViewClass
903
903
  {
904
904
  let tmpDescriptor = tmpManifest.elementDescriptors[tmpDescriptorKeys[i]];
905
905
 
906
- if (tmpDescriptor && tmpDescriptor.PictForm)
906
+ if (tmpDescriptor)
907
907
  {
908
908
  this.pict.manifest.addDescriptor(tmpDescriptorKeys[i], tmpDescriptor);
909
909
  }