Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/open-ui-kit/src/charts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export * from "./donut-chart/donut-chart";
export * from "./gauge-chart/gauge-chart";
export * from "./horizontal-bar-chart/horizontal-bar-chart";
export * from "./line-chart/line-chart";
export * from "./spider-chart";

export * from "./common/types";
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2025 Cisco Systems, Inc. and its affiliates
*
* SPDX-License-Identifier: Apache-2.0
*/

const CustomElement = ({
points,
color,
}: {
points: { x: number; y: number }[];
color: string;
}) => {
const path =
points
.map((p) => [p.x, p.y])
.map((c, i) => (i ? `${c[0]} ${c[1]}` : `M${c[0]} ${c[1]}`))
.join(" ") + "Z";

return (
<svg>
<clipPath id="clip">
<path d={path} />
</clipPath>
<foreignObject width="100%" height="100%" clipPath="url(#clip)">
<div
style={{
width: "100%",
height: "100%",
background: color,
}}
/>
</foreignObject>
</svg>
);
};

export default CustomElement;
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2025 Cisco Systems, Inc. and its affiliates
*
* SPDX-License-Identifier: Apache-2.0
*/

import { useTheme } from "@mui/material";

const RADIAN = Math.PI / 180;

export default function CustomLines({
polarAngles,
scale,
...props
}: {
polarAngles: number[];
innerRadius: number;
outerRadius: number;
cx: string;
cy: string;
width: number;
height: number;
scale: number;
}) {
const theme = useTheme();
const polarToCartesian = (
cx: number,
cy: number,
radius: number,
angle: number,
) => ({
x: cx + Math.cos(-RADIAN * angle) * radius,
y: cy + Math.sin(-RADIAN * angle) * radius,
});

function convertPercentageToNumeric(
cx: string,
cy: string,
width: number,
height: number,
): [number, number] {
const x = (parseFloat(cx.replace("%", "")) / 100) * width;
const y = (parseFloat(cy.replace("%", "")) / 100) * height;
return [x, y];
}

const getPolarLine = (angle: number) => {
const [cx, cy] = convertPercentageToNumeric(
props.cx,
props.cy,
props.width,
props.height,
);
const start = polarToCartesian(cx, cy, props.innerRadius, angle);
const end = polarToCartesian(cx, cy, props.outerRadius * scale, angle);
return (
<line
key={`angle-${angle}`}
stroke={theme.palette.vars.baseBorderDefault}
opacity={theme.palette.mode === "dark" ? 0.5 : 1}
x1={start.x}
y1={start.y}
x2={end.x}
y2={end.y}
></line>
);
};
return <>{polarAngles.map((angle) => getPolarLine(angle))}</>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright 2025 Cisco Systems, Inc. and its affiliates
*
* SPDX-License-Identifier: Apache-2.0
*/

import { useTheme } from "@mui/material";

const RADIAN = Math.PI / 180;

function CustomGrid({
polarRadius,
polarAngles,
scale,
...props
}: {
polarRadius: number[];
polarAngles: number[];
cx: string;
cy: string;
width: number;
height: number;
scale: number;
}) {
const theme = useTheme();

const polarToCartesian = (
cx: number,
cy: number,
radius: number,
angle: number,
) => ({
x: cx + Math.cos(-RADIAN * angle) * radius * scale,
y: cy + Math.sin(-RADIAN * angle) * radius * scale,
});

function convertPercentageToNumeric(
cx: string,
cy: string,
width: number,
height: number,
): [number, number] {
const x = (parseFloat(cx.replace("%", "")) / 100) * width;
const y = (parseFloat(cy.replace("%", "")) / 100) * height;
return [x, y];
}

const getPolygonPath = (radius: number) => {
let path = "";

const [cx, cy] = convertPercentageToNumeric(
props.cx,
props.cy,
props.width,
props.height,
);

polarAngles.forEach((angle: number, i: number) => {
const point = polarToCartesian(cx, cy, radius, angle);
if (i) {
path += `L ${point.x},${point.y}`;
} else {
path += `M ${point.x},${point.y}`;
}
});
path += "Z";

return path;
};
const getConcentricPolygon = (entry: number, index: number) => {
const total = polarRadius.length;
const t = total > 1 ? index / (total - 1) : 1;
// Inner ring more visible, outer ring lighter
const start = theme.palette.mode === "dark" ? 0.32 : 0.6;
const end = theme.palette.mode === "dark" ? 0.12 : 0.3;
const opacity = start + (end - start) * t;

return (
<path
fill={theme.palette.vars.baseBorderDefault}
opacity={opacity}
className="recharts-polar-grid-concentric-polygon"
key={`path-${index}`}
d={getPolygonPath(entry)}
/>
);
};
return (
<>{polarRadius.map((entry, index) => getConcentricPolygon(entry, index))}</>
);
}

export default CustomGrid;
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2025 Cisco Systems, Inc. and its affiliates
*
* SPDX-License-Identifier: Apache-2.0
*/

import { useTheme } from "@mui/material";
import { Offset } from "../types/spider-chart.types";

const RADIAN = Math.PI / 180;

const CustomLabels = (props: {
cx: string;
cy: string;
width: number;
height: number;
labelOffsets: Offset[];
outerRadius: number;
band: number;
tooltipTicks: { value: string; coordinate: number }[];
data: { subject: string }[];
}) => {
const theme = useTheme();

const polarToCartesian = (
cx: number,
cy: number,
radius: number,
angle: number,
) => ({
x: cx + Math.cos(-RADIAN * angle) * radius,
y: cy + Math.sin(-RADIAN * angle) * radius,
});

function convertPercentageToNumeric(
cx: string,
cy: string,
width: number,
height: number,
): [number, number] {
const x = (parseFloat(cx.replace("%", "")) / 100) * width;
const y = (parseFloat(cy.replace("%", "")) / 100) * height;
return [x, y];
}

const getTickLineCoord = (coordinate: number, index: number) => {
let [cx, cy] = convertPercentageToNumeric(
props.cx,
props.cy,
props.width,
props.height,
);
if (props.labelOffsets && props.labelOffsets.length > index) {
cx += props.labelOffsets[index].cx;
cy += props.labelOffsets[index].cy;
}
const tickLineSize = 8;
const p2 = polarToCartesian(
cx,
cy,
props.outerRadius + props.band * tickLineSize,
coordinate,
);

return { x: p2.x, y: p2.y };
};

if (!props.tooltipTicks) return <></>;
return (
<>
{props.tooltipTicks.map((tick, index) => {
const { x, y } = getTickLineCoord(tick.coordinate, index);
return (
<text
key={`tick=${index}`}
x={x}
y={y}
fill={theme.palette.vars.baseTextDefault}
style={{ ...theme.typography.caption }}
>
<tspan>{props.data[index].subject}</tspan>
</text>
);
})}
</>
);
};

export default CustomLabels;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2025 Cisco Systems, Inc. and its affiliates
*
* SPDX-License-Identifier: Apache-2.0
*/

import { useTheme } from "@mui/material";

type TickProps = {
payload?: { value: string };
x?: number;
y?: number;
};

export default function CustomRadarTick({ x = 0, y = 0, payload }: TickProps) {
const theme = useTheme();
return (
<g>
<text dx={5} y={y - 5} fill={theme.palette.vars.baseTextMedium}>
<tspan x={x} dy="0em" fontSize={"0.625rem"} fontWeight={"600"}>
{payload?.value}
</tspan>
</text>
</g>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2025 Cisco Systems, Inc. and its affiliates
*
* SPDX-License-Identifier: Apache-2.0
*/

import React from "react";

import { Typography } from "@mui/material";
import { ExtendedDataPoint } from "../types/spider-chart.types";
import { StyledTooltip } from "../styles/spider-chart.styles";

export type CustomTooltipProps = {
active?: boolean;
payload?: { payload: ExtendedDataPoint }[];
tooltipContent?: (dataPoint: ExtendedDataPoint) => React.ReactNode;
};

const CustomTooltip = ({ active, payload }: CustomTooltipProps) => {
if (!active || !payload || !payload[0].payload.subject) {
return null;
}
const data = payload[0].payload;

return (
<StyledTooltip>
<Typography variant="caption">
{data.variableA && (
<div>
{data.variableA} {data.subject}
</div>
)}
</Typography>
</StyledTooltip>
);
};

export default CustomTooltip;
Loading