export function renderGraphicalElementSVG()

in src/app/renderer/index.tsx [276:655]


export function renderGraphicalElementSVG(
  element: Graphics.Element,
  options?: RenderGraphicalElementSVGOptions
): JSX.Element {
  if (!element) {
    return null;
  }

  if (!options) {
    options = {};
  }

  const style = options.noStyle
    ? null
    : renderStyle(options.styleOverride || element.style);

  // OnClick event handler
  const mouseEvents: {
    onClick?: (e: React.MouseEvent<Element>) => void;
    onMouseEnter?: (e: React.MouseEvent<Element>) => void;
    onMouseLeave?: (e: React.MouseEvent<Element>) => void;
    onContextMenu?: (e: React.MouseEvent<Element>) => void;
    onMouseDown?: (e: React.MouseEvent<Element>) => void;
    onMouseUp?: (e: React.MouseEvent<Element>) => void;
    onWheel?: (e: React.MouseEvent<Element>) => void;
    onMouseMove?: (e: React.MouseEvent<Element>) => void;
  } = {};
  if (element.selectable) {
    style.cursor = "pointer";
    style.pointerEvents = "all";
    if (options.onClick) {
      mouseEvents.onClick = (e: React.MouseEvent<Element>) => {
        e.stopPropagation();
        if (
          element.selectable.enableSelection ||
          element.selectable.enableSelection === undefined
        ) {
          options.onClick(element.selectable, e.nativeEvent);
        }
      };
    }
    if (options.onMouseEnter) {
      mouseEvents.onMouseEnter = (e: React.MouseEvent<Element>) => {
        if (
          element.selectable.enableTooltips ||
          element.selectable.enableTooltips === undefined
        ) {
          options.onMouseEnter(element.selectable, e.nativeEvent);
        }
      };
    }
    if (options.onMouseLeave) {
      mouseEvents.onMouseLeave = (e: React.MouseEvent<Element>) => {
        if (
          element.selectable.enableTooltips ||
          element.selectable.enableTooltips === undefined
        ) {
          options.onMouseLeave(element.selectable, e.nativeEvent);
        }
      };
    }
    if (options.onContextMenu) {
      mouseEvents.onContextMenu = (e: React.MouseEvent<Element>) => {
        e.stopPropagation();
        if (
          element.selectable.enableContextMenu ||
          element.selectable.enableContextMenu === undefined
        ) {
          options.onContextMenu(element.selectable, e.nativeEvent);
        }
      };
    }
  }

  if (element.interactable) {
    if (element.interactable.onClick) {
      mouseEvents.onClick = element.interactable.onClick;
    }
    if (element.interactable.onMousedown) {
      mouseEvents.onMouseDown = element.interactable.onMousedown;
    }
    if (element.interactable.onMouseup) {
      mouseEvents.onMouseUp = element.interactable.onMouseup;
    }
    if (element.interactable.onMousewheel) {
      mouseEvents.onWheel = element.interactable.onMousewheel;
    }
    if (element.interactable.onMousemove) {
      mouseEvents.onMouseMove = element.interactable.onMousemove;
    }
  }

  switch (element.type) {
    case "rect": {
      const rect = element as Graphics.Rect;
      return (
        <rect
          key={options.key}
          {...mouseEvents}
          className={options.className || null}
          style={style}
          x={Math.min(rect.x1, rect.x2)}
          y={-Math.max(rect.y1, rect.y2)}
          width={Math.abs(rect.x1 - rect.x2)}
          height={Math.abs(rect.y1 - rect.y2)}
          rx={rect.rx}
          ry={rect.ry}
          transform={`rotate(${rect.rotation ?? 0})`}
        />
      );
    }
    case "circle": {
      const circle = element as Graphics.Circle;
      return (
        <circle
          key={options.key}
          {...mouseEvents}
          className={options.className || null}
          style={style}
          cx={circle.cx}
          cy={-circle.cy}
          r={circle.r}
        />
      );
    }
    case "ellipse": {
      const ellipse = element as Graphics.Ellipse;
      return (
        <ellipse
          key={options.key}
          {...mouseEvents}
          className={options.className || null}
          style={style}
          cx={(ellipse.x1 + ellipse.x2) / 2}
          cy={-(ellipse.y1 + ellipse.y2) / 2}
          rx={Math.abs(ellipse.x1 - ellipse.x2) / 2}
          ry={Math.abs(ellipse.y1 - ellipse.y2) / 2}
        />
      );
    }
    case "line": {
      const line = element as Graphics.Line;
      return (
        <line
          key={options.key}
          {...mouseEvents}
          className={options.className || null}
          style={style}
          x1={line.x1}
          y1={-line.y1}
          x2={line.x2}
          y2={-line.y2}
        />
      );
    }
    case "polygon": {
      const polygon = element as Graphics.Polygon;
      return (
        <polygon
          key={options.key}
          {...mouseEvents}
          className={options.className || null}
          style={style}
          points={polygon.points
            .map((p) => `${toSVGNumber(p.x)},${toSVGNumber(-p.y)}`)
            .join(" ")}
        />
      );
    }
    case "path": {
      const path = element as Graphics.Path;
      const d = renderSVGPath(path.cmds);
      return (
        <path
          key={options.key}
          {...mouseEvents}
          className={options.className || null}
          style={style}
          d={d}
          transform={path.transform}
        />
      );
    }
    case "text-on-path": {
      const text = element as Graphics.TextOnPath;
      style.fontFamily = text.fontFamily;
      style.fontSize = text.fontSize + "px";
      return (
        <TextOnPath
          text={text.text}
          style={style}
          cmds={text.pathCmds}
          align={text.align}
        />
      );
    }
    case "text": {
      const text = element as Graphics.Text;
      style.fontFamily = text.fontFamily;
      style.fontSize = text.fontSize + "px";
      const filter = text.style.backgroundColor
        ? `url(#${text.style.backgroundColorId})`
        : null;
      if (style.stroke != "none") {
        const style2 = shallowClone(style);
        style2.fill = style.stroke;
        const e1 = (
          <text
            {...mouseEvents}
            className={options.className || null}
            style={style2}
            x={text.cx}
            y={-text.cy}
            filter={filter}
          >
            {text.text}
          </text>
        );
        style.stroke = "none";
        const e2 = (
          <text
            {...mouseEvents}
            className={options.className || null}
            style={style}
            x={text.cx}
            y={-text.cy}
            filter={filter}
          >
            {text.text}
          </text>
        );
        return (
          <g key={options.key}>
            {e1}
            {e2}
          </g>
        );
      } else {
        return (
          <text
            key={options.key}
            {...mouseEvents}
            className={options.className || null}
            style={style}
            x={text.cx}
            y={-text.cy}
            filter={filter}
          >
            {text.text}
          </text>
        );
      }
    }
    case "image": {
      const image = element as Graphics.Image;
      let preserveAspectRatio = null;
      switch (image.mode) {
        case "letterbox":
          preserveAspectRatio = "meet";
          break;
        case "stretch":
          preserveAspectRatio = "none";
          break;
      }
      return (
        <image
          key={options.key}
          {...mouseEvents}
          className={options.className || null}
          style={style}
          preserveAspectRatio={preserveAspectRatio}
          xlinkHref={
            options.externalResourceResolver
              ? options.externalResourceResolver(image.src)
              : image.src
          }
          x={image.x}
          y={-image.y - image.height}
          width={image.width}
          height={image.height}
        />
      );
    }
    case "chart-container": {
      const component = element as Graphics.ChartContainerElement;
      const subSelection = options.selection
        ? {
            isSelected: (table: string, rowIndices: number[]) => {
              // Get parent row indices from component row indices
              const parentRowIndices = rowIndices.map(
                (x) => component.selectable.rowIndices[x]
              );
              // Query the selection with parent row indices
              return options.selection.isSelected(
                component.selectable.plotSegment.table,
                parentRowIndices
              );
            },
          }
        : null;

      const convertEventHandler = (
        handler: GraphicalElementEventHandler
      ): GlyphEventHandler => {
        if (!handler) {
          return null;
        }
        return (s, parameters) => {
          if (s == null) {
            // Clicked inside the ChartComponent but not on a glyph,
            // in this case we select the whole thing
            handler(component.selectable, parameters);
          } else {
            // Clicked on a glyph of ChartComponent (or a sub-component)
            // in this case we translate the component's rowIndices its parent's
            handler(
              {
                plotSegment: component.selectable.plotSegment,
                glyphIndex: component.selectable.glyphIndex,
                rowIndices: s.rowIndices.map(
                  (i) => component.selectable.rowIndices[i]
                ),
              },
              parameters
            );
          }
        };
      };

      return (
        <ChartComponent
          key={options.key}
          chart={component.chart}
          dataset={component.dataset}
          width={component.width}
          height={component.height}
          rootElement="g"
          sync={options.chartComponentSync}
          selection={subSelection}
          onGlyphClick={convertEventHandler(options.onClick)}
          onGlyphMouseEnter={convertEventHandler(options.onMouseEnter)}
          onGlyphMouseLeave={convertEventHandler(options.onMouseLeave)}
          rendererOptions={{
            chartComponentSync: options.chartComponentSync,
            externalResourceResolver: options.externalResourceResolver,
          }}
        />
      );
    }
    case "group": {
      const group = element as Graphics.Group;
      return (
        <g
          transform={renderTransform(group.transform)}
          key={group.key || options.key}
          style={{
            opacity:
              group.style && group.style.opacity != null
                ? group.style.opacity
                : 1,
          }}
          {...mouseEvents}
        >
          {group.elements.map((x, index) => {
            return renderGraphicalElementSVG(x, {
              key: `m${index}`,
              chartComponentSync: options.chartComponentSync,
              externalResourceResolver: options.externalResourceResolver,
              onClick: options.onClick,
              onMouseEnter: options.onMouseEnter,
              onMouseLeave: options.onMouseLeave,
              onContextMenu: options.onContextMenu,
              selection: options.selection,
            });
          })}
        </g>
      );
    }
  }
}