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>
);
}
}
}