export default function()

in src/app/stores/action_handlers/mark.ts [19:271]


export default function (REG: ActionHandlerRegistry<AppStore, Actions.Action>) {
  // Internal registry of mark-level action handlers
  const MR = new ActionHandlerRegistry<AppStore, Actions.MarkAction>();

  MR.add(Actions.UpdateMarkAttribute, function (action) {
    for (const key in action.updates) {
      if (!Object.prototype.hasOwnProperty.call(action.updates, key)) {
        continue;
      }
      delete action.mark.mappings[key];

      action.glyph.constraints = action.glyph.constraints.filter((c) => {
        if (c.type == "snap") {
          if (
            c.attributes.element == action.mark._id &&
            c.attributes.attribute == key
          ) {
            return false;
          }
        }
        return true;
      });
    }

    this.forAllGlyph(action.glyph, (glyphState) => {
      for (const [mark, markState] of zipArray(
        action.glyph.marks,
        glyphState.marks
      )) {
        if (mark == action.mark) {
          for (const key in action.updates) {
            if (!Object.prototype.hasOwnProperty.call(action.updates, key)) {
              continue;
            }
            markState.attributes[key] = action.updates[key];
            this.addPresolveValue(
              Solver.ConstraintStrength.WEAK,
              markState.attributes,
              key,
              action.updates[key] as number
            );
          }
        }
      }
    });
  });

  MR.add(Actions.SetObjectProperty, function (this, action) {
    // check name property. Names of objects are unique
    if (
      action.property === "name" &&
      this.chartManager.isNameUsed(action.value as string)
    ) {
      return;
    }
    if (action.field == null) {
      action.object.properties[action.property] = action.value;
    } else {
      const obj = action.object.properties[action.property];
      action.object.properties[action.property] = setField(
        obj,
        action.field,
        action.value
      );
    }
  });

  MR.add(Actions.SetMarkAttribute, function (this, action) {
    if (action.mapping == null) {
      delete action.mark.mappings[action.attribute];
    } else {
      action.mark.mappings[action.attribute] = action.mapping;
      action.glyph.constraints = action.glyph.constraints.filter((c) => {
        if (c.type == "snap") {
          if (
            c.attributes.element == action.mark._id &&
            c.attributes.attribute == action.attribute
          ) {
            return false;
          }
        }
        return true;
      });
    }
  });

  MR.add(Actions.UnmapMarkAttribute, function (this, action) {
    delete action.mark.mappings[action.attribute];
  });

  MR.add(Actions.SnapMarks, function (action) {
    const idx1 = action.glyph.marks.indexOf(action.mark);
    if (idx1 < 0) {
      return;
    }
    // let elementState = this.markState.elements[idx1];
    const idx2 = action.glyph.marks.indexOf(action.targetMark);
    if (idx2 < 0) {
      return;
    }
    // let targetElementState = this.markState.elements[idx2];
    // elementState.attributes[action.attribute] = targetElementState.attributes[action.targetAttribute];
    // Remove any existing attribute mapping
    delete action.mark.mappings[action.attribute];
    // Remove any existing snapping
    action.glyph.constraints = action.glyph.constraints.filter((c) => {
      if (c.type == "snap") {
        if (
          c.attributes.element == action.mark._id &&
          c.attributes.attribute == action.attribute
        ) {
          return false;
        }
      }
      return true;
    });
    action.glyph.constraints.push({
      type: "snap",
      attributes: {
        element: action.mark._id,
        attribute: action.attribute,
        targetElement: action.targetMark._id,
        targetAttribute: action.targetAttribute,
        gap: 0,
      },
    });

    // Force the states to be equal
    this.forAllGlyph(action.glyph, (glyphState) => {
      const elementState = glyphState.marks[idx1];
      const targetElementState = glyphState.marks[idx2];
      elementState.attributes[action.attribute] =
        targetElementState.attributes[action.targetAttribute];
      this.addPresolveValue(
        Solver.ConstraintStrength.STRONG,
        elementState.attributes,
        action.attribute,
        targetElementState.attributes[action.targetAttribute] as number
      );
    });
  });

  MR.add(Actions.MarkActionGroup, function (action) {
    for (const item of action.actions) {
      // Recursively handle group actions
      MR.handleAction(this, item);
    }
  });

  // The entry point for mark actions
  REG.add(Actions.MarkAction, function (this, mainAction) {
    this.saveHistory();

    MR.handleAction(this, mainAction);

    // Solve constraints only after all actions are processed
    this.solveConstraintsAndUpdateGraphics();
  });

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

    const inferred =
      (action.hints && action.hints.scaleID) ||
      this.scaleInference(
        {
          glyph: action.glyph,
          chart: {
            table: action.expressionTable,
          },
        },
        {
          expression: action.expression,
          valueType: action.valueType,
          valueKind: action.valueMetadata.kind,
          outputType: action.attributeType,
          hints: action.hints,
          markAttribute: action.attribute,
        }
      );
    if (inferred != null) {
      if (
        action.valueType == Specification.DataType.Image &&
        action.valueType === Specification.DataType.Image
      ) {
        action.mark.mappings[action.attribute] = {
          type: MappingType.expressionScale,
          table: action.expressionTable ?? action.glyph.table,
          expression: `first(${ImageKeyColumn})`,
          valueExpression: action.expression,
          valueType: action.valueType,
          scale: inferred,
          attribute: action.attribute,
          valueIndex:
            action.hints && action.hints.allowSelectValue != undefined
              ? 0
              : null,
        } as Specification.ScaleValueExpressionMapping;
      } else {
        action.mark.mappings[action.attribute] = {
          type: MappingType.scale,
          table: action.expressionTable ?? action.glyph.table,
          expression: action.expression,
          valueType: action.valueType,
          scale: inferred,
          attribute: action.attribute,
          valueIndex:
            action.hints && action.hints.allowSelectValue != undefined
              ? 0
              : null,
        } as Specification.ScaleMapping;
      }
      if (
        !this.chart.scaleMappings.find(
          (scaleMapping) => scaleMapping.scale === inferred
        )
      ) {
        this.chart.scaleMappings.push({
          ...action.mark.mappings[action.attribute],
          attribute: action.attribute,
        } as Specification.ScaleMapping);
      }
    } else {
      if (
        (action.valueType == Specification.DataType.Boolean ||
          action.valueType == Specification.DataType.String ||
          action.valueType == Specification.DataType.Number) &&
        action.attributeType == Specification.AttributeType.Text
      ) {
        let format: string;
        // don't apply format to numbers if data kind is categorical to draw as are
        if (action.valueMetadata.kind === DataKind.Categorical) {
          format = undefined;
        } else {
          // If the valueType is a number and kind is not categorical, use a format
          format =
            action.valueType == Specification.DataType.Number
              ? ".1f"
              : undefined;
        }
        action.mark.mappings[action.attribute] = {
          type: MappingType.text,
          table: action.expressionTable ?? action.glyph.table,
          textExpression: new Expression.TextExpression([
            { expression: Expression.parse(action.expression), format },
          ]).toString(),
        } as Specification.TextMapping;
      }
    }

    this.solveConstraintsAndUpdateGraphics();
  });
}