public build()

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;
  }