<script>import { LayerCake, Svg } from'layercake';
importRadarfrom'./_components/Radar.svelte';
importAxisRadialfrom'./_components/AxisRadial.svelte';
// This example loads csv data as json and converts numeric columns to numbers using @rollup/plugin-dsv. See vite.config.js for detailsimport data from'./_data/radarScores.csv';
const seriesKey = 'name';
const xKey = ['fastball', 'change', 'slider', 'cutter', 'curve'];
const seriesNames = Object.keys(data[0]).filter(d => d !== seriesKey);
</script><divclass="chart-container"><LayerCakepadding={{ top: 30, right: 0, bottom: 7, left: 0 }}x={xKey}xDomain={[0, 10]}xRange={({ height }) => [0, height / 2]}{data}
><Svg><AxisRadial /><Radar /></Svg></LayerCake></div><style>/*
The wrapper div needs to have an explicit width and height in CSS.
It can also be a flexbox child or CSS grid element.
The point being it needs dimensions since the <LayerCake> element will
expand to fill it.
*/.chart-container {
width: 100%;
height: 250px;
}
</style>
<!--
@component
Generates an SVG radar chart.
--><script>import { getContext } from'svelte';
import { line, curveCardinalClosed } from'd3-shape';
const { data, width, height, xGet, config } = getContext('LayerCake');
/**
* @typedef {Object} Props
* @property {string} [fill='#f0c'] - The radar's fill color. This is technically optional because it comes with a default value but you'll likely want to replace it with your own color.
* @property {string} [stroke='#f0c'] - The radar's stroke color. This is technically optional because it comes with a default value but you'll likely want to replace it with your own color.
* @property {number} [strokeWidth=2] - The radar's stroke color.
* @property {number} [fillOpacity=0.5] - The radar's fill opacity.
* @property {number} [r=4.5] - Each circle's radius.
* @property {string} [circleFill='#f0c'] - Each circle's fill color. This is technically optional because it comes with a default value but you'll likely want to replace it with your own color.
* @property {string} [circleStroke='#fff'] - Each circle's stroke color. This is technically optional because it comes with a default value but you'll likely want to replace it with your own color.
* @property {number} [circleStrokeWidth=1] - Each circle's stroke width.
*//** @type {Props} */let {
fill = '#f0c',
stroke = '#f0c',
strokeWidth = 2,
fillOpacity = 0.5,
r = 4.5,
circleFill = '#f0c',
circleStroke = '#fff',
circleStrokeWidth = 1
} = $props();
let angleSlice = $derived((Math.PI * 2) / $config.x.length);
let path = $derived(
line()
.curve(curveCardinalClosed)
// @ts-expect-error
.x((d, i) => d * Math.cos(angleSlice * i - Math.PI / 2))
// @ts-expect-error
.y((d, i) => d * Math.sin(angleSlice * i - Math.PI / 2))
);
/* The non-D3 line generator way. */// let path = $derived(// values =>// 'M' +// values// .map(d => {// return $rGet(d).map((val, i) => {// return [// val * Math.cos(angleSlice * i - Math.PI / 2),// val * Math.sin(angleSlice * i - Math.PI / 2)// ].join(',');// });// })// .join('L') +// 'z'// );</script><gtransform="translate({$width / 2}, {$height / 2})">{#each $data as row}{@const xVals = $xGet(row)}<!-- Draw a line connecting all the dots --><pathclass="path-line"d={path(xVals)}{stroke}stroke-width={strokeWidth}{fill}fill-opacity={fillOpacity}
></path><!-- Plot each dots -->{#each xVals as circleR, i}{@const thisAngleSlice = angleSlice * i - Math.PI / 2}<circlecx={circleR * Math.cos(thisAngleSlice)}cy={circleR * Math.sin(thisAngleSlice)}{r}fill={circleFill}stroke={circleStroke}stroke-width={circleStrokeWidth}
></circle>{/each}{/each}</g><style>.path-line {
stroke-linejoin: round;
stroke-linecap: round;
}
</style>
<!--
@component
Generates an SVG radial scale, useful for radar charts.
--><script>import { getContext } from'svelte';
const { width, height, xScale, extents, config } = getContext('LayerCake');
/**
* @typedef {Object} Props
* @property {number} [lineLengthFactor=1.1] - How far to extend the lines from the circle's center. A value of `1` puts them at the circle's circumference.
* @property {number} [labelPlacementFactor=1.25] - How far to place the labels from the circle's center. A value of `1` puts them at the circle's circumference.
*//** @type {Props} */let { lineLengthFactor = 1.1, labelPlacementFactor = 1.25 } = $props();
let max = $derived($xScale(Math.max(...$extents.x)));
let lineLength = $derived(max * lineLengthFactor);
let labelPlacement = $derived(max * labelPlacementFactor);
let angleSlice = $derived((Math.PI * 2) / $config.x.length);
/** @param {number} total
* @param {number} i */functionanchor(total, i) {
if (i === 0 || i === total / 2) {
return'middle';
} elseif (i < total / 2) {
return'start';
}
return'end';
}
</script><gtransform="translate({$width / 2}, {$height / 2})"><circlecx="0"cy="0"r={max}stroke="#ccc"stroke-width="1"fill="#CDCDCD"fill-opacity="0.1"
></circle><circlecx="0"cy="0"r={max / 2}stroke="#ccc"stroke-width="1"fill="none"></circle>{#each $config.xas label, i}{@const thisAngleSlice = angleSlice * i - Math.PI / 2}<linex1="0"y1="0"x2={lineLength * Math.cos(thisAngleSlice)}y2={lineLength * Math.sin(thisAngleSlice)}stroke="#ccc"stroke-width="1"fill="none"
></line><texttext-anchor={anchor($config.x.length, i)}dy="0.35em"font-size="12px"transform="translate({labelPlacement * Math.cos(thisAngleSlice)}, {labelPlacement *
Math.sin(thisAngleSlice)})">{label}</text
>
{/each}</g>