Skip to content

Commit

Permalink
release spider
Browse files Browse the repository at this point in the history
  • Loading branch information
Holtz Yan authored and Holtz Yan committed Jul 28, 2023
1 parent 035cbec commit 9362cfd
Show file tree
Hide file tree
Showing 22 changed files with 846 additions and 266 deletions.
266 changes: 182 additions & 84 deletions pages/radar-chart.tsx

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion util/sectionDescriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ export const chartTypesInfo: ChartTypesInfo[] = [
d3URL: 'https://www.d3-graph-gallery.comt/spider',
reactURL: 'https://react-graph-gallery.com/radar-chart',
label: 'Spider / Radar',
isAvailable: false,
isAvailable: true,
},
{
id: 'treemap',
Expand Down
77 changes: 0 additions & 77 deletions viz/RadarBasic/AxisVertical.tsx

This file was deleted.

122 changes: 36 additions & 86 deletions viz/RadarBasic/Radar.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
import * as d3 from "d3";
import { AxisVertical } from "./AxisVertical";
import { Data, Variable } from "./data";
import * as d3 from 'd3';
import { Data } from './data';
import { AxisConfig, INNER_RADIUS, RadarGrid } from './RadarGrid';

const MARGIN = 30;

const COLORS = [
"#e0ac2b",
"#e85252",
"#6689c6",
"#9a6fb0",
"#a53253",
"#69b3a2",
];
type YScale = d3.ScaleRadial<number, number, never>;

type RadarProps = {
width: number;
height: number;
data: Data;
axisConfig: {
name: Variable;
max: number;
}[];
axisConfig: AxisConfig[];
};

type YScale = d3.ScaleRadial<number, number, never>;

/*
A react component that builds a Radar Chart for several groups in the dataset
*/
export const Radar = ({ width, height, data, axisConfig }: RadarProps) => {
const outerRadius = Math.min(width, height) / 2 - MARGIN;
const innerRadius = 40;

// The x scale provides an angle for each variable of the dataset
const allVariableNames = axisConfig.map((axis) => axis.name);
Expand All @@ -36,87 +26,47 @@ export const Radar = ({ width, height, data, axisConfig }: RadarProps) => {
.domain(allVariableNames)
.range([0, 2 * Math.PI]);

// Compute the yScales: 1 scale per variable. Provides the distance to the center.
// Compute the y scales: 1 scale per variable.
// Provides the distance to the center.
let yScales: { [name: string]: YScale } = {};

axisConfig.forEach((axis) => {
yScales[axis.name] = d3
.scaleRadial()
.domain([0, axis.max])
.range([innerRadius, outerRadius]);
.range([INNER_RADIUS, outerRadius]);
});

const lineGenerator = d3
.lineRadial()
.angle((d) => d.angle)
.radius((d) => d.radius);
// .curve(d3.curveLinearClosed);

// Color Scale
const allGroups = data.map((d) => d.name);
const colorScale = d3.scaleOrdinal<string>().domain(allGroups).range(COLORS);

const allLines = data.map((series, i) => {
const allCoordinates = axisConfig.map((axis) => {
const yScale = yScales[axis.name];
const angle = xScale(axis.name) ?? 0; // I don't understand the type of scalePoint. IMO x cannot be undefined since I'm passing it something of type Variable.
const radius = yScale(series[axis.name]);
const coordinate: [number, number] = { angle, radius };
return coordinate;
});

const d = lineGenerator(allCoordinates);

if (!d) {
return;
}
// Compute the main radar shapes, 1 per group
const lineGenerator = d3.lineRadial();

return (
<path
key={i}
d={d}
stroke={colorScale(series.name)}
strokeWidth={3}
fill={colorScale(series.name)}
fillOpacity={0.1}
/>
);
const allCoordinates = axisConfig.map((axis) => {
const yScale = yScales[axis.name];
const angle = xScale(axis.name) ?? 0; // I don't understand the type of scalePoint. IMO x cannot be undefined since I'm passing it something of type Variable.
const radius = yScale(data[axis.name]);
const coordinate: [number, number] = [angle, radius];
return coordinate;
});

// Compute Axes
const allAxes = axisConfig.map((axis, i) => {
const path = lineGenerator([
{ angle: xScale(axis.name), radius: innerRadius },
{ angle: xScale(axis.name), radius: outerRadius },
]);

return (
<g key={i}>
<path
d={path}
stroke="#9d174d"
fill="#9d174d"
fillOpacity={0.3}
strokeWidth={1}
rx={1}
/>
</g>
);
});
// To close the path of each group, the path must finish where it started
// so add the last data point at the end of the array
allCoordinates.push(allCoordinates[0]);
const linePath = lineGenerator(allCoordinates);

return (
<svg width={width} height={height}>
<g
transform={
"translate(" +
(width / 2 + MARGIN) +
"," +
(height / 2 + MARGIN) +
")"
}
>
{allLines}
{allAxes}
<g transform={'translate(' + width / 2 + ',' + height / 2 + ')'}>
<RadarGrid
outerRadius={outerRadius}
xScale={xScale}
axisConfig={axisConfig}
/>
<path
d={linePath}
stroke={'#cb1dd1'}
strokeWidth={3}
fill={'#cb1dd1'}
fillOpacity={0.1}
/>
</g>
</svg>
);
Expand Down
16 changes: 8 additions & 8 deletions viz/RadarBasic/RadarBasicDemo.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { data } from "./data";
import { Radar } from "./Radar";
import { data } from './data';
import { Radar } from './Radar';

export const RadarBasicDemo = ({ width = 700, height = 400 }) => (
<Radar
data={data}
width={width}
height={height}
axisConfig={[
{ name: "speed", max: 10 },
{ name: "acceleration", max: 10 },
{ name: "conso", max: 10 },
{ name: "safety", max: 2 },
{ name: "style", max: 1000 },
{ name: "price", max: 100 },
{ name: 'speed', max: 10 },
{ name: 'acceleration', max: 10 },
{ name: 'conso', max: 10 },
{ name: 'safety', max: 2 },
{ name: 'style', max: 1000 },
{ name: 'price', max: 100 },
]}
/>
);
95 changes: 95 additions & 0 deletions viz/RadarBasic/RadarGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Variable } from './data';
import { polarToCartesian } from './polarToCartesian';
import * as d3 from 'd3';

//
// Constants
//
export const INNER_RADIUS = 40;
const GRID_NUMBER = 5;
const GRID_COLOR = 'lightGrey';

//
// Types
//
export type AxisConfig = {
name: Variable;
max: number;
};

type RadarGridProps = {
outerRadius: number;
xScale: d3.ScaleBand<string>;
axisConfig: AxisConfig[];
};

/*
A react component that adds a grid background
for a radar chart in polar coordinates
*/
export const RadarGrid = ({
outerRadius,
xScale,
axisConfig,
}: RadarGridProps) => {
const lineGenerator = d3.lineRadial();

// Compute Axes = from center to outer
const allAxes = axisConfig.map((axis, i) => {
const angle = xScale(axis.name);

if (angle === undefined) {
return null;
}

const path = lineGenerator([
[angle, INNER_RADIUS],
[angle, outerRadius],
]);

const labelPosition = polarToCartesian(
angle - Math.PI / 2,
outerRadius + 10
);

return (
<g key={i}>
<path d={path} stroke={GRID_COLOR} strokeWidth={0.5} rx={1} />
<text
x={labelPosition.x}
y={labelPosition.y}
fontSize={12}
fill={GRID_COLOR}
textAnchor={labelPosition.x > 0 ? 'start' : 'end'}
dominantBaseline="middle"
>
{axis.name}
</text>
</g>
);
});

// Compte grid = concentric circles
const allCircles = [...Array(GRID_NUMBER).keys()].map((position, i) => {
return (
<circle
key={i}
cx={0}
cy={0}
r={
INNER_RADIUS +
(position * (outerRadius - INNER_RADIUS)) / (GRID_NUMBER - 1)
}
stroke={GRID_COLOR}
fill="none"
/>
);
});

return (
<g>
{allAxes}
{allCircles}
</g>
);
};
Loading

0 comments on commit 9362cfd

Please sign in to comment.