in src/app/template/index.ts [361:617]
public build(): Specification.Template.ChartTemplate {
this.reset();
const template = this.template;
for (const elementClass of this.manager.getElements()) {
let table = null;
if (Prototypes.isType(elementClass.object.classID, "plot-segment")) {
const plotSegment = elementClass.object as Specification.PlotSegment;
table = plotSegment.table;
}
if (Prototypes.isType(elementClass.object.classID, "links")) {
if (Prototypes.isType(elementClass.object.classID, "links.through")) {
const facetExpressions = (elementClass.object.properties
.linkThrough as any).facetExpressions as string[];
const mainTable = this.dataset.tables.find(
(table) => table.type === TableType.Main
);
this.addTable(mainTable.name);
for (const expression of facetExpressions) {
this.addColumn(mainTable.name, expression);
}
}
const linkTable = elementClass.object.properties
.linkTable as Specification.AttributeMap;
if (linkTable) {
const linksTableName = linkTable.table as string;
this.addTable(linksTableName); // TODO get table by type
const linksTable = this.dataset.tables.find(
(table) => table.name === linksTableName
);
linksTable.columns.forEach((linksColumn) =>
this.addColumn(linksTableName, linksColumn.name)
);
const table = this.dataset.tables[0];
const idColumn =
table && table.columns.find((column) => column.name === "id");
if (idColumn) {
this.addColumn(table.name, idColumn.name);
}
}
}
this.addObject(table, elementClass);
if (Prototypes.isType(elementClass.object.classID, "plot-segment")) {
const plotSegmentState = elementClass.state as Specification.PlotSegmentState;
for (const glyph of plotSegmentState.glyphs) {
this.addObject(table, this.manager.getClass(glyph));
for (const mark of glyph.marks) {
this.addObject(table, this.manager.getClass(mark));
}
// Only one glyph is enough.
break;
}
}
}
for (const scaleState of this.manager.chartState.scales) {
this.addObject(null, this.manager.getClass(scaleState));
}
this.addObject(null, this.manager.getChartClass(this.manager.chartState));
// need to foreach objects to find all used columns
try {
for (const item of forEachObject(this.manager.chart)) {
if (item.kind == ObjectItemKind.ChartElement) {
if (Prototypes.isType(item.chartElement.classID, "legend.custom")) {
const scaleMapping = item.chartElement.mappings
.mappingOptions as ScaleMapping;
scaleMapping.expression = this.trackColumnFromExpression(
scaleMapping.expression,
scaleMapping.table
);
}
if (Prototypes.isType(item.chartElement.classID, "plot-segment")) {
const plotSegment = item.chartElement as Specification.PlotSegment;
// need to parse all expression to get column name
const originalTable = plotSegment.table;
const filter = plotSegment.filter;
if (filter && filter.expression) {
this.trackColumnFromExpression(filter.expression, originalTable);
}
const groupBy = plotSegment.groupBy;
if (groupBy && groupBy.expression) {
this.trackColumnFromExpression(groupBy.expression, originalTable);
}
const xAxisExpression = (plotSegment.properties.xData as any)
?.expression;
if (xAxisExpression) {
this.trackColumnFromExpression(xAxisExpression, originalTable);
}
const yAxisExpression = (plotSegment.properties.yData as any)
?.expression;
if (yAxisExpression) {
this.trackColumnFromExpression(yAxisExpression, originalTable);
}
const axisExpression = (plotSegment.properties.axis as any)
?.expression;
if (axisExpression) {
this.trackColumnFromExpression(axisExpression, originalTable);
}
const sublayout = (plotSegment.properties
.sublayout as Region2DSublayoutOptions)?.order?.expression;
if (sublayout) {
this.trackColumnFromExpression(sublayout, originalTable);
}
}
if (Prototypes.isType(item.chartElement.classID, "links")) {
if (item.chartElement.classID == "links.through") {
const props = item.chartElement
.properties as Prototypes.Links.LinksProperties;
if (props.linkThrough.facetExpressions) {
props.linkThrough.facetExpressions = props.linkThrough.facetExpressions.map(
(x) =>
this.trackColumnFromExpression(
x,
(getById(
this.template.specification.elements,
props.linkThrough.plotSegment
) as Specification.PlotSegment).table
)
);
}
}
if (item.chartElement.classID == "links.table") {
const props = item.chartElement
.properties as Prototypes.Links.LinksProperties;
if (!this.usedColumns[props.linkTable.table]) {
this.trackTable(props.linkTable.table);
}
}
}
}
if (item.kind == "glyph") {
if (!this.usedColumns[item.glyph.table]) {
this.trackTable(item.glyph.table);
}
}
if (item.kind === ObjectItemKind.Mark) {
if (Prototypes.isType(item.mark.classID, "mark.nested-chart")) {
const nestedChart = item.mark;
const columnNameMap = Object.keys(
nestedChart.properties.columnNameMap
);
const mainTable = this.usedColumns[
this.manager.dataset.tables.find((t) => t.type === TableType.Main)
.name
];
columnNameMap.forEach(
(columnNames) => (mainTable[columnNames] = columnNames)
);
}
if (Prototypes.isType(item.mark.classID, "mark.data-axis")) {
try {
const glyphId = item.glyph._id;
const glyphPlotSegment = [
...forEachObject(this.manager.chart),
].find(
(item) =>
item.kind == ObjectItemKind.ChartElement &&
Prototypes.isType(
item.chartElement.classID,
"plot-segment"
) &&
(item.chartElement as any).glyph === glyphId
);
const dataExpressions = item.mark.properties
.dataExpressions as DataAxisExpression[];
const table = (glyphPlotSegment.chartElement as any).table;
dataExpressions.forEach((expression) => {
expression.expression = this.trackColumnFromExpression(
expression.expression,
table
);
});
} catch (ex) {
console.error(ex);
}
}
}
const mappings = item.object.mappings;
for (const [, mapping] of forEachMapping(mappings)) {
if (mapping.type == MappingType.scale) {
const scaleMapping = mapping as Specification.ScaleMapping;
scaleMapping.expression = this.trackColumnFromExpression(
scaleMapping.expression,
scaleMapping.table
);
if (!this.usedColumns[scaleMapping.table]) {
this.trackTable(scaleMapping.table);
}
}
if (mapping.type == MappingType.text) {
const textMapping = mapping as Specification.TextMapping;
if (!this.usedColumns[textMapping.table]) {
this.trackTable(textMapping.table);
}
textMapping.textExpression = this.trackColumnFromExpression(
textMapping.textExpression,
textMapping.table,
true
);
}
}
}
} catch (ex) {
console.error(ex);
}
// Extract data tables
// if usedColumns count is 0, error was happened, add all columns as used
const noUsedColumns = Object.keys(this.usedColumns).length === 0;
template.tables = this.dataset.tables
.map((table) => {
if (
Object.prototype.hasOwnProperty.call(this.tableColumns, table.name) &&
(this.usedColumns[table.name] || noUsedColumns)
) {
return {
name: table.name,
type: table.type,
columns: table.columns
.filter((x) => {
return (
(this.tableColumns[table.name].has(x.name) &&
this.usedColumns[table.name]?.[x.name]) ||
isReservedColumnName(x.name)
);
})
.map((x) => ({
displayName: x.displayName || x.name,
name: x.name,
type: x.type,
metadata: x.metadata,
})),
};
} else {
return null;
}
})
.filter((x) => x != null);
this.computeDefaultAttributes();
return template;
}