vega-lite
Version:
Vega-Lite is a concise high-level language for interactive visualization.
114 lines (91 loc) • 3.69 kB
text/typescript
import {DataComponent} from './index.js';
import * as log from '../../log/index.js';
import {Model} from '../model.js';
import {DataFlowNode} from './dataflow.js';
import {Optimizer} from './optimizer.js';
import * as optimizers from './optimizers.js';
import {moveFacetDown} from './subtree.js';
export const FACET_SCALE_PREFIX = 'scale_';
export const MAX_OPTIMIZATION_RUNS = 5;
/**
* Iterates over a dataflow graph and checks whether all links are consistent.
*/
export function checkLinks(nodes: readonly DataFlowNode[]): boolean {
for (const node of nodes) {
for (const child of node.children) {
if (child.parent !== node) {
// log.error('Dataflow graph is inconsistent.', node, child);
return false;
}
}
if (!checkLinks(node.children)) {
return false;
}
}
return true;
}
/**
* Run the specified optimizer on the provided nodes.
*
* @param optimizer The optimizer instance to run.
* @param nodes A set of nodes to optimize.
*/
function runOptimizer(optimizer: Optimizer, nodes: DataFlowNode[]): boolean {
let modified = false;
for (const node of nodes) {
modified = optimizer.optimize(node) || modified;
}
return modified;
}
function optimizationDataflowHelper(dataComponent: DataComponent, model: Model, firstPass: boolean) {
let roots = dataComponent.sources;
let modified = false;
modified = runOptimizer(new optimizers.RemoveUnnecessaryOutputNodes(), roots) || modified;
modified = runOptimizer(new optimizers.RemoveUnnecessaryIdentifierNodes(model), roots) || modified;
// remove source nodes that don't have any children because they also don't have output nodes
roots = roots.filter((r) => r.numChildren() > 0);
modified = runOptimizer(new optimizers.RemoveUnusedSubtrees(), roots) || modified;
roots = roots.filter((r) => r.numChildren() > 0);
if (!firstPass) {
// Only run these optimizations after the optimizer has moved down the facet node.
// With this change, we can be more aggressive in the optimizations.
modified = runOptimizer(new optimizers.MoveParseUp(), roots) || modified;
modified = runOptimizer(new optimizers.MergeBins(model), roots) || modified;
modified = runOptimizer(new optimizers.RemoveDuplicateTimeUnits(), roots) || modified;
modified = runOptimizer(new optimizers.MergeParse(), roots) || modified;
modified = runOptimizer(new optimizers.MergeAggregates(), roots) || modified;
modified = runOptimizer(new optimizers.MergeTimeUnits(), roots) || modified;
modified = runOptimizer(new optimizers.MergeIdenticalNodes(), roots) || modified;
modified = runOptimizer(new optimizers.MergeOutputs(), roots) || modified;
}
dataComponent.sources = roots;
return modified;
}
/**
* Optimizes the dataflow of the passed in data component.
*/
export function optimizeDataflow(data: DataComponent, model: Model) {
// check before optimizations
checkLinks(data.sources);
let firstPassCounter = 0;
let secondPassCounter = 0;
for (let i = 0; i < MAX_OPTIMIZATION_RUNS; i++) {
if (!optimizationDataflowHelper(data, model, true)) {
break;
}
firstPassCounter++;
}
// move facets down and make a copy of the subtree so that we can have scales at the top level
data.sources.map(moveFacetDown);
for (let i = 0; i < MAX_OPTIMIZATION_RUNS; i++) {
if (!optimizationDataflowHelper(data, model, false)) {
break;
}
secondPassCounter++;
}
// check after optimizations
checkLinks(data.sources);
if (Math.max(firstPassCounter, secondPassCounter) === MAX_OPTIMIZATION_RUNS) {
log.warn(`Maximum optimization runs(${MAX_OPTIMIZATION_RUNS}) reached.`);
}
}