import { RefProps, bindGlobalStyle, getGlobalStylesId } from 'lupine.web';
import { chartCommonCss, getChartColor, polarToCartesian, BasicChartProps } from './chart-utils';
import { Tooltip } from '../tooltip';
export type RadarChartProps = BasicChartProps & {
valueFormatter?: (value: number) => string;
};
export const RadarChart = (props: RadarChartProps) => {
const globalCssId = getGlobalStylesId(chartCommonCss);
bindGlobalStyle(globalCssId, chartCommonCss);
const showLegend = props.showLegend !== false;
const series = props.data.series;
const labels = props.data.labels;
if (!series || series.length === 0 || labels.length === 0) {
return
No data
;
}
// Find max value to determine scale
let maxVal = 0;
series.forEach((s) => {
s.data.forEach((val) => {
if (val > maxVal) maxVal = val;
});
});
const tickCount = 4;
const order = Math.floor(Math.log10(maxVal || 1));
const magnitude = Math.pow(10, order);
const niceStep = Math.ceil(maxVal / magnitude / tickCount) * magnitude;
const niceMax = niceStep * tickCount;
// Viewbox and layout
const viewBoxSize = 400;
const centerX = viewBoxSize / 2;
const centerY = viewBoxSize / 2;
const maxRadius = (viewBoxSize / 2) * 0.7; // Leave space for labels
const angleStep = 360 / labels.length;
const formatValue = props.valueFormatter || ((val) => val.toString());
// Render Grid (Polygons)
const renderGrid = () => {
const gridElements: any[] = [];
// Grid webs
for (let i = 1; i <= tickCount; i++) {
const radius = (i / tickCount) * maxRadius;
const points = Array.from({ length: labels.length }, (_, j) => {
const angle = j * angleStep;
const pos = polarToCartesian(centerX, centerY, radius, angle);
return `${pos.x},${pos.y}`;
}).join(' ');
gridElements.push(
);
}
// Grid axes
for (let j = 0; j < labels.length; j++) {
const angle = j * angleStep;
const endPos = polarToCartesian(centerX, centerY, maxRadius, angle);
gridElements.push(
);
// Label
const labelPos = polarToCartesian(centerX, centerY, maxRadius + 20, angle);
gridElements.push(
{labels[j]}
);
}
return gridElements;
};
// Render Data
const renderData = () => {
const dataElements: any[] = [];
series.forEach((s, sIndex) => {
const color = s.color || getChartColor(sIndex);
const coordinates = s.data.map((val, j) => {
const radius = (val / niceMax) * maxRadius;
const angle = j * angleStep;
return { ...polarToCartesian(centerX, centerY, radius, angle), val, label: labels[j] };
});
const pointsStr = coordinates.map((c) => `${c.x},${c.y}`).join(' ');
const seriesGroupElements: any[] = [];
// Fill area
seriesGroupElements.push(
);
// Points and Tooltips
coordinates.forEach((c) => {
const handleMouseEnter = (e: any) => {
Tooltip.show(
e,
{c.label}
{s.name}: {formatValue(c.val)}
,
{ position: 'auto' }
);
e.target.setAttribute('r', '6');
};
const handleMouseLeave = (e: any) => {
Tooltip.hide();
e.target.setAttribute('r', '4');
};
seriesGroupElements.push(
);
});
const handleGroupMouseEnter = (e: any) => {
const polygon = e.currentTarget.querySelector('.radar-polygon');
if (polygon) polygon.setAttribute('fill-opacity', '0.6');
};
const handleGroupMouseLeave = (e: any) => {
const polygon = e.currentTarget.querySelector('.radar-polygon');
if (polygon) polygon.setAttribute('fill-opacity', '0.15');
};
dataElements.push(
{seriesGroupElements}
);
});
return dataElements;
};
const ref: RefProps = {
referToCssId: globalCssId,
};
const ratio = props.aspectRatio ?? 16 / 9;
const paddingTop = `${(1 / ratio) * 100}%`;
const styleStr = `width: ${props.width || '100%'};`;
return (
{props.title &&
{props.title}
}
{showLegend && (
{series.map((s, i) => (
))}
)}
);
};