export default function()

in src/app/stores/action_handlers/chart.ts [18:534]


export default function (REG: ActionHandlerRegistry<AppStore, Actions.Action>) {
  REG.add(Actions.MapDataToChartElementAttribute, function (action) {
    const inferred =
      (action.hints && action.hints.scaleID) ||
      this.scaleInference(
        { chart: { table: action.table } },
        {
          expression: action.expression,
          valueType: action.valueType,
          valueKind: action.valueMetadata.kind,
          outputType: action.attributeType,
          hints: action.hints,
        }
      );
    if (inferred != null) {
      action.chartElement.mappings[action.attribute] = {
        type: MappingType.scale,
        table: action.table,
        expression: action.expression,
        valueType: action.valueType,
        scale: inferred,
        valueIndex:
          action.hints && action.hints.allowSelectValue != undefined ? 0 : null,
      } as Specification.ScaleMapping;
    } else {
      if (
        (action.valueType == Specification.DataType.String ||
          action.valueType == Specification.DataType.Boolean ||
          action.valueType == Specification.DataType.Number) &&
        action.attributeType == Specification.AttributeType.Text
      ) {
        // If the valueType is a number, use a format
        const format =
          action.valueType == Specification.DataType.Number ? ".1f" : undefined;
        action.chartElement.mappings[action.attribute] = {
          type: MappingType.text,
          table: action.table,
          textExpression: new Expression.TextExpression([
            { expression: Expression.parse(action.expression), format },
          ]).toString(),
        } as Specification.TextMapping;
      }
    }

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.AddChartElement, function (action) {
    this.saveHistory();

    if (action.classID === "mark.nested-chart") {
      return; // prevent to add nested chart into chart, nested chart can be created only in glyph
    }

    let glyph = this.currentGlyph;
    if (!glyph || this.chart.glyphs.indexOf(glyph) < 0) {
      glyph = this.chart.glyphs[0];
    }

    const newChartElement = this.chartManager.createObject(
      action.classID,
      glyph
    ) as Specification.PlotSegment;
    for (const key in action.properties) {
      newChartElement.properties[key] = action.properties[key];
    }
    // console.log(newPlotSegment);
    if (Prototypes.isType(action.classID, "plot-segment")) {
      newChartElement.filter = null;
      newChartElement.order = null;
    }

    this.chartManager.addChartElement(newChartElement);

    const idx = this.chart.elements.indexOf(newChartElement);
    const elementClass = this.chartManager.getChartElementClass(
      this.chartState.elements[idx]
    );

    for (const key in action.mappings) {
      if (Object.prototype.hasOwnProperty.call(action.mappings, key)) {
        const [value, mapping] = action.mappings[key];
        if (mapping != null) {
          if (mapping.type == MappingType._element) {
            const elementMapping = mapping as SnappingElementMapping;
            this.chartManager.chart.constraints.push({
              type: "snap",
              attributes: {
                element: newChartElement._id,
                attribute: key,
                targetElement: elementMapping.element,
                targetAttribute: elementMapping.attribute,
                gap: 0,
              },
            });
          } else {
            newChartElement.mappings[key] = mapping;
          }
        }
        if (value != null) {
          const idx = this.chart.elements.indexOf(newChartElement);
          this.chartState.elements[idx].attributes[key] = value;
          if (!elementClass.attributes[key].solverExclude) {
            this.addPresolveValue(
              Solver.ConstraintStrength.HARD,
              this.chartState.elements[idx].attributes,
              key,
              value as number
            );
          }
        }
      }
    }

    // TODO fix issue with applying
    if (action.properties.snapToClosestGuide) {
      const idx = this.chart.elements.indexOf(newChartElement);
      const x = this.chartState.elements[idx].attributes.x as number;
      const y = this.chartState.elements[idx].attributes.y as number;
      const [guideX, guideY] = this.getClosestSnappingGuide({
        x,
        y,
      });

      this.chartState.elements[idx].attributes.x = (guideX.guide as any).value;
      this.chartState.elements[idx].attributes.y = (guideY.guide as any).value;
    }

    this.currentSelection = new ChartElementSelection(newChartElement);
    this.emit(AppStore.EVENT_SELECTION);

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.SetPlotSegmentFilter, function (action) {
    this.saveHistory();
    action.plotSegment.filter = action.filter;
    // Filter updated, we need to regenerate some glyph states
    this.chartManager.remapPlotSegmentGlyphs(action.plotSegment);
    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.SetPlotSegmentGroupBy, function (action) {
    this.saveHistory();
    action.plotSegment.groupBy = action.groupBy;
    // Filter updated, we need to regenerate some glyph states
    this.chartManager.remapPlotSegmentGlyphs(action.plotSegment);
    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.UpdateChartElementAttribute, function (action) {
    this.saveHistory();

    const idx = this.chart.elements.indexOf(action.chartElement);
    if (idx < 0) {
      return;
    }
    const layoutState = this.chartState.elements[idx];
    for (const key in action.updates) {
      if (!Object.prototype.hasOwnProperty.call(action.updates, key)) {
        continue;
      }
      // Remove current mapping and any snapping constraint
      delete action.chartElement.mappings[key];
      this.chart.constraints = this.chart.constraints.filter((c) => {
        if (c.type == "snap") {
          if (
            c.attributes.element == action.chartElement._id &&
            c.attributes.attribute == key
          ) {
            return false;
          }
        }
        return true;
      });
      layoutState.attributes[key] = action.updates[key];
      this.addPresolveValue(
        Solver.ConstraintStrength.STRONG,
        layoutState.attributes,
        key,
        action.updates[key] as number
      );
    }

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.SetChartElementMapping, function (action) {
    this.saveHistory();

    if (action.mapping == null) {
      delete action.chartElement.mappings[action.attribute];
    } else {
      action.chartElement.mappings[action.attribute] = action.mapping;
      this.chart.constraints = this.chart.constraints.filter((c) => {
        if (c.type == "snap") {
          if (
            c.attributes.element == action.chartElement._id &&
            c.attributes.attribute == action.attribute
          ) {
            return false;
          }
        }
        return true;
      });
    }

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.SnapChartElements, function (action) {
    this.saveHistory();

    delete action.element.mappings[action.attribute];
    // Remove any existing snapping
    this.chart.constraints = this.chart.constraints.filter((c) => {
      if (c.type == "snap") {
        if (
          c.attributes.element == action.element._id &&
          c.attributes.attribute == action.attribute
        ) {
          return false;
        }
      }
      return true;
    });
    this.chart.constraints.push({
      type: "snap",
      attributes: {
        element: action.element._id,
        attribute: action.attribute,
        targetElement: action.targetElement._id,
        targetAttribute: action.targetAttribute,
        gap: 0,
      },
    });

    this.addPresolveValue(
      Solver.ConstraintStrength.STRONG,
      this.chartManager.getClassById(action.element._id).state.attributes,
      action.attribute,
      this.chartManager.getClassById(action.targetElement._id).state.attributes[
        action.targetAttribute
      ] as number
    );

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.SetObjectMappingScale, function (action) {
    this.saveHistory();

    if (
      action.scaleId == null ||
      action.object.mappings[action.property].type != MappingType.scale
    ) {
      return;
    } else {
      (action.object.mappings[action.property] as any).scale = action.scaleId;
    }

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.SetScaleAttribute, function (action) {
    this.saveHistory();

    if (action.mapping == null) {
      delete action.scale.mappings[action.attribute];
    } else {
      action.scale.mappings[action.attribute] = action.mapping;
    }

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.UpdateChartAttribute, function (action) {
    this.saveHistory();

    for (const key in action.updates) {
      if (!Object.prototype.hasOwnProperty.call(action.updates, key)) {
        continue;
      }
      this.chartState.attributes[key] = action.updates[key];
      this.addPresolveValue(
        Solver.ConstraintStrength.STRONG,
        this.chartState.attributes,
        key,
        action.updates[key] as number
      );
    }

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.BindDataToAxis, function (action: BindDataToAxis) {
    this.saveHistory();
    this.bindDataToAxis({
      ...action,
      autoDomainMax: true,
      autoDomainMin: true,
      domainMax: null,
      domainMin: null,
    });
    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.SetChartAttribute, function (action) {
    this.saveHistory();

    if (action.mapping == null) {
      delete this.chart.mappings[action.attribute];
    } else {
      this.chart.mappings[action.attribute] = action.mapping;
    }

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.SetChartSize, function (action) {
    this.saveHistory();

    this.chartState.attributes.width = action.width;
    this.chartState.attributes.height = action.height;
    this.chart.mappings.width = {
      type: MappingType.value,
      value: action.width,
    } as Specification.ValueMapping;
    this.chart.mappings.height = {
      type: MappingType.value,
      value: action.height,
    } as Specification.ValueMapping;

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.SetObjectProperty, function (action) {
    this.setProperty(action);
  });

  REG.add(Actions.DeleteObjectProperty, function (action) {
    if (action.property === "name") {
      return;
    }
    this.saveHistory();

    if (action.field == null) {
      delete action.object.properties[action.property];
    } else {
      const obj = action.object.properties[action.property] as any;
      delete obj[action.field as any];
    }

    if (action.noUpdateState) {
      this.emit(AppStore.EVENT_GRAPHICS);
    } else {
      this.solveConstraintsAndUpdateGraphics(action.noComputeLayout);
    }
  });

  REG.add(Actions.ExtendPlotSegment, function (action) {
    this.saveHistory();

    const plotSegment = action.plotSegment as Specification.PlotSegment;
    const plotSegmentState = this.chartState.elements[
      this.chart.elements.indexOf(plotSegment)
    ] as Specification.PlotSegmentState;

    let newClassID: string;
    switch (action.extension) {
      case "cartesian-x": {
        newClassID = "plot-segment.cartesian";
        break;
      }
      case "cartesian-y":
        {
          newClassID = plotSegment.classID;
        }
        break;
      case "polar":
        {
          newClassID = "plot-segment.polar";
        }
        break;
      case "curve":
        {
          newClassID = "plot-segment.curve";
        }
        break;
    }
    if (plotSegment.classID != newClassID) {
      const originalAttributes = plotSegment.mappings;
      plotSegment.classID = newClassID;
      plotSegment.mappings = {};

      if (originalAttributes.x1) {
        plotSegment.mappings.x1 = originalAttributes.x1;
      }
      if (originalAttributes.x2) {
        plotSegment.mappings.x2 = originalAttributes.x2;
      }
      if (originalAttributes.y1) {
        plotSegment.mappings.y1 = originalAttributes.y1;
      }
      if (originalAttributes.y2) {
        plotSegment.mappings.y2 = originalAttributes.y2;
      }

      plotSegment.properties = {
        name: replaceUndefinedByNull(plotSegment.properties.name),
        visible: replaceUndefinedByNull(plotSegment.properties.visible),
        sublayout: replaceUndefinedByNull(plotSegment.properties.sublayout),
        xData: replaceUndefinedByNull(plotSegment.properties.xData),
        yData: replaceUndefinedByNull(plotSegment.properties.yData),
        marginX1: replaceUndefinedByNull(plotSegment.properties.marginX1),
        marginY1: replaceUndefinedByNull(plotSegment.properties.marginY1),
        marginX2: replaceUndefinedByNull(plotSegment.properties.marginX2),
        marginY2: replaceUndefinedByNull(plotSegment.properties.marginY2),
      };

      if (newClassID == "plot-segment.polar") {
        plotSegment.properties.startAngle =
          Prototypes.PlotSegments.PolarPlotSegment.defaultProperties.startAngle;
        plotSegment.properties.endAngle =
          Prototypes.PlotSegments.PolarPlotSegment.defaultProperties.endAngle;
        plotSegment.properties.innerRatio =
          Prototypes.PlotSegments.PolarPlotSegment.defaultProperties.innerRatio;
        plotSegment.properties.outerRatio =
          Prototypes.PlotSegments.PolarPlotSegment.defaultProperties.outerRatio;
      }
      if (newClassID == "plot-segment.curve") {
        plotSegment.properties.curve =
          Prototypes.PlotSegments.CurvePlotSegment.defaultProperties.curve;
        plotSegment.properties.normalStart =
          Prototypes.PlotSegments.CurvePlotSegment.defaultProperties.normalStart;
        plotSegment.properties.normalEnd =
          Prototypes.PlotSegments.CurvePlotSegment.defaultProperties.normalEnd;
      }

      this.chartManager.initializeCache();
      const layoutClass = this.chartManager.getPlotSegmentClass(
        plotSegmentState
      );
      plotSegmentState.attributes = {};
      layoutClass.initializeState();
    } else {
      if (
        action.extension == "cartesian-x" ||
        action.extension == "polar" ||
        action.extension == "curve"
      ) {
        plotSegment.properties.xData = { type: "default", gapRatio: 0.1 };
      }
      if (action.extension == "cartesian-y") {
        plotSegment.properties.yData = { type: "default", gapRatio: 0.1 };
      }
    }
    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.ReorderGlyphMark, function (action) {
    this.saveHistory();

    this.chartManager.reorderGlyphElement(
      action.glyph,
      action.fromIndex,
      action.toIndex
    );

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.ToggleLegendForScale, function (action) {
    this.saveHistory();

    this.toggleLegendForScale(action.scale, action.mapping, action.plotSegment);

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.ReorderChartElement, function (action) {
    this.saveHistory();

    this.chartManager.reorderChartElement(action.fromIndex, action.toIndex);

    this.solveConstraintsAndUpdateGraphics();
  });

  REG.add(Actions.AddLinks, function (action) {
    this.saveHistory();

    action.links.properties.name = this.chartManager.findUnusedName("Link");
    // Always add links to the back
    this.chartManager.addChartElement(action.links, 0);
    const selection = new ChartElementSelection(action.links);
    this.currentSelection = selection;

    // Note: currently, links has no constraints to solve
    this.emit(AppStore.EVENT_GRAPHICS);
    this.emit(AppStore.EVENT_SELECTION);
  });

  REG.add(Actions.DeleteChartElement, function (action) {
    this.saveHistory();

    if (
      this.currentSelection instanceof ChartElementSelection &&
      this.currentSelection.chartElement == action.chartElement
    ) {
      this.currentSelection = null;
      this.emit(AppStore.EVENT_SELECTION);
    }
    this.chartManager.removeChartElement(action.chartElement);

    this.solveConstraintsAndUpdateGraphics();
  });
}