chore(codestyle): cleanup and reformat

This commit is contained in:
Bastian Ike
2022-09-13 11:18:36 +02:00
committed by Bastian
parent 6081f1edbb
commit 37c43712d1
56 changed files with 1142 additions and 560 deletions

View File

@@ -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} />;
};

View File

@@ -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;

View File

@@ -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}
/>
);
};

View File

@@ -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;

View File

@@ -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;