chore(codestyle): cleanup and reformat
This commit is contained in:
@@ -1,46 +1,44 @@
|
||||
import React, { useRef, useLayoutEffect } from 'react';
|
||||
import * as d3 from "d3";
|
||||
import React, { useLayoutEffect, useRef } from "react";
|
||||
|
||||
export const YAxis: React.FC<{
|
||||
scale: d3.ScaleLinear<number, number>
|
||||
scale: d3.ScaleLinear<number, number>;
|
||||
}> = ({ scale }) => {
|
||||
const ref = useRef<SVGSVGElement>(null);
|
||||
|
||||
const ref = useRef<SVGSVGElement>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current == null) {
|
||||
return;
|
||||
}
|
||||
const axisGenerator = d3.axisLeft(scale).ticks(6);
|
||||
d3.select(ref.current)
|
||||
.attr("class", "y-axis")
|
||||
.call(axisGenerator)
|
||||
.call((g) => g.selectAll(".tick text").remove())
|
||||
.call((g) => g.selectAll(".tick line").remove())
|
||||
.call((g) => g.selectAll(".domain").remove());
|
||||
}, [scale]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current == null) {
|
||||
return
|
||||
}
|
||||
const axisGenerator = d3.axisLeft(scale).ticks(6);
|
||||
d3.select(ref.current)
|
||||
.attr('class', 'y-axis')
|
||||
.call(axisGenerator)
|
||||
.call(g => g.selectAll('.tick text').remove())
|
||||
.call(g => g.selectAll('.tick line').remove())
|
||||
.call(g => g.selectAll('.domain').remove());
|
||||
}, [scale]);
|
||||
|
||||
return <g ref={ref} />;
|
||||
return <g ref={ref} />;
|
||||
};
|
||||
|
||||
export const XAxis: React.FC<{
|
||||
scale: d3.ScaleLinear<number, number>
|
||||
scale: d3.ScaleLinear<number, number>;
|
||||
}> = ({ scale }) => {
|
||||
const ref = useRef<SVGSVGElement>(null);
|
||||
|
||||
const ref = useRef<SVGSVGElement>(null);
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current == null) {
|
||||
return;
|
||||
}
|
||||
const axisGenerator = d3.axisBottom(scale).ticks(6);
|
||||
d3.select(ref.current)
|
||||
.attr("class", "x-axis")
|
||||
.call(axisGenerator)
|
||||
.call((g) => g.selectAll(".tick text").remove())
|
||||
.call((g) => g.selectAll(".tick line").remove())
|
||||
.call((g) => g.selectAll(".domain").remove());
|
||||
}, [scale]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (ref.current == null) {
|
||||
return
|
||||
}
|
||||
const axisGenerator = d3.axisBottom(scale).ticks(6);
|
||||
d3.select(ref.current)
|
||||
.attr('class', 'x-axis')
|
||||
.call(axisGenerator)
|
||||
.call(g => g.selectAll('.tick text').remove())
|
||||
.call(g => g.selectAll('.tick line').remove())
|
||||
.call(g => g.selectAll('.domain').remove());
|
||||
}, [scale]);
|
||||
|
||||
return <g ref={ref} />;
|
||||
return <g ref={ref} />;
|
||||
};
|
||||
|
||||
@@ -1,126 +1,147 @@
|
||||
import React from 'react';
|
||||
import { ScaleLinear } from 'd3';
|
||||
import { FlagType, Item, Blip, Point } from '../../model';
|
||||
import Link from '../Link/Link';
|
||||
import { NewBlip, ChangedBlip, DefaultBlip } from './BlipShapes';
|
||||
import { ConfigData } from '../../config';
|
||||
import { ScaleLinear } from "d3";
|
||||
import React from "react";
|
||||
|
||||
import { ConfigData } from "../../config";
|
||||
import { Blip, FlagType, Item, Point } from "../../model";
|
||||
import Link from "../Link/Link";
|
||||
import { ChangedBlip, DefaultBlip, NewBlip } from "./BlipShapes";
|
||||
|
||||
/*
|
||||
See https://medium.com/create-code/build-a-radar-diagram-with-d3-js-9db6458a9248
|
||||
for a good explanation of formulas used to calculate various things in this component
|
||||
*/
|
||||
|
||||
function generateCoordinates(blip: Blip, xScale: ScaleLinear<number, number>, yScale: ScaleLinear<number, number>, config: ConfigData): Point {
|
||||
const pi = Math.PI,
|
||||
ringRadius = config.chartConfig.ringsAttributes[blip.ringPosition].radius,
|
||||
previousRingRadius = blip.ringPosition === 0 ? 0 : config.chartConfig.ringsAttributes[blip.ringPosition - 1].radius,
|
||||
ringPadding = 0.7;
|
||||
function generateCoordinates(
|
||||
blip: Blip,
|
||||
xScale: ScaleLinear<number, number>,
|
||||
yScale: ScaleLinear<number, number>,
|
||||
config: ConfigData
|
||||
): Point {
|
||||
const pi = Math.PI,
|
||||
ringRadius = config.chartConfig.ringsAttributes[blip.ringPosition].radius,
|
||||
previousRingRadius =
|
||||
blip.ringPosition === 0
|
||||
? 0
|
||||
: config.chartConfig.ringsAttributes[blip.ringPosition - 1].radius,
|
||||
ringPadding = 0.7;
|
||||
|
||||
// radian between 0 and 90 degrees
|
||||
const randomDegree = ((Math.random() * 90) * pi) / 180;
|
||||
// random distance from the centre of the radar, but within given ring. Also, with some "padding" so the points don't touch ring borders.
|
||||
const radius = randomBetween(previousRingRadius + ringPadding, ringRadius - ringPadding);
|
||||
/*
|
||||
// radian between 0 and 90 degrees
|
||||
const randomDegree = (Math.random() * 90 * pi) / 180;
|
||||
// random distance from the centre of the radar, but within given ring. Also, with some "padding" so the points don't touch ring borders.
|
||||
const radius = randomBetween(
|
||||
previousRingRadius + ringPadding,
|
||||
ringRadius - ringPadding
|
||||
);
|
||||
/*
|
||||
Multiples of PI/2. To apply the calculated position to the specific quadrant.
|
||||
Order here is counter-clockwise, so we need to "invert" quadrant positions (i.e. swap 2 with 4)
|
||||
*/
|
||||
const shift = pi * [1, 4, 3, 2][blip.quadrantPosition - 1] / 2;
|
||||
const shift = (pi * [1, 4, 3, 2][blip.quadrantPosition - 1]) / 2;
|
||||
|
||||
return {
|
||||
x: xScale(Math.cos(randomDegree + shift) * radius),
|
||||
y: yScale(Math.sin(randomDegree + shift) * radius)
|
||||
};
|
||||
};
|
||||
return {
|
||||
x: xScale(Math.cos(randomDegree + shift) * radius),
|
||||
y: yScale(Math.sin(randomDegree + shift) * radius),
|
||||
};
|
||||
}
|
||||
|
||||
function randomBetween (min: number, max: number): number {
|
||||
return Math.random() * (max - min) + min;
|
||||
};
|
||||
function randomBetween(min: number, max: number): number {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
function distanceBetween(point1: Point, point2: Point): number {
|
||||
const a = point2.x - point1.x;
|
||||
const b = point2.y - point1.y;
|
||||
return Math.sqrt((a * a) + (b * b));
|
||||
};
|
||||
const a = point2.x - point1.x;
|
||||
const b = point2.y - point1.y;
|
||||
return Math.sqrt(a * a + b * b);
|
||||
}
|
||||
|
||||
function renderBlip(blip: Blip, index: number, config: ConfigData): JSX.Element {
|
||||
const props = {
|
||||
blip,
|
||||
className: 'blip',
|
||||
fill: blip.colour,
|
||||
'data-background-color': blip.colour,
|
||||
'data-text-color': blip.txtColour,
|
||||
'data-tip': blip.title,
|
||||
key: index
|
||||
}
|
||||
switch (blip.flag) {
|
||||
case FlagType.new:
|
||||
return <NewBlip {...props} config={config} />;
|
||||
case FlagType.changed:
|
||||
return <ChangedBlip {...props} config={config} />;
|
||||
default:
|
||||
return <DefaultBlip {...props} config={config} />;
|
||||
}
|
||||
};
|
||||
function renderBlip(
|
||||
blip: Blip,
|
||||
index: number,
|
||||
config: ConfigData
|
||||
): JSX.Element {
|
||||
const props = {
|
||||
blip,
|
||||
className: "blip",
|
||||
fill: blip.colour,
|
||||
"data-background-color": blip.colour,
|
||||
"data-text-color": blip.txtColour,
|
||||
"data-tip": blip.title,
|
||||
key: index,
|
||||
};
|
||||
switch (blip.flag) {
|
||||
case FlagType.new:
|
||||
return <NewBlip {...props} config={config} />;
|
||||
case FlagType.changed:
|
||||
return <ChangedBlip {...props} config={config} />;
|
||||
default:
|
||||
return <DefaultBlip {...props} config={config} />;
|
||||
}
|
||||
}
|
||||
|
||||
const BlipPoints: React.FC<{
|
||||
items: Item[]
|
||||
xScale:ScaleLinear<number, number>
|
||||
yScale:ScaleLinear<number, number>
|
||||
config:ConfigData
|
||||
}> = ({items, xScale, yScale, config}) => {
|
||||
items: Item[];
|
||||
xScale: ScaleLinear<number, number>;
|
||||
yScale: ScaleLinear<number, number>;
|
||||
config: ConfigData;
|
||||
}> = ({ items, xScale, yScale, config }) => {
|
||||
const blips: Blip[] = items.reduce((list: Blip[], item: Item) => {
|
||||
if (!item.ring || !item.quadrant) {
|
||||
// skip the blip if it doesn't have a ring or quadrant assigned
|
||||
return list;
|
||||
}
|
||||
const quadrantConfig = config.quadrantsMap[item.quadrant];
|
||||
if (!quadrantConfig) {
|
||||
return list;
|
||||
}
|
||||
|
||||
const blips: Blip[] = items.reduce((list: Blip[], item: Item) => {
|
||||
if (!item.ring || !item.quadrant) {
|
||||
// skip the blip if it doesn't have a ring or quadrant assigned
|
||||
return list;
|
||||
}
|
||||
const quadrantConfig = config.quadrantsMap[item.quadrant];
|
||||
if (!quadrantConfig) {
|
||||
return list;
|
||||
}
|
||||
let blip: Blip = {
|
||||
...item,
|
||||
quadrantPosition: quadrantConfig.position,
|
||||
ringPosition: config.rings.findIndex((r) => r === item.ring),
|
||||
colour: quadrantConfig.colour,
|
||||
txtColour: quadrantConfig.txtColour,
|
||||
coordinates: { x: 0, y: 0 },
|
||||
};
|
||||
|
||||
let blip: Blip = { ...item,
|
||||
quadrantPosition: quadrantConfig.position,
|
||||
ringPosition: config.rings.findIndex(r => r === item.ring),
|
||||
colour: quadrantConfig.colour,
|
||||
txtColour: quadrantConfig.txtColour,
|
||||
coordinates: {x: 0, y: 0},
|
||||
};
|
||||
|
||||
let point: Point;
|
||||
let counter = 1;
|
||||
let distanceBetweenCheck: boolean;
|
||||
do {
|
||||
const localpoint = generateCoordinates(blip, xScale, yScale, config);
|
||||
point = localpoint
|
||||
counter++;
|
||||
/*
|
||||
let point: Point;
|
||||
let counter = 1;
|
||||
let distanceBetweenCheck: boolean;
|
||||
do {
|
||||
const localpoint = generateCoordinates(blip, xScale, yScale, config);
|
||||
point = localpoint;
|
||||
counter++;
|
||||
/*
|
||||
Generate position of the new blip until it has a satisfactory distance to every other blip (so that they don't touch each other)
|
||||
and quadrant borders (so that they don't overlap quadrants)
|
||||
This feels pretty inefficient, but good enough for now.
|
||||
*/
|
||||
distanceBetweenCheck = list.some(b => distanceBetween(localpoint, b.coordinates) < config.chartConfig.blipSize + config.chartConfig.blipSize / 2)
|
||||
} while (counter < 100
|
||||
&& (Math.abs(point.x - xScale(0)) < 15
|
||||
|| Math.abs(point.y - yScale(0)) < 15
|
||||
|| distanceBetweenCheck
|
||||
));
|
||||
|
||||
blip.coordinates = point;
|
||||
|
||||
list.push(blip);
|
||||
return list;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<g className="blips">
|
||||
{blips.map((blip, index) => (
|
||||
<Link pageName={`${blip.quadrant}/${blip.name}`} key={index}>
|
||||
{renderBlip(blip, index, config)}
|
||||
</Link>
|
||||
))}
|
||||
</g>
|
||||
distanceBetweenCheck = list.some(
|
||||
(b) =>
|
||||
distanceBetween(localpoint, b.coordinates) <
|
||||
config.chartConfig.blipSize + config.chartConfig.blipSize / 2
|
||||
);
|
||||
} while (
|
||||
counter < 100 &&
|
||||
(Math.abs(point.x - xScale(0)) < 15 ||
|
||||
Math.abs(point.y - yScale(0)) < 15 ||
|
||||
distanceBetweenCheck)
|
||||
);
|
||||
|
||||
blip.coordinates = point;
|
||||
|
||||
list.push(blip);
|
||||
return list;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<g className="blips">
|
||||
{blips.map((blip, index) => (
|
||||
<Link pageName={`${blip.quadrant}/${blip.name}`} key={index}>
|
||||
{renderBlip(blip, index, config)}
|
||||
</Link>
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlipPoints;
|
||||
export default BlipPoints;
|
||||
|
||||
@@ -1,64 +1,65 @@
|
||||
import React from 'react';
|
||||
import { ConfigData } from '../../config';
|
||||
import { Blip } from '../../model';
|
||||
import React from "react";
|
||||
|
||||
import { ConfigData } from "../../config";
|
||||
import { Blip } from "../../model";
|
||||
|
||||
type VisualBlipProps = {
|
||||
className: string,
|
||||
fill: string,
|
||||
'data-background-color': string,
|
||||
'data-text-color': string,
|
||||
'data-tip': string,
|
||||
key: number
|
||||
}
|
||||
className: string;
|
||||
fill: string;
|
||||
"data-background-color": string;
|
||||
"data-text-color": string;
|
||||
"data-tip": string;
|
||||
key: number;
|
||||
};
|
||||
|
||||
export const ChangedBlip: React.FC<
|
||||
{blip: Blip, config: ConfigData} & VisualBlipProps
|
||||
> = ({blip, config, ...props}) => {
|
||||
const centeredX = blip.coordinates.x - config.chartConfig.blipSize/2,
|
||||
centeredY = blip.coordinates.y - config.chartConfig.blipSize/2;
|
||||
{ blip: Blip; config: ConfigData } & VisualBlipProps
|
||||
> = ({ blip, config, ...props }) => {
|
||||
const centeredX = blip.coordinates.x - config.chartConfig.blipSize / 2,
|
||||
centeredY = blip.coordinates.y - config.chartConfig.blipSize / 2;
|
||||
|
||||
return (
|
||||
<rect
|
||||
transform={`rotate(-45 ${centeredX} ${centeredY})`}
|
||||
x={centeredX}
|
||||
y={centeredY}
|
||||
width={config.chartConfig.blipSize}
|
||||
height={config.chartConfig.blipSize}
|
||||
rx="3"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<rect
|
||||
transform={`rotate(-45 ${centeredX} ${centeredY})`}
|
||||
x={centeredX}
|
||||
y={centeredY}
|
||||
width={config.chartConfig.blipSize}
|
||||
height={config.chartConfig.blipSize}
|
||||
rx="3"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const NewBlip: React.FC<
|
||||
{blip: Blip, config: ConfigData} & VisualBlipProps
|
||||
> = ({blip, config, ...props}) => {
|
||||
const centeredX = blip.coordinates.x - config.chartConfig.blipSize/2,
|
||||
centeredY = blip.coordinates.y - config.chartConfig.blipSize/2;
|
||||
{ blip: Blip; config: ConfigData } & VisualBlipProps
|
||||
> = ({ blip, config, ...props }) => {
|
||||
const centeredX = blip.coordinates.x - config.chartConfig.blipSize / 2,
|
||||
centeredY = blip.coordinates.y - config.chartConfig.blipSize / 2;
|
||||
|
||||
/*
|
||||
/*
|
||||
The below is a predefined path of a triangle with rounded corners.
|
||||
I didn't find any more human friendly way of doing this as all examples I found have tons of lines of code
|
||||
e.g. https://observablehq.com/@perlmonger42/interactive-rounded-corners-on-svg-polygons-using-d3-js
|
||||
*/
|
||||
return (
|
||||
<path
|
||||
transform={`translate(${centeredX}, ${centeredY})`}
|
||||
d="M.247 10.212l5.02-8.697a2 2 0 013.465 0l5.021 8.697a2 2 0 01-1.732 3H1.98a2 2 0 01-1.732-3z"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<path
|
||||
transform={`translate(${centeredX}, ${centeredY})`}
|
||||
d="M.247 10.212l5.02-8.697a2 2 0 013.465 0l5.021 8.697a2 2 0 01-1.732 3H1.98a2 2 0 01-1.732-3z"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const DefaultBlip: React.FC<
|
||||
{blip: Blip, config: ConfigData} & VisualBlipProps
|
||||
> = ({blip, config, ...props}) => {
|
||||
return (
|
||||
<circle
|
||||
r={config.chartConfig.blipSize / 2}
|
||||
cx={blip.coordinates.x}
|
||||
cy={blip.coordinates.y}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
{ blip: Blip; config: ConfigData } & VisualBlipProps
|
||||
> = ({ blip, config, ...props }) => {
|
||||
return (
|
||||
<circle
|
||||
r={config.chartConfig.blipSize / 2}
|
||||
cx={blip.coordinates.x}
|
||||
cy={blip.coordinates.y}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,74 +1,92 @@
|
||||
import React from 'react';
|
||||
import * as d3 from 'd3';
|
||||
import { QuadrantConfig } from '../../model';
|
||||
import { ConfigData } from '../../config';
|
||||
import * as d3 from "d3";
|
||||
import React from "react";
|
||||
|
||||
function arcPath(quadrantPosition: number, ringPosition: number, xScale: d3.ScaleLinear<number, number>, config: ConfigData) {
|
||||
const startAngle = quadrantPosition === 1 ?
|
||||
3 * Math.PI / 2
|
||||
: (quadrantPosition - 2) * Math.PI / 2
|
||||
const endAngle = quadrantPosition === 1 ?
|
||||
4 * Math.PI / 2
|
||||
: (quadrantPosition -1) * Math.PI / 2
|
||||
const arcAttrs = config.chartConfig.ringsAttributes[ringPosition],
|
||||
ringRadiusPx = xScale(arcAttrs.radius) - xScale(0),
|
||||
arc = d3.arc();
|
||||
import { ConfigData } from "../../config";
|
||||
import { QuadrantConfig } from "../../model";
|
||||
|
||||
return arc({
|
||||
innerRadius: ringRadiusPx - arcAttrs.arcWidth,
|
||||
outerRadius: ringRadiusPx,
|
||||
startAngle,
|
||||
endAngle
|
||||
}) || undefined;
|
||||
function arcPath(
|
||||
quadrantPosition: number,
|
||||
ringPosition: number,
|
||||
xScale: d3.ScaleLinear<number, number>,
|
||||
config: ConfigData
|
||||
) {
|
||||
const startAngle =
|
||||
quadrantPosition === 1
|
||||
? (3 * Math.PI) / 2
|
||||
: ((quadrantPosition - 2) * Math.PI) / 2;
|
||||
const endAngle =
|
||||
quadrantPosition === 1
|
||||
? (4 * Math.PI) / 2
|
||||
: ((quadrantPosition - 1) * Math.PI) / 2;
|
||||
const arcAttrs = config.chartConfig.ringsAttributes[ringPosition],
|
||||
ringRadiusPx = xScale(arcAttrs.radius) - xScale(0),
|
||||
arc = d3.arc();
|
||||
|
||||
return (
|
||||
arc({
|
||||
innerRadius: ringRadiusPx - arcAttrs.arcWidth,
|
||||
outerRadius: ringRadiusPx,
|
||||
startAngle,
|
||||
endAngle,
|
||||
}) || undefined
|
||||
);
|
||||
}
|
||||
|
||||
const QuadrantRings: React.FC<{
|
||||
quadrant: QuadrantConfig
|
||||
xScale: d3.ScaleLinear<number, number>
|
||||
config: ConfigData
|
||||
}> = ({ quadrant, xScale, config}) => {
|
||||
// order from top-right clockwise
|
||||
const gradientAttributes = [
|
||||
{x: 0, y: 0, cx: 1, cy: 1, r: 1},
|
||||
{x: xScale(0), y: 0, cx: 0, cy: 1, r: 1},
|
||||
{x: xScale(0), y: xScale(0), cx: 0, cy: 0, r: 1},
|
||||
{x: 0, y: xScale(0), cx: 1, cy: 0, r: 1}
|
||||
];
|
||||
const gradientId = `${quadrant.position}-radial-gradient`,
|
||||
quadrantSize = config.chartConfig.size / 2;
|
||||
quadrant: QuadrantConfig;
|
||||
xScale: d3.ScaleLinear<number, number>;
|
||||
config: ConfigData;
|
||||
}> = ({ quadrant, xScale, config }) => {
|
||||
// order from top-right clockwise
|
||||
const gradientAttributes = [
|
||||
{ x: 0, y: 0, cx: 1, cy: 1, r: 1 },
|
||||
{ x: xScale(0), y: 0, cx: 0, cy: 1, r: 1 },
|
||||
{ x: xScale(0), y: xScale(0), cx: 0, cy: 0, r: 1 },
|
||||
{ x: 0, y: xScale(0), cx: 1, cy: 0, r: 1 },
|
||||
];
|
||||
const gradientId = `${quadrant.position}-radial-gradient`,
|
||||
quadrantSize = config.chartConfig.size / 2;
|
||||
|
||||
return (
|
||||
<g className="quadrant-ring">
|
||||
{/* Definition of the quadrant gradient */}
|
||||
<defs>
|
||||
<radialGradient id={gradientId} {...gradientAttributes[quadrant.position - 1]}>
|
||||
<stop offset="0%" stopColor={quadrant.colour}></stop>
|
||||
<stop offset="100%" stopColor={quadrant.colour} stopOpacity="0"></stop>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
return (
|
||||
<g className="quadrant-ring">
|
||||
{/* Definition of the quadrant gradient */}
|
||||
<defs>
|
||||
<radialGradient
|
||||
id={gradientId}
|
||||
{...gradientAttributes[quadrant.position - 1]}
|
||||
>
|
||||
<stop offset="0%" stopColor={quadrant.colour}></stop>
|
||||
<stop
|
||||
offset="100%"
|
||||
stopColor={quadrant.colour}
|
||||
stopOpacity="0"
|
||||
></stop>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
{/* Gradient background area */}
|
||||
<rect
|
||||
width={quadrantSize}
|
||||
height={quadrantSize}
|
||||
x={gradientAttributes[quadrant.position - 1].x}
|
||||
y={gradientAttributes[quadrant.position - 1].y}
|
||||
fill={`url(#${gradientId})`}
|
||||
style={{opacity: 0.5}}
|
||||
{/* Gradient background area */}
|
||||
<rect
|
||||
width={quadrantSize}
|
||||
height={quadrantSize}
|
||||
x={gradientAttributes[quadrant.position - 1].x}
|
||||
y={gradientAttributes[quadrant.position - 1].y}
|
||||
fill={`url(#${gradientId})`}
|
||||
style={{ opacity: 0.5 }}
|
||||
/>
|
||||
|
||||
{/* Rings' arcs */}
|
||||
{Array.from(config.rings).map((ringPosition, index) => (
|
||||
<path
|
||||
key={index}
|
||||
fill={quadrant.colour}
|
||||
d={arcPath(quadrant.position, index, xScale, config)}
|
||||
style={{
|
||||
transform: `translate(${quadrantSize}px, ${quadrantSize}px)`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
{/* Rings' arcs */}
|
||||
{Array.from(config.rings).map((ringPosition, index) => (
|
||||
<path
|
||||
key={index}
|
||||
fill={quadrant.colour}
|
||||
d={arcPath(quadrant.position, index, xScale, config)}
|
||||
style={{transform: `translate(${quadrantSize}px, ${quadrantSize}px)`}}
|
||||
/>
|
||||
))}
|
||||
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
export default QuadrantRings;
|
||||
export default QuadrantRings;
|
||||
|
||||
@@ -1,77 +1,109 @@
|
||||
import React from 'react';
|
||||
import * as d3 from "d3";
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import { Item } from '../../model';
|
||||
import { YAxis, XAxis } from './Axes';
|
||||
import QuadrantRings from './QuadrantRings';
|
||||
import BlipPoints from './BlipPoints';
|
||||
import React from "react";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
|
||||
import './chart.scss';
|
||||
import { ConfigData } from '../../config';
|
||||
import { ConfigData } from "../../config";
|
||||
import { Item } from "../../model";
|
||||
import { XAxis, YAxis } from "./Axes";
|
||||
import BlipPoints from "./BlipPoints";
|
||||
import QuadrantRings from "./QuadrantRings";
|
||||
import "./chart.scss";
|
||||
|
||||
const RingLabel: React.FC<{
|
||||
ring: string
|
||||
xScale: d3.ScaleLinear<number, number>
|
||||
yScale: d3.ScaleLinear<number, number>
|
||||
config: ConfigData
|
||||
}> = ({ring, xScale, yScale, config}) => {
|
||||
const ringIndex = config.rings.findIndex(r => r === ring)
|
||||
ring: string;
|
||||
xScale: d3.ScaleLinear<number, number>;
|
||||
yScale: d3.ScaleLinear<number, number>;
|
||||
config: ConfigData;
|
||||
}> = ({ ring, xScale, yScale, config }) => {
|
||||
const ringIndex = config.rings.findIndex((r) => r === ring);
|
||||
|
||||
const ringRadius = config.chartConfig.ringsAttributes[ringIndex].radius,
|
||||
previousRingRadius = ringIndex === 0 ? 0 : config.chartConfig.ringsAttributes[ringIndex - 1].radius,
|
||||
const ringRadius = config.chartConfig.ringsAttributes[ringIndex].radius,
|
||||
previousRingRadius =
|
||||
ringIndex === 0
|
||||
? 0
|
||||
: config.chartConfig.ringsAttributes[ringIndex - 1].radius,
|
||||
// middle point in between two ring arcs
|
||||
distanceFromCentre =
|
||||
previousRingRadius + (ringRadius - previousRingRadius) / 2;
|
||||
|
||||
// middle point in between two ring arcs
|
||||
distanceFromCentre = previousRingRadius + (ringRadius - previousRingRadius) / 2;
|
||||
|
||||
return (
|
||||
<g className="ring-label">
|
||||
{/* Right hand-side label */}
|
||||
<text x={xScale(distanceFromCentre)} y={yScale(0)} textAnchor="middle" dy=".35em">
|
||||
{ring}
|
||||
</text>
|
||||
{/* Left hand-side label */}
|
||||
<text x={xScale(-distanceFromCentre)} y={yScale(0)} textAnchor="middle" dy=".35em">
|
||||
{ring}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
return (
|
||||
<g className="ring-label">
|
||||
{/* Right hand-side label */}
|
||||
<text
|
||||
x={xScale(distanceFromCentre)}
|
||||
y={yScale(0)}
|
||||
textAnchor="middle"
|
||||
dy=".35em"
|
||||
>
|
||||
{ring}
|
||||
</text>
|
||||
{/* Left hand-side label */}
|
||||
<text
|
||||
x={xScale(-distanceFromCentre)}
|
||||
y={yScale(0)}
|
||||
textAnchor="middle"
|
||||
dy=".35em"
|
||||
>
|
||||
{ring}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
const RadarChart: React.FC<{
|
||||
items: Item[]
|
||||
config: ConfigData
|
||||
items: Item[];
|
||||
config: ConfigData;
|
||||
}> = ({ items, config }) => {
|
||||
|
||||
const xScale = d3.scaleLinear()
|
||||
const xScale = d3
|
||||
.scaleLinear()
|
||||
.domain(config.chartConfig.scale)
|
||||
.range([0, config.chartConfig.size]);
|
||||
const yScale = d3.scaleLinear()
|
||||
const yScale = d3
|
||||
.scaleLinear()
|
||||
.domain(config.chartConfig.scale)
|
||||
.range([config.chartConfig.size, 0]);
|
||||
|
||||
return (
|
||||
<div className="chart" style={{maxWidth: `${config.chartConfig.size}px`}}>
|
||||
<svg viewBox={`0 0 ${config.chartConfig.size} ${config.chartConfig.size}`}>
|
||||
<g transform={`translate(${xScale(0)}, 0)`}>
|
||||
<YAxis scale={yScale}/>
|
||||
</g>
|
||||
<g transform={`translate(0, ${yScale(0)})`}>
|
||||
<XAxis scale={xScale}/>
|
||||
</g>
|
||||
<div className="chart" style={{ maxWidth: `${config.chartConfig.size}px` }}>
|
||||
<svg
|
||||
viewBox={`0 0 ${config.chartConfig.size} ${config.chartConfig.size}`}
|
||||
>
|
||||
<g transform={`translate(${xScale(0)}, 0)`}>
|
||||
<YAxis scale={yScale} />
|
||||
</g>
|
||||
<g transform={`translate(0, ${yScale(0)})`}>
|
||||
<XAxis scale={xScale} />
|
||||
</g>
|
||||
|
||||
{Object.values(config.quadrantsMap).map((value, index) => (
|
||||
<QuadrantRings key={index} quadrant={value} xScale={xScale} config={config} />
|
||||
))}
|
||||
{Object.values(config.quadrantsMap).map((value, index) => (
|
||||
<QuadrantRings
|
||||
key={index}
|
||||
quadrant={value}
|
||||
xScale={xScale}
|
||||
config={config}
|
||||
/>
|
||||
))}
|
||||
|
||||
{Array.from(config.rings).map((ring: string, index) => (
|
||||
<RingLabel key={index} ring={ring} xScale={xScale} yScale={yScale} config={config} />
|
||||
))}
|
||||
{Array.from(config.rings).map((ring: string, index) => (
|
||||
<RingLabel
|
||||
key={index}
|
||||
ring={ring}
|
||||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
config={config}
|
||||
/>
|
||||
))}
|
||||
|
||||
<BlipPoints items={items} xScale={xScale} yScale={yScale} config={config} />
|
||||
<BlipPoints
|
||||
items={items}
|
||||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
config={config}
|
||||
/>
|
||||
</svg>
|
||||
<ReactTooltip className="tooltip" offset={{top: -5}}/>
|
||||
<ReactTooltip className="tooltip" offset={{ top: -5 }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default RadarChart;
|
||||
|
||||
Reference in New Issue
Block a user