private renderMarkPlaceholders()

in src/app/views/canvas/editing_link.tsx [190:498]


  private renderMarkPlaceholders() {
    const manager = this.props.store.chartManager;

    // Get the two glyphs
    const props = this.props.link
      .properties as Prototypes.Links.LinksProperties;
    const lineMode: string = props.linkType;
    let glyphs: {
      glyph: Specification.Glyph;
      glyphState: Specification.GlyphState;
      coordinateSystem: Graphics.CoordinateSystem;
    }[] = [];
    switch (this.props.link.classID) {
      case "links.through":
        {
          const plotSegmentClass = manager.getClassById(
            props.linkThrough.plotSegment
          ) as Prototypes.PlotSegments.PlotSegmentClass;
          const coordinateSystem = plotSegmentClass.getCoordinateSystem();
          const facets = Prototypes.Links.facetRows(
            manager.dataflow.getTable(plotSegmentClass.object.table),
            plotSegmentClass.state.dataRowIndices,
            props.linkThrough.facetExpressions.map((x) =>
              manager.dataflow.cache.parse(x)
            )
          );
          const glyph = getById(
            manager.chart.glyphs,
            plotSegmentClass.object.glyph
          );
          const rowToMarkState = new Map<string, Specification.GlyphState>();
          for (
            let i = 0;
            i < plotSegmentClass.state.dataRowIndices.length;
            i++
          ) {
            rowToMarkState.set(
              plotSegmentClass.state.dataRowIndices[i].join(","),
              plotSegmentClass.state.glyphs[i]
            );
          }
          let firstNonEmptyFacet = 0;
          for (; firstNonEmptyFacet < facets.length; firstNonEmptyFacet++) {
            if (facets[firstNonEmptyFacet].length >= 2) {
              break;
            }
          }
          if (firstNonEmptyFacet < facets.length) {
            glyphs = [
              {
                glyph,
                glyphState: rowToMarkState.get(
                  facets[firstNonEmptyFacet][0].join(",")
                ),
                coordinateSystem,
              },
              {
                glyph,
                glyphState: rowToMarkState.get(
                  facets[firstNonEmptyFacet][1].join(",")
                ),
                coordinateSystem,
              },
            ];
          }
        }
        break;
      case "links.between":
        {
          const plotSegmentClasses = props.linkBetween.plotSegments.map(
            (x) =>
              manager.getClassById(
                x
              ) as Prototypes.PlotSegments.PlotSegmentClass
          );
          const glyphObjects = plotSegmentClasses.map((x) =>
            getById(manager.chart.glyphs, x.object.glyph)
          );
          glyphs = [
            {
              glyph: glyphObjects[0],
              glyphState: plotSegmentClasses[0].state.glyphs[0],
              coordinateSystem: plotSegmentClasses[0].getCoordinateSystem(),
            },
            {
              glyph: glyphObjects[1],
              glyphState: plotSegmentClasses[1].state.glyphs[0],
              coordinateSystem: plotSegmentClasses[1].getCoordinateSystem(),
            },
          ];
        }
        break;
      case "links.table":
        {
          const plotSegmentClasses = props.linkTable.plotSegments.map(
            (x) =>
              manager.getClassById(
                x
              ) as Prototypes.PlotSegments.PlotSegmentClass
          );
          const glyphObjects = plotSegmentClasses.map((x) =>
            getById(manager.chart.glyphs, x.object.glyph)
          );
          const linkTable = this.props.store.chartManager.dataflow.getTable(
            props.linkTable.table
          );
          const tables = plotSegmentClasses.map((plotSegmentClass) => {
            const table = this.props.store.chartManager.dataflow.getTable(
              plotSegmentClass.object.table
            );
            const id2RowGlyphIndex = new Map<string, [number[], number]>();
            for (
              let i = 0;
              i < plotSegmentClass.state.dataRowIndices.length;
              i++
            ) {
              const rowIndex = plotSegmentClass.state.dataRowIndices[i];
              const rowIDs = rowIndex.map((i) => table.getRow(i).id).join(",");
              id2RowGlyphIndex.set(rowIDs, [rowIndex, i]);
            }
            return {
              table,
              id2RowGlyphIndex,
            };
          });
          // Find the first links with nodes are exists in main table
          const rowItem: Specification.DataRow = linkTable.rows.find(
            (row) =>
              row.source_id &&
              tables[0].id2RowGlyphIndex.get(row.source_id.toString()) !=
                undefined &&
              row.target_id &&
              tables[1].id2RowGlyphIndex.get(row.target_id.toString()) !=
                undefined
          );
          if (rowItem) {
            const [, i0] = tables[0].id2RowGlyphIndex.get(
              rowItem.source_id?.toString()
            );
            const [, i1] = tables[1].id2RowGlyphIndex.get(
              rowItem.target_id?.toString()
            );
            glyphs = [
              {
                glyph: glyphObjects[0],
                glyphState: plotSegmentClasses[0].state.glyphs[i0],
                coordinateSystem: plotSegmentClasses[0].getCoordinateSystem(),
              },
              {
                glyph: glyphObjects[1],
                glyphState: plotSegmentClasses[1].state.glyphs[i1],
                coordinateSystem: plotSegmentClasses[1].getCoordinateSystem(),
              },
            ];
          } else {
            glyphs = [];
          }
        }
        break;
    }

    // Render mark anchor candidates
    const elements = glyphs.map(
      ({ glyph, glyphState, coordinateSystem }, glyphIndex) => {
        const anchorX = glyphState.marks[0].attributes.x as number;
        const anchorY = glyphState.marks[0].attributes.y as number;
        const offsetX = (glyphState.attributes.x as number) - anchorX;
        const offsetY = (glyphState.attributes.y as number) - anchorY;
        const marks = glyph.marks.map((element, elementIndex) => {
          if (glyph.marks.length > 1 && element.classID == "mark.anchor") {
            return null;
          }
          const markClass = manager.getMarkClass(
            glyphState.marks[elementIndex]
          );
          const mode: "begin" | "end" = glyphIndex == 0 ? "begin" : "end";
          let anchors = markClass.getLinkAnchors
            ? markClass.getLinkAnchors(mode)
            : [];
          anchors = anchors.filter((anchor) => {
            if (lineMode == "line") {
              return anchor.points.length == 1;
            }
            if (lineMode == "band") {
              return anchor.points.length == 2;
            }
          });
          return (
            <g key={element._id}>
              {anchors.map((anchor, index) => (
                <g
                  className="anchor"
                  key={`m${index}`}
                  ref={(g) => {
                    if (g != null) {
                      this.markPlaceholders.set(g, {
                        mode,
                        markID: element._id,
                        anchor,
                        offsetX,
                        offsetY,
                        coordinateSystem,
                      });
                    }
                  }}
                >
                  {this.renderAnchor(
                    coordinateSystem,
                    offsetX,
                    offsetY,
                    anchor
                  )}
                </g>
              ))}
            </g>
          );
        });
        return <g key={glyphIndex}>{marks}</g>;
      }
    );
    const currentAnchors = glyphs
      .map(({ glyph, glyphState, coordinateSystem }, glyphIndex) => {
        const anchorX = glyphState.marks[0].attributes.x as number;
        const anchorY = glyphState.marks[0].attributes.y as number;
        const offsetX = (glyphState.attributes.x as number) - anchorX;
        const offsetY = (glyphState.attributes.y as number) - anchorY;
        const anchor = glyphIndex == 0 ? props.anchor1 : props.anchor2;
        const element = anchor[0].x.element;
        if (!element) {
          return null;
        }
        const elementState =
          glyphState.marks[getIndexById(glyph.marks, element)];
        const anchorDescription: Prototypes.LinkAnchor.Description = {
          element,
          points: anchor.map((a) => {
            return {
              x: elementState.attributes[a.x.attribute] as number,
              xAttribute: a.x.attribute,
              y: elementState.attributes[a.y.attribute] as number,
              yAttribute: a.y.attribute,
              direction: a.direction,
            };
          }),
        };
        return {
          coordinateSystem,
          offsetX,
          offsetY,
          anchor: anchorDescription,
        };
      })
      .filter((anchor) => anchor != null);
    let currentLinkElement: JSX.Element = null;
    if (currentAnchors.length == 2) {
      const path = Graphics.makePath();
      const anchor1 = {
        coordinateSystem: currentAnchors[0].coordinateSystem,
        points: currentAnchors[0].anchor.points.map((p) => {
          return {
            x: p.x + currentAnchors[0].offsetX,
            y: p.y + currentAnchors[0].offsetY,
            direction: p.direction,
          };
        }),
        curveness: this.props.link.properties.curveness as number,
      };
      const anchor2 = {
        coordinateSystem: currentAnchors[1].coordinateSystem,
        points: currentAnchors[1].anchor.points.map((p) => {
          return {
            x: p.x + currentAnchors[1].offsetX,
            y: p.y + currentAnchors[1].offsetY,
            direction: p.direction,
          };
        }),
        curveness: this.props.link.properties.curveness as number,
      };
      Prototypes.Links.LinksClass.LinkPath(
        path,
        props.linkType,
        props.interpolationType,
        anchor1,
        anchor2
      );
      const transform = `translate(${this.props.zoom.centerX},${this.props.zoom.centerY}) scale(${this.props.zoom.scale})`;
      currentLinkElement = (
        <g transform={transform}>
          <path
            d={renderSVGPath(path.path.cmds)}
            className={`link-hint-${props.linkType}`}
          />
        </g>
      );
    }
    return (
      <g>
        {currentLinkElement}
        {elements}
        {currentAnchors.map(
          ({ coordinateSystem, offsetX, offsetY, anchor }, index) => (
            <g className="anchor active" key={index}>
              {this.renderAnchor(coordinateSystem, offsetX, offsetY, anchor)}
            </g>
          )
        )}
      </g>
    );
  }