Add types
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
import ReactFauxDOM from 'react-faux-dom';
|
||||
import * as d3 from "d3";
|
||||
|
||||
export const YAxis = ({ scale }) => {
|
||||
export const YAxis: React.FC<{
|
||||
scale: d3.ScaleLinear
|
||||
}> = ({ scale }) => {
|
||||
const el = ReactFauxDOM.createElement('g');
|
||||
|
||||
const axisGenerator = d3.axisLeft(scale).ticks(6);
|
||||
@@ -15,7 +17,9 @@ export const YAxis = ({ scale }) => {
|
||||
return el.toReact();
|
||||
};
|
||||
|
||||
export const XAxis = ({ scale }) => {
|
||||
export const XAxis: React.FC<{
|
||||
scale: d3.ScaleLinear
|
||||
}> = ({ scale }) => {
|
||||
const el = ReactFauxDOM.createElement('g');
|
||||
|
||||
const axisGenerator = d3.axisBottom(scale).ticks(6);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import {FlagType, Ring} from '../../model';
|
||||
import { ScaleLinear } from 'd3';
|
||||
import { FlagType, Item, Blip, Point, Ring } from '../../model';
|
||||
import { quadrantsMap, chartConfig } from '../../config';
|
||||
import Link from '../Link/Link';
|
||||
import { NewBlip, ChangedBlip, DefaultBlip } from './BlipShapes';
|
||||
@@ -9,10 +10,10 @@ 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
|
||||
*/
|
||||
|
||||
const generateCoordinates = (enrichedBlip, xScale, yScale) => {
|
||||
function generateCoordinates(blip: Blip, xScale: ScaleLinear, yScale: ScaleLinear): Point {
|
||||
const pi = Math.PI,
|
||||
ringRadius = chartConfig.ringsAttributes[enrichedBlip.ringPosition - 1].radius,
|
||||
previousRingRadius = enrichedBlip.ringPosition == 1 ? 0 : chartConfig.ringsAttributes[enrichedBlip.ringPosition - 2].radius,
|
||||
ringRadius = chartConfig.ringsAttributes[blip.ringPosition - 1].radius,
|
||||
previousRingRadius = blip.ringPosition == 1 ? 0 : chartConfig.ringsAttributes[blip.ringPosition - 2].radius,
|
||||
ringPadding = 0.7;
|
||||
|
||||
// radian between 0 and 90 degrees
|
||||
@@ -23,7 +24,7 @@ const generateCoordinates = (enrichedBlip, xScale, yScale) => {
|
||||
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][enrichedBlip.quadrantPosition - 1] / 2;
|
||||
const shift = pi * [1, 4, 3, 2][blip.quadrantPosition - 1] / 2;
|
||||
|
||||
return {
|
||||
x: xScale(Math.cos(randomDegree + shift) * radius),
|
||||
@@ -31,34 +32,62 @@ const generateCoordinates = (enrichedBlip, xScale, yScale) => {
|
||||
};
|
||||
};
|
||||
|
||||
const randomBetween = (min, max) => {
|
||||
function randomBetween (min: number, max: number): number {
|
||||
return Math.random() * (max - min) + min;
|
||||
};
|
||||
|
||||
const distanceBetween = (point1, point2) => {
|
||||
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));
|
||||
};
|
||||
|
||||
export default function BlipPoints({blips, xScale, yScale}) {
|
||||
function renderBlip(blip: Blip, index: number): 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} />;
|
||||
case FlagType.changed:
|
||||
return <ChangedBlip {...props} />;
|
||||
default:
|
||||
return <DefaultBlip {...props} />;
|
||||
}
|
||||
};
|
||||
|
||||
const enrichedBlips = blips.reduce((list, blip) => {
|
||||
if (!blip.ring || !blip.quadrant) {
|
||||
const BlipPoints: React.FC<{
|
||||
items: Item[]
|
||||
xScale:ScaleLinear
|
||||
yScale:ScaleLinear
|
||||
}> = ({items, xScale, yScale}) => {
|
||||
|
||||
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;
|
||||
}
|
||||
let enrichedBlip = { ...blip,
|
||||
quadrantPosition: quadrantsMap[blip.quadrant].position,
|
||||
ringPosition: Ring[blip.ring],
|
||||
colour: quadrantsMap[blip.quadrant].colour,
|
||||
txtColour: quadrantsMap[blip.quadrant].txtColour
|
||||
const quadrantConfig = quadrantsMap.get(item.quadrant);
|
||||
|
||||
let blip: Blip = { ...item,
|
||||
quadrantPosition: quadrantConfig.position,
|
||||
// TODO get to the bottom of this
|
||||
// @ts-ignore
|
||||
ringPosition: Ring[item.ring],
|
||||
colour: quadrantConfig.colour,
|
||||
txtColour: quadrantConfig.txtColour
|
||||
};
|
||||
|
||||
let point;
|
||||
let counter = 1;
|
||||
do {
|
||||
point = generateCoordinates(enrichedBlip, xScale, yScale);
|
||||
point = generateCoordinates(blip, xScale, yScale);
|
||||
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)
|
||||
@@ -68,43 +97,24 @@ export default function BlipPoints({blips, xScale, yScale}) {
|
||||
} while (counter < 100
|
||||
&& (Math.abs(point.x - xScale(0)) < 15
|
||||
|| Math.abs(point.y - yScale(0)) < 15
|
||||
|| list.some(item => distanceBetween(point, item) < chartConfig.blipSize + chartConfig.blipSize / 2)
|
||||
|| list.some(b => distanceBetween(point, b.coordinates) < chartConfig.blipSize + chartConfig.blipSize / 2)
|
||||
));
|
||||
|
||||
enrichedBlip.x = point.x;
|
||||
enrichedBlip.y = point.y;
|
||||
blip.coordinates = point;
|
||||
|
||||
list.push(enrichedBlip);
|
||||
list.push(blip);
|
||||
return list;
|
||||
}, []);
|
||||
|
||||
const renderBlip = (blip, index) => {
|
||||
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} />;
|
||||
case FlagType.changed:
|
||||
return <ChangedBlip {...props} />;
|
||||
default:
|
||||
return <DefaultBlip {...props} />;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<g className="blips">
|
||||
{enrichedBlips.map((blip, index) => (
|
||||
{blips.map((blip, index) => (
|
||||
<Link pageName={`${blip.quadrant}/${blip.name}`} key={index}>
|
||||
{renderBlip(blip, index)}
|
||||
</Link>
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default BlipPoints;
|
||||
@@ -1,9 +1,21 @@
|
||||
import React from 'react';
|
||||
import { Blip } from '../../model';
|
||||
import { chartConfig } from '../../config';
|
||||
|
||||
export const ChangedBlip = ({blip, ...props}) => {
|
||||
const centeredX = blip.x - chartConfig.blipSize/2,
|
||||
centeredY = blip.y - chartConfig.blipSize/2;
|
||||
type VisualBlipProps = {
|
||||
className: string,
|
||||
fill: string,
|
||||
'data-background-color': string,
|
||||
'data-text-color': string,
|
||||
'data-tip': string,
|
||||
key: number
|
||||
}
|
||||
|
||||
export const ChangedBlip: React.FC<
|
||||
{blip: Blip} & VisualBlipProps
|
||||
> = ({blip, ...props}) => {
|
||||
const centeredX = blip.coordinates.x - chartConfig.blipSize/2,
|
||||
centeredY = blip.coordinates.y - chartConfig.blipSize/2;
|
||||
|
||||
return (
|
||||
<rect
|
||||
@@ -18,9 +30,11 @@ export const ChangedBlip = ({blip, ...props}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const NewBlip = ({blip, ...props}) => {
|
||||
const centeredX = blip.x - chartConfig.blipSize/2,
|
||||
centeredY = blip.y - chartConfig.blipSize/2;
|
||||
export const NewBlip: React.FC<
|
||||
{blip: Blip} & VisualBlipProps
|
||||
> = ({blip, ...props}) => {
|
||||
const centeredX = blip.coordinates.x - chartConfig.blipSize/2,
|
||||
centeredY = blip.coordinates.y - chartConfig.blipSize/2;
|
||||
|
||||
/*
|
||||
The below is a predefined path of a triangle with rounded corners.
|
||||
@@ -36,12 +50,14 @@ export const NewBlip = ({blip, ...props}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const DefaultBlip = ({blip, ...props}) => {
|
||||
export const DefaultBlip: React.FC<
|
||||
{blip: Blip} & VisualBlipProps
|
||||
> = ({blip, ...props}) => {
|
||||
return (
|
||||
<circle
|
||||
r={chartConfig.blipSize / 2}
|
||||
cx={blip.x}
|
||||
cy={blip.y}
|
||||
cx={blip.coordinates.x}
|
||||
cy={blip.coordinates.y}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import * as d3 from 'd3';
|
||||
import { chartConfig } from '../../config';
|
||||
import { QuadrantConfig } from '../../model';
|
||||
|
||||
const arcPath = (quadrantPosition, ringPosition, xScale) => {
|
||||
function arcPath(quadrantPosition: number, ringPosition: number, xScale: d3.ScaleLinear) {
|
||||
const startAngle = quadrantPosition == 1 ?
|
||||
3 * Math.PI / 2
|
||||
: (quadrantPosition - 2) * Math.PI / 2
|
||||
@@ -21,7 +22,10 @@ const arcPath = (quadrantPosition, ringPosition, xScale) => {
|
||||
});
|
||||
}
|
||||
|
||||
export default function QuadrantRings ({ quadrant, xScale}) {
|
||||
const QuadrantRings: React.FC<{
|
||||
quadrant: QuadrantConfig
|
||||
xScale: d3.ScaleLinear
|
||||
}> = ({ quadrant, xScale}) => {
|
||||
// order from top-right clockwise
|
||||
const gradientAttributes = [
|
||||
{x: 0, y: 0, cx: 1, cy: 1, r: 1},
|
||||
@@ -64,4 +68,6 @@ export default function QuadrantRings ({ quadrant, xScale}) {
|
||||
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default QuadrantRings;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import * as d3 from "d3";
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import { Ring } from '../../model';
|
||||
import { Item, Ring } from '../../model';
|
||||
import { chartConfig, quadrantsMap } from '../../config';
|
||||
import { YAxis, XAxis } from './Axes';
|
||||
import QuadrantRings from './QuadrantRings';
|
||||
@@ -9,7 +9,11 @@ import BlipPoints from './BlipPoints';
|
||||
|
||||
import './chart.scss';
|
||||
|
||||
const RingLabel = ({ring, xScale, yScale}) => {
|
||||
const RingLabel: React.FC<{
|
||||
ring: Ring
|
||||
xScale: d3.ScaleLinear
|
||||
yScale: d3.ScaleLinear
|
||||
}> = ({ring, xScale, yScale}) => {
|
||||
const ringRadius = chartConfig.ringsAttributes[ring - 1].radius,
|
||||
previousRingRadius = ring == 1 ? 0 : chartConfig.ringsAttributes[ring - 2].radius,
|
||||
|
||||
@@ -30,7 +34,10 @@ const RingLabel = ({ring, xScale, yScale}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default function RadarChart({ blips }) {
|
||||
const RadarChart: React.FC<{
|
||||
items: Item[]
|
||||
}> = ({ items }) => {
|
||||
|
||||
const xScale = d3.scaleLinear()
|
||||
.domain(chartConfig.scale)
|
||||
.range([0, chartConfig.size]);
|
||||
@@ -48,17 +55,19 @@ export default function RadarChart({ blips }) {
|
||||
<XAxis scale={xScale}/>
|
||||
</g>
|
||||
|
||||
{Object.keys(quadrantsMap).map((id, index) => (
|
||||
<QuadrantRings key={index} quadrant={quadrantsMap[id]} xScale={xScale} />
|
||||
{[...quadrantsMap.values()].map((value, index) => (
|
||||
<QuadrantRings key={index} quadrant={value} xScale={xScale} />
|
||||
))}
|
||||
|
||||
{[1, 2, 3, 4].map((ring, index) => (
|
||||
{[Ring.adopt, Ring.trial, Ring.assess, Ring.hold].map((ring, index) => (
|
||||
<RingLabel key={index} ring={ring} xScale={xScale} yScale={yScale} />
|
||||
))}
|
||||
|
||||
<BlipPoints blips={blips} xScale={xScale} yScale={yScale}/>
|
||||
<BlipPoints items={items} xScale={xScale} yScale={yScale}/>
|
||||
</svg>
|
||||
<ReactTooltip className="tooltip" offset={{top: -5}}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RadarChart;
|
||||
Reference in New Issue
Block a user