protected updateRun()

in powerbi-visual-builder/src_visual/visual.ts [665:1083]


    protected updateRun(options: VisualUpdateOptions) {
      try {
        const dataView = options.dataViews[0];
        this.resize(options.viewport.width, options.viewport.height);

        const getDatasetResult = this.getDataset(dataView);

        if (getDatasetResult == null) {
          // If dataset is null, show a warning message
          this.divChart.innerHTML = `
                <h2>Dataset incomplete. Please specify all data fields.</h2>
            `;
          this.currentDatasetJSON = null;
          this.handleMouseMove = null;
          this.unmountContainer();
        } else {
          try {
            CharticulatorContainer.ChartContainer.setFormatOptions({
              currency: parseSafe(
                (dataView.metadata.objects?.[SettingsNames.General]
                  .currency as any) || defaultCurrency,
                defaultCurrency
              ),
              decimal:
                (dataView.metadata.objects?.[SettingsNames.General]
                  .decimalSeparator as any) || defaultDecimalSeparator,
              thousands:
                (dataView.metadata.objects?.[SettingsNames.General]
                  .thousandSeparator as any) || defaultThousandSeparator,
              grouping: parseSafe(
                (dataView.metadata.objects?.[SettingsNames.General]
                  .group as any) || defaultGroup,
                defaultGroup
              )
            });
          } catch (ex) {
            console.warn("Loading localization settings failed");
          }
          const { dataset } = getDatasetResult;
          // Check if dataset is the same
          const datasetJSON = JSON.stringify(dataset);
          if (datasetJSON != this.currentDatasetJSON) {
            this.handleMouseMove = null;
            this.unmountContainer();
          }
          this.currentDatasetJSON = datasetJSON;

          // Recreate chartContainer if not exist
          if (!this.chartContainer) {
            this.divChart.innerHTML = "";
            this.chartTemplate.reset();

            const defaultTable = this.getDefaultTable(this.template);
            const columns = defaultTable.columns as PowerBIColumn[];
            this.chartTemplate.assignTable(
              defaultTable.name,
              defaultTable.name
            );
            for (const column of columns) {
              this.chartTemplate.assignColumn(
                defaultTable.name,
                column.name,
                column.powerBIName || column.name
              );
            }

            // links table
            const linksTable = this.getLinksTable(this.template); // todo save table name in property
            const links = linksTable && (linksTable.columns as PowerBIColumn[]);
            if (links) {
              this.chartTemplate.assignTable(linksTable.name, linksTable.name);
              for (const column of links) {
                this.chartTemplate.assignColumn(
                  linksTable.name,
                  column.name,
                  column.powerBIName || column.name
                );
              }
            }

            // tooltips table
            const tooltipsTable = this.getTooltipsTable(this.template); // todo save table name in property
            const tooltips =
              tooltipsTable && (tooltipsTable.columns as PowerBIColumn[]);
            if (tooltips) {
              this.chartTemplate.assignTable(
                tooltipsTable.name,
                powerBITooltipsTablename
              );
              for (const column of tooltips) {
                this.chartTemplate.assignColumn(
                  tooltipsTable.name,
                  column.name,
                  column.powerBIName || column.name
                );
              }
            }

            const instance = this.chartTemplate.instantiate(dataset);
            const { chart } = instance;

            // Apply chart properties
            for (const property of this.template
              .properties as PowerBIProperty[]) {
              if (this.properties[property.powerBIName] === undefined) {
                continue;
              }
              const targetProperty = property.target.property;
              if (targetProperty) {
                if (
                  typeof targetProperty === "object" &&
                  (targetProperty.property === "xData" ||
                    targetProperty.property === "yData" ||
                    targetProperty.property === "axis") &&
                  targetProperty.field === "categories"
                ) {
                  const direction = this.properties[property.powerBIName];
                  let values = CharticulatorContainer.ChartTemplate.GetChartProperty(
                    chart,
                    property.objectID,
                    {
                      property: targetProperty.property,
                      field: targetProperty.field
                    }
                  );
                  if (values) {
                    values = this.deepClone(values);
                    values = (values as string[]).sort();
                    if (direction === "descending") {
                      values = (values as string[]).reverse();
                    }
                    CharticulatorContainer.ChartTemplate.SetChartProperty(
                      chart,
                      property.objectID,
                      targetProperty,
                      values
                    );
                  }
                } else {
                  if (this.properties[property.powerBIName] != null) {
                    CharticulatorContainer.ChartTemplate.SetChartProperty(
                      chart,
                      property.objectID,
                      targetProperty,
                      this.properties[property.powerBIName]
                    );
                  }
                }
              } else {
                if (this.properties[property.powerBIName] != null) {
                  CharticulatorContainer.ChartTemplate.SetChartAttributeMapping(
                    chart,
                    property.objectID,
                    property.target.attribute,
                    {
                      type: "value",
                      value: this.properties[property.powerBIName]
                    } as CharticulatorContainer.Specification.ValueMapping
                  );
                }
              }
            }

            // Make selection ids:
            const selectionIDs: visuals.ISelectionId[] = [];
            const selectionID2RowIndex = new WeakMap<ISelectionId, number>();
            const rowIndex2selectionID = new Map<number, ISelectionId>();
            dataset.tables[0].rows.forEach((row, i) => {
              const selectionID = this.host
                .createSelectionIdBuilder()
                .withCategory(
                  options.dataViews[0].categorical.categories[0],
                  getDatasetResult.rowInfo.get(row).index
                )
                .createSelectionId();
              selectionIDs.push(selectionID);
              selectionID2RowIndex.set(selectionID, i);
              rowIndex2selectionID.set(i, selectionID);
            });
            this.chartContainer = new CharticulatorContainer.ChartContainer(
              instance,
              dataset
            );
            this.chartContainer.addSelectionListener((table, rowIndices) => {
              if (
                table != null &&
                rowIndices != null &&
                rowIndices.length > 0
              ) {
                // Power BI's toggle behavior
                let alreadySelected = false;
                if (this.selectionManager.hasSelection()) {
                  const ids = this.selectionManager
                    .getSelectionIds()
                    .map(id => selectionID2RowIndex.get(id));
                  if (arrayEquals(ids, rowIndices)) {
                    alreadySelected = true;
                  }
                }
                if (alreadySelected) {
                  this.selectionManager.clear();
                  this.chartContainer.clearSelection();
                } else {
                  const ids = rowIndices
                    .map(i => selectionIDs[i])
                    .filter(x => x != null);
                  this.selectionManager.select(ids);
                  this.chartContainer.setSelection(table, rowIndices);
                }
              } else {
                this.selectionManager.clear();
              }
            });

            if (this.enableDrillDown) {
              const service = this.selectionManager;
              this.chartContainer.addContextMenuListener(
                // TODO change to point object
                (table, rowIndices, options) => {
                  const { clientX, clientY, event } = options;
                  if ((service as any).showContextMenu) {
                    const selection = rowIndex2selectionID.get(rowIndices[0]);
                    (service as any).showContextMenu(selection, {
                      x: clientX,
                      y: clientY
                    });
                    event.preventDefault();
                  }
                }
              );
            }
            const powerBITooltips = dataset.tables.find(
              table => table.name === powerBITooltipsTablename
            );
            const tooltipsTableColumns = [
              ...(options.dataViews[0].categorical.categories
                ? options.dataViews[0].categorical.categories
                : []),
              ...(options.dataViews[0].categorical.values
                ? options.dataViews[0].categorical.values
                : [])
            ];
            const visualHasTooltipData = tooltipsTableColumns.find(
              column => column.source.roles.powerBITooltips
            );
            if (
              this.host.tooltipService.enabled() &&
              powerBITooltips &&
              visualHasTooltipData
            ) {
              const service = this.host.tooltipService;
              this.chartContainer.addMouseEnterListener((table, rowIndices) => {
                const ids = rowIndices
                  .map(i => selectionIDs[i])
                  .filter(x => x != null);

                const info = {
                  coordinates: [this.currentX, this.currentY],
                  isTouchEvent: false,
                  dataItems: Array.prototype.concat(
                    ...rowIndices.map(idx => {
                      const tooltiprow =
                        powerBITooltips && powerBITooltips.rows[idx];
                      const row = dataset.tables[0].rows[idx];
                      return (
                        Object.keys(tooltiprow)
                          // excule _id column
                          .filter(x => x != "_id")
                          .map(key => {
                            const header = getDatasetResult.rowInfo.get(row)
                              .granularity;
                            let value = tooltiprow[key];

                            const column = [
                              ...dataset.tables[0].columns,
                              ...(dataset.tables[1]
                                ? dataset.tables[1].columns
                                : [])
                            ].filter(n => n.name === key)[0];

                            if (
                              value !== undefined &&
                              value !== null &&
                              column
                            ) {
                              if (
                                column.type ===
                                CharticulatorContainer.Specification.DataType
                                  .Number
                              ) {
                                value = parseFloat(value + "").toFixed(2);
                              } else if (
                                column.type ===
                                CharticulatorContainer.Specification.DataType
                                  .Date
                              ) {
                                const numVal = value as number;
                                if (typeof numVal.toFixed === "function") {
                                  const parsed = new Date(numVal);
                                  value = parsed.toDateString();
                                }
                              }
                            }

                            return {
                              displayName: key,
                              header,
                              value
                            };
                          })
                      );
                    })
                  ),
                  identities: ids
                };
                service.show(info);
                this.handleMouseMove = () => {
                  service.move({
                    ...info,
                    coordinates: [this.currentX, this.currentY]
                  });
                };
              });
              this.chartContainer.addMouseLeaveListener((table, rowIndices) => {
                service.hide({ isTouchEvent: false, immediately: false });
                this.handleMouseMove = null;
              });
              this.chartContainer.addMouseLeaveListener(
                (table, rowIndices) => {}
              );
            }
            this.chartContainer.mount(this.divChart);
          }

          if (this.chartContainer) {
            // Feed in properties
            for (const property of this.template
              .properties as PowerBIProperty[]) {
              if (this.properties[property.powerBIName] === undefined) {
                continue;
              }
              const targetProperty = property.target.property;
              if (targetProperty) {
                if (
                  typeof targetProperty === "object" &&
                  (targetProperty.property === "xData" ||
                    targetProperty.property === "yData" ||
                    targetProperty.property === "axis") &&
                  targetProperty.field === "categories"
                ) {
                  const direction = this.properties[property.powerBIName];
                  let values = this.chartContainer.getProperty(
                    property.objectID,
                    {
                      property: targetProperty.property,
                      field: targetProperty.field
                    }
                  );
                  if (values) {
                    values = this.deepClone(values);
                    values = (values as string[]).sort();
                    if (direction === "descending") {
                      values = (values as string[]).reverse();
                    }
                    this.chartContainer.setProperty(
                      property.objectID,
                      targetProperty,
                      values
                    );
                  }
                } else {
                  if (this.properties[property.powerBIName] != null) {
                    this.chartContainer.setProperty(
                      property.objectID,
                      targetProperty,
                      this.properties[property.powerBIName]
                    );
                  }
                }
              }
              if (
                property.target.attribute &&
                this.properties[property.powerBIName] != null
              ) {
                this.chartContainer.setAttributeMapping(
                  property.objectID,
                  property.target.attribute,
                  {
                    type: "value",
                    value: this.properties[property.powerBIName]
                  } as CharticulatorContainer.Specification.ValueMapping
                );
              }
            }
            if (getDatasetResult && getDatasetResult.rowInfo) {
              const rows = dataset.tables[0].rows;
              const indices = [];
              for (let i = 0; i < rows.length; i++) {
                if (getDatasetResult.rowInfo.get(rows[i]).highlight) {
                  indices.push(i);
                }
              }
              if (indices.length > 0) {
                const defaultTable = this.getDefaultTable(this.template);
                this.chartContainer.setSelection(defaultTable.name, indices);
              } else {
                this.chartContainer.clearSelection();
              }
            }
            this.chartContainer.resize(
              options.viewport.width,
              options.viewport.height
            );
          }
        }
      } catch (e) {
        console.log(e);
      }
    }