@@ -98,7 +98,7 @@ export function runCodeOnInputListsInVM(
98
98
let target = undefined ;
99
99
try {
100
100
// copy args to ensure correctness of mapping
101
- target = func ( ...JSON . parse ( JSON . stringify ( args ) ) )
101
+ target = func ( ...structuredClone ( args ) )
102
102
} catch ( err ) {
103
103
console . warn ( `execution err ${ err } ` )
104
104
}
@@ -179,7 +179,7 @@ export function baseTableToExtTable(table: any[], derivedFields: FieldItem[], al
179
179
let args = inputTuples ;
180
180
if ( func . length == baseCols . length * 2 + 1 ) {
181
181
// avoid side effect, use the copy of the column when calling the function
182
- args = [ ...inputTuples , rowIdx , ...JSON . parse ( JSON . stringify ( baseCols ) ) ]
182
+ args = [ ...inputTuples , rowIdx , ...structuredClone ( baseCols ) ]
183
183
}
184
184
185
185
target = func ( ...args ) ;
@@ -242,7 +242,13 @@ export function baseTableToExtTable(table: any[], derivedFields: FieldItem[], al
242
242
}
243
243
244
244
245
- export const instantiateVegaTemplate = ( chartType : string , encodingMap : { [ key in Channel ] : EncodingItem ; } , allFields : FieldItem [ ] , workingTable : any [ ] ) => {
245
+ export const assembleVegaChart = (
246
+ chartType : string ,
247
+ encodingMap : { [ key in Channel ] : EncodingItem ; } ,
248
+ conceptShelfItems : FieldItem [ ] ,
249
+ workingTable : any [ ] ,
250
+ maxNominalValues : number = 68
251
+ ) => {
246
252
247
253
if ( chartType == "Table" ) {
248
254
return [ "Table" , undefined ] ;
@@ -251,8 +257,7 @@ export const instantiateVegaTemplate = (chartType: string, encodingMap: { [key i
251
257
let chartTemplate = getChartTemplate ( chartType ) as ChartTemplate ;
252
258
//console.log(chartTemplate);
253
259
254
- let vgObj = JSON . parse ( JSON . stringify ( chartTemplate . template ) ) ;
255
- const baseTableSchemaObj : any = { } ;
260
+ let vgObj = structuredClone ( chartTemplate . template ) ;
256
261
257
262
for ( const [ channel , encoding ] of Object . entries ( encodingMap ) ) {
258
263
@@ -262,30 +267,8 @@ export const instantiateVegaTemplate = (chartType: string, encodingMap: { [key i
262
267
encodingObj [ "scale" ] = { "type" : "sqrt" , "zero" : true } ;
263
268
}
264
269
265
- const field = encoding . fieldID ? _ . find ( allFields , ( f ) => f . id === encoding . fieldID ) : undefined ;
270
+ const field = encoding . fieldID ? _ . find ( conceptShelfItems , ( f ) => f . id === encoding . fieldID ) : undefined ;
266
271
if ( field ) {
267
- //console.log(field)
268
- // the synthesizer only need to see base table schema
269
- let baseFields = ( field . source == "derived" ?
270
- ( field . transform as ConceptTransformation ) . parentIDs . map ( ( parentID ) => allFields . find ( ( f ) => f . id == parentID ) as FieldItem )
271
- : [ field ] ) ;
272
-
273
- for ( let baseField of baseFields ) {
274
- if ( Object . keys ( baseTableSchemaObj ) . includes ( baseField . name ) ) {
275
- continue ;
276
- }
277
- baseTableSchemaObj [ baseField . name ] = {
278
- channel,
279
- dtype : getDType ( baseField . type , workingTable . map ( r => r [ baseField . name ] ) ) ,
280
- name : baseField . name ,
281
- original : baseField . source == "original" ,
282
- // domain: {
283
- // values: [...new Set(baseField.domain.values)],
284
- // is_complete: baseField.domain.isComplete
285
- // },
286
- } ;
287
- }
288
-
289
272
// create the encoding
290
273
encodingObj [ "field" ] = field . name ;
291
274
encodingObj [ "type" ] = getDType ( field . type , workingTable . map ( r => r [ field . name ] ) ) ;
@@ -428,16 +411,8 @@ export const instantiateVegaTemplate = (chartType: string, encodingMap: { [key i
428
411
vgObj = chartTemplate . postProcessor ( vgObj , workingTable ) ;
429
412
}
430
413
431
- // console.log(JSON.stringify(vgObj))
432
-
433
- return [ vgObj , baseTableSchemaObj ] ;
434
- }
435
-
436
- export const assembleChart = ( chart : Chart , conceptShelfItems : FieldItem [ ] , dataValues : any [ ] ) => {
437
-
438
- let vgSpec : any = instantiateVegaTemplate ( chart . chartType , chart . encodingMap , conceptShelfItems , dataValues ) [ 0 ] ;
439
-
440
- let values = JSON . parse ( JSON . stringify ( dataValues ) ) ;
414
+ // this is the data that will be assembled into the vega chart
415
+ let values = structuredClone ( workingTable ) ;
441
416
values = values . map ( ( r : any ) => {
442
417
let keys = Object . keys ( r ) ;
443
418
let temporalKeys = keys . filter ( ( k : string ) => conceptShelfItems . some ( concept => concept . name == k && ( concept . type == "date" || concept . semanticType == "Year" ) ) ) ;
@@ -446,9 +421,82 @@ export const assembleChart = (chart: Chart, conceptShelfItems: FieldItem[], data
446
421
}
447
422
return r ;
448
423
} )
449
- return { ...vgSpec , data : { values : values } }
424
+
425
+ // Handle nominal axes with many entries
426
+ for ( const channel of [ 'x' , 'y' , 'column' , 'row' , 'xOffset' ] ) {
427
+ const encoding = vgObj . encoding ?. [ channel ] ;
428
+ if ( encoding ?. type === 'nominal' ) {
429
+ const fieldName = encoding . field ;
430
+ const uniqueValues = [ ...new Set ( values . map ( ( r : any ) => r [ fieldName ] ) ) ] ;
431
+
432
+ let valuesToKeep : any [ ] ;
433
+ if ( uniqueValues . length > maxNominalValues ) {
434
+
435
+ if ( channel == 'x' || channel == 'y' ) {
436
+ const oppositeChannel = channel === 'x' ? 'y' : 'x' ;
437
+ const oppositeEncoding = vgObj . encoding ?. [ oppositeChannel ] ;
438
+
439
+ if ( oppositeEncoding ?. type === 'quantitative' ) {
440
+ // Sort by the quantitative field and take top maxNominalValues
441
+ const quantField = oppositeEncoding . field ;
442
+ valuesToKeep = uniqueValues
443
+ . map ( val => ( {
444
+ value : val ,
445
+ sum : workingTable
446
+ . filter ( r => r [ fieldName ] === val )
447
+ . reduce ( ( sum , r ) => sum + ( r [ quantField ] || 0 ) , 0 )
448
+ } ) )
449
+ . sort ( ( a , b ) => b . sum - a . sum )
450
+ . slice ( 0 , maxNominalValues )
451
+ . map ( v => v . value ) ;
452
+ } else {
453
+ // If no quantitative axis, just take first maxNominalValues
454
+ valuesToKeep = uniqueValues . slice ( 0 , maxNominalValues ) ;
455
+ }
456
+ } else if ( channel == 'row' ) {
457
+ valuesToKeep = uniqueValues . slice ( 0 , 20 ) ;
458
+ } else {
459
+ valuesToKeep = uniqueValues . slice ( 0 , maxNominalValues ) ;
460
+ }
461
+
462
+ // Filter the working table
463
+ const omittedCount = uniqueValues . length - maxNominalValues ;
464
+ const placeholder = `...${ omittedCount } items omitted` ;
465
+ values = values . filter ( ( row : any ) => valuesToKeep . includes ( row [ fieldName ] ) ) ;
466
+
467
+ // Add text formatting configuration
468
+ if ( ! encoding . axis ) {
469
+ encoding . axis = { } ;
470
+ }
471
+ encoding . axis . labelColor = {
472
+ condition : {
473
+ test : `datum.label == '${ placeholder } '` ,
474
+ value : "#999999"
475
+ } ,
476
+ value : "#000000" // default color for other labels
477
+ } ;
478
+ encoding . axis . labelFont = {
479
+ condition : {
480
+ test : `datum.label == '${ placeholder } '` ,
481
+ value : "italic"
482
+ } ,
483
+ value : "normal" // default font style for other labels
484
+ } ;
485
+
486
+ // Add placeholder to domain
487
+ if ( ! encoding . scale ) {
488
+ encoding . scale = { } ;
489
+ }
490
+ encoding . scale . domain = [ ...valuesToKeep , placeholder ]
491
+ }
492
+ }
493
+ }
494
+
495
+ return { ...vgObj , data : { values : values } }
450
496
}
451
497
498
+
499
+
452
500
export const adaptChart = ( chart : Chart , targetTemplate : ChartTemplate ) => {
453
501
454
502
let discardedChannels = Object . entries ( chart . encodingMap ) . filter ( ( [ ch , enc ] ) => {
0 commit comments