import {InlineDataset, isUrlData} from '../../data'; import {Dict} from '../../util'; import {VgData} from '../../vega.schema'; import {DataComponent} from './'; import {AggregateNode} from './aggregate'; import {BinNode} from './bin'; import {CalculateNode} from './calculate'; import {DataFlowNode, OutputNode} from './dataflow'; import {DensityTransformNode} from './density'; import {ExtentTransformNode} from './extent'; import {FacetNode} from './facet'; import {FilterNode} from './filter'; import {FilterInvalidNode} from './filterinvalid'; import {FlattenTransformNode} from './flatten'; import {FoldTransformNode} from './fold'; import {ParseNode} from './formatparse'; import {GeoJSONNode} from './geojson'; import {GeoPointNode} from './geopoint'; import {GraticuleNode} from './graticule'; import {IdentifierNode} from './identifier'; import {ImputeNode} from './impute'; import {JoinAggregateTransformNode} from './joinaggregate'; import {LoessTransformNode} from './loess'; import {LookupNode} from './lookup'; import {QuantileTransformNode} from './quantile'; import {RegressionTransformNode} from './regression'; import {PivotTransformNode} from './pivot'; import {SampleTransformNode} from './sample'; import {SequenceNode} from './sequence'; import {SourceNode} from './source'; import {StackNode} from './stack'; import {TimeUnitNode} from './timeunit'; import {WindowTransformNode} from './window'; function makeWalkTree(data: VgData[]) { // to name datasources let datasetIndex = 0; /** * Recursively walk down the tree. */ function walkTree(node: DataFlowNode, dataSource: VgData) { if (node instanceof SourceNode) { // If the source is a named data source or a data source with values, we need // to put it in a different data source. Otherwise, Vega may override the data. if (!node.isGenerator && !isUrlData(node.data)) { data.push(dataSource); const newData: VgData = { name: null, source: dataSource.name, transform: [] }; dataSource = newData; } } if (node instanceof ParseNode) { if (node.parent instanceof SourceNode && !dataSource.source) { // If node's parent is a root source and the data source does not refer to another data source, use normal format parse dataSource.format = { ...dataSource.format, parse: node.assembleFormatParse() }; // add calculates for all nested fields dataSource.transform.push(...node.assembleTransforms(true)); } else { // Otherwise use Vega expression to parse dataSource.transform.push(...node.assembleTransforms()); } } if (node instanceof FacetNode) { if (!dataSource.name) { dataSource.name = `data_${datasetIndex++}`; } if (!dataSource.source || dataSource.transform.length > 0) { data.push(dataSource); node.data = dataSource.name; } else { node.data = dataSource.source; } data.push(...node.assemble()); // break here because the rest of the tree has to be taken care of by the facet. return; } if ( node instanceof GraticuleNode || node instanceof SequenceNode || node instanceof FilterInvalidNode || node instanceof FilterNode || node instanceof CalculateNode || node instanceof GeoPointNode || node instanceof AggregateNode || node instanceof LookupNode || node instanceof WindowTransformNode || node instanceof JoinAggregateTransformNode || node instanceof FoldTransformNode || node instanceof FlattenTransformNode || node instanceof DensityTransformNode || node instanceof LoessTransformNode || node instanceof QuantileTransformNode || node instanceof RegressionTransformNode || node instanceof IdentifierNode || node instanceof SampleTransformNode || node instanceof PivotTransformNode || node instanceof ExtentTransformNode ) { dataSource.transform.push(node.assemble()); } if ( node instanceof BinNode || node instanceof TimeUnitNode || node instanceof ImputeNode || node instanceof StackNode || node instanceof GeoJSONNode ) { dataSource.transform.push(...node.assemble()); } if (node instanceof OutputNode) { if (dataSource.source && dataSource.transform.length === 0) { node.setSource(dataSource.source); } else if (node.parent instanceof OutputNode) { // Note that an output node may be required but we still do not assemble a // separate data source for it. node.setSource(dataSource.name); } else { if (!dataSource.name) { dataSource.name = `data_${datasetIndex++}`; } // Here we set the name of the datasource we generated. From now on // other assemblers can use it. node.setSource(dataSource.name); // if this node has more than one child, we will add a datasource automatically if (node.numChildren() === 1) { data.push(dataSource); const newData: VgData = { name: null, source: dataSource.name, transform: [] }; dataSource = newData; } } } switch (node.numChildren()) { case 0: // done if (node instanceof OutputNode && (!dataSource.source || dataSource.transform.length > 0)) { // do not push empty datasources that are simply references data.push(dataSource); } break; case 1: walkTree(node.children[0], dataSource); break; default: { if (!dataSource.name) { dataSource.name = `data_${datasetIndex++}`; } let source = dataSource.name; if (!dataSource.source || dataSource.transform.length > 0) { data.push(dataSource); } else { source = dataSource.source; } for (const child of node.children) { const newData: VgData = { name: null, source, transform: [] }; walkTree(child, newData); } break; } } } return walkTree; } /** * Assemble data sources that are derived from faceted data. */ export function assembleFacetData(root: FacetNode): VgData[] { const data: VgData[] = []; const walkTree = makeWalkTree(data); for (const child of root.children) { walkTree(child, { source: root.name, name: null, transform: [] }); } return data; } /** * Create Vega data array from a given compiled model and append all of them to the given array * * @param model * @param data array * @return modified data array */ export function assembleRootData(dataComponent: DataComponent, datasets: Dict): VgData[] { const data: VgData[] = []; // dataComponent.sources.forEach(debug); // draw(dataComponent.sources); const walkTree = makeWalkTree(data); let sourceIndex = 0; for (const root of dataComponent.sources) { // assign a name if the source does not have a name yet if (!root.hasName()) { root.dataName = `source_${sourceIndex++}`; } const newData: VgData = root.assemble(); walkTree(root, newData); } // remove empty transform arrays for cleaner output for (const d of data) { if (d.transform.length === 0) { delete d.transform; } } // move sources without transforms (the ones that are potentially used in lookups) to the beginning let whereTo = 0; for (const [i, d] of data.entries()) { if ((d.transform ?? []).length === 0 && !d.source) { data.splice(whereTo++, 0, data.splice(i, 1)[0]); } } // now fix the from references in lookup transforms for (const d of data) { for (const t of d.transform ?? []) { if (t.type === 'lookup') { t.from = dataComponent.outputNodes[t.from].getSource(); } } } // inline values for datasets that are in the datastore for (const d of data) { if (d.name in datasets) { d.values = datasets[d.name]; } } return data; }