vega-lite
Version:
Vega-Lite is a concise high-level language for interactive visualization.
205 lines (170 loc) • 4.83 kB
text/typescript
import {DataSourceType} from '../../data.js';
import * as log from '../../log/index.js';
import {Dict, uniqueId} from '../../util.js';
/**
* A node in the dataflow tree.
*/
export abstract class DataFlowNode {
private _children: DataFlowNode[] = [];
private _parent: DataFlowNode = null;
protected _hash: string | number;
constructor(
parent: DataFlowNode,
public readonly debugName?: string,
) {
if (parent) {
this.parent = parent;
}
}
/**
* Clone this node with a deep copy but don't clone links to children or parents.
*/
public clone(): DataFlowNode {
throw new Error('Cannot clone node');
}
/**
* Return a hash of the node.
*/
public abstract hash(): string | number;
/**
* Set of fields that this node depends on.
*/
public abstract dependentFields(): Set<string>;
/**
* Set of fields that are being created by this node.
*/
public abstract producedFields(): Set<string>;
get parent() {
return this._parent;
}
/**
* Set the parent of the node and also add this node to the parent's children.
*/
set parent(parent: DataFlowNode) {
this._parent = parent;
if (parent) {
parent.addChild(this);
}
}
get children() {
return this._children;
}
public numChildren() {
return this._children.length;
}
public addChild(child: DataFlowNode, loc?: number) {
// do not add the same child twice
if (this._children.includes(child)) {
log.warn(log.message.ADD_SAME_CHILD_TWICE);
return;
}
if (loc !== undefined) {
this._children.splice(loc, 0, child);
} else {
this._children.push(child);
}
}
public removeChild(oldChild: DataFlowNode) {
const loc = this._children.indexOf(oldChild);
this._children.splice(loc, 1);
return loc;
}
/**
* Remove node from the dataflow.
*/
public remove() {
let loc = this._parent.removeChild(this);
for (const child of this._children) {
// do not use the set method because we want to insert at a particular location
child._parent = this._parent;
this._parent.addChild(child, loc++);
}
}
/**
* Insert another node as a parent of this node.
*/
public insertAsParentOf(other: DataFlowNode) {
const parent = other.parent;
parent.removeChild(this);
this.parent = parent;
other.parent = this;
}
public swapWithParent() {
const parent = this._parent;
const newParent = parent.parent;
// reconnect the children
for (const child of this._children) {
child.parent = parent;
}
// remove old links
this._children = []; // equivalent to removing every child link one by one
parent.removeChild(this);
const loc = parent.parent.removeChild(parent);
// swap two nodes but maintain order in children
this._parent = newParent;
newParent.addChild(this, loc);
parent.parent = this;
}
}
export class OutputNode extends DataFlowNode {
private _source: string;
private _name: string;
public clone(): this {
const cloneObj = new (this.constructor as any)();
cloneObj.debugName = `clone_${this.debugName}`;
cloneObj._source = this._source;
cloneObj._name = `clone_${this._name}`;
cloneObj.type = this.type;
cloneObj.refCounts = this.refCounts;
cloneObj.refCounts[cloneObj._name] = 0;
return cloneObj;
}
/**
* @param source The name of the source. Will change in assemble.
* @param type The type of the output node.
* @param refCounts A global ref counter map.
*/
constructor(
parent: DataFlowNode,
source: string,
public readonly type: DataSourceType,
private readonly refCounts: Dict<number>,
) {
super(parent, source);
this._source = this._name = source;
if (this.refCounts && !(this._name in this.refCounts)) {
this.refCounts[this._name] = 0;
}
}
public dependentFields() {
return new Set<string>();
}
public producedFields() {
return new Set<string>();
}
public hash() {
if (this._hash === undefined) {
this._hash = `Output ${uniqueId()}`;
}
return this._hash;
}
/**
* Request the datasource name and increase the ref counter.
*
* During the parsing phase, this will return the simple name such as 'main' or 'raw'.
* It is crucial to request the name from an output node to mark it as a required node.
* If nobody ever requests the name, this datasource will not be instantiated in the assemble phase.
*
* In the assemble phase, this will return the correct name.
*/
public getSource() {
this.refCounts[this._name]++;
return this._source;
}
public isRequired(): boolean {
return !!this.refCounts[this._name];
}
public setSource(source: string) {
this._source = source;
}
}