Add types

This commit is contained in:
Jarosław Marek
2021-05-02 00:48:43 +12:00
parent e8eff6bdab
commit b1e63528dc
18 changed files with 189 additions and 113 deletions

View File

@@ -1,11 +1,11 @@
import React, { MouseEventHandler } from 'react'; import React, { MouseEventHandler } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import './badge.scss'; import './badge.scss';
import {Ring} from "../../config"; import {Ring} from "../../model";
type BadgeProps = { type BadgeProps = {
onClick?: MouseEventHandler; onClick?: MouseEventHandler;
big?: boolean; big?: boolean;
type: 'big' | 'all' | 'empty' | Ring; type: Ring | null;
}; };
export default function Badge({ onClick, big, type, children }: React.PropsWithChildren<BadgeProps>) { export default function Badge({ onClick, big, type, children }: React.PropsWithChildren<BadgeProps>) {
@@ -13,7 +13,7 @@ export default function Badge({ onClick, big, type, children }: React.PropsWithC
return ( return (
<Comp <Comp
className={classNames('badge', `badge--${type}`, { className={classNames('badge', `badge--${type != null ? Ring[type] : ''}`, {
'badge--big': big === true, 'badge--big': big === true,
})} })}
onClick={onClick} onClick={onClick}

View File

@@ -2,7 +2,9 @@
import ReactFauxDOM from 'react-faux-dom'; import ReactFauxDOM from 'react-faux-dom';
import * as d3 from "d3"; import * as d3 from "d3";
export const YAxis = ({ scale }) => { export const YAxis: React.FC<{
scale: d3.ScaleLinear
}> = ({ scale }) => {
const el = ReactFauxDOM.createElement('g'); const el = ReactFauxDOM.createElement('g');
const axisGenerator = d3.axisLeft(scale).ticks(6); const axisGenerator = d3.axisLeft(scale).ticks(6);
@@ -15,7 +17,9 @@ export const YAxis = ({ scale }) => {
return el.toReact(); return el.toReact();
}; };
export const XAxis = ({ scale }) => { export const XAxis: React.FC<{
scale: d3.ScaleLinear
}> = ({ scale }) => {
const el = ReactFauxDOM.createElement('g'); const el = ReactFauxDOM.createElement('g');
const axisGenerator = d3.axisBottom(scale).ticks(6); const axisGenerator = d3.axisBottom(scale).ticks(6);

View File

@@ -1,5 +1,6 @@
import React from 'react'; 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 { quadrantsMap, chartConfig } from '../../config';
import Link from '../Link/Link'; import Link from '../Link/Link';
import { NewBlip, ChangedBlip, DefaultBlip } from './BlipShapes'; 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 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, const pi = Math.PI,
ringRadius = chartConfig.ringsAttributes[enrichedBlip.ringPosition - 1].radius, ringRadius = chartConfig.ringsAttributes[blip.ringPosition - 1].radius,
previousRingRadius = enrichedBlip.ringPosition == 1 ? 0 : chartConfig.ringsAttributes[enrichedBlip.ringPosition - 2].radius, previousRingRadius = blip.ringPosition == 1 ? 0 : chartConfig.ringsAttributes[blip.ringPosition - 2].radius,
ringPadding = 0.7; ringPadding = 0.7;
// radian between 0 and 90 degrees // 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. 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) 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 { return {
x: xScale(Math.cos(randomDegree + shift) * radius), x: xScale(Math.cos(randomDegree + shift) * radius),
@@ -31,54 +32,17 @@ const generateCoordinates = (enrichedBlip, xScale, yScale) => {
}; };
}; };
const randomBetween = (min, max) => { function randomBetween (min: number, max: number): number {
return Math.random() * (max - min) + min; return Math.random() * (max - min) + min;
}; };
const distanceBetween = (point1, point2) => { function distanceBetween(point1: Point, point2: Point): number {
const a = point2.x - point1.x; const a = point2.x - point1.x;
const b = point2.y - point1.y; const b = point2.y - point1.y;
return Math.sqrt((a * a) + (b * b)); return Math.sqrt((a * a) + (b * b));
}; };
export default function BlipPoints({blips, xScale, yScale}) { function renderBlip(blip: Blip, index: number): JSX.Element {
const enrichedBlips = blips.reduce((list, blip) => {
if (!blip.ring || !blip.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
};
let point;
let counter = 1;
do {
point = generateCoordinates(enrichedBlip, 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)
and quadrant borders (so that they don't overlap quadrants)
This feels pretty inefficient, but good enough for now.
*/
} 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)
));
enrichedBlip.x = point.x;
enrichedBlip.y = point.y;
list.push(enrichedBlip);
return list;
}, []);
const renderBlip = (blip, index) => {
const props = { const props = {
blip, blip,
className: 'blip', className: 'blip',
@@ -96,11 +60,55 @@ export default function BlipPoints({blips, xScale, yScale}) {
default: default:
return <DefaultBlip {...props} />; return <DefaultBlip {...props} />;
} }
};
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;
} }
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(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)
and quadrant borders (so that they don't overlap quadrants)
This feels pretty inefficient, but good enough for now.
*/
} while (counter < 100
&& (Math.abs(point.x - xScale(0)) < 15
|| Math.abs(point.y - yScale(0)) < 15
|| list.some(b => distanceBetween(point, b.coordinates) < chartConfig.blipSize + chartConfig.blipSize / 2)
));
blip.coordinates = point;
list.push(blip);
return list;
}, []);
return ( return (
<g className="blips"> <g className="blips">
{enrichedBlips.map((blip, index) => ( {blips.map((blip, index) => (
<Link pageName={`${blip.quadrant}/${blip.name}`} key={index}> <Link pageName={`${blip.quadrant}/${blip.name}`} key={index}>
{renderBlip(blip, index)} {renderBlip(blip, index)}
</Link> </Link>
@@ -108,3 +116,5 @@ export default function BlipPoints({blips, xScale, yScale}) {
</g> </g>
); );
}; };
export default BlipPoints;

View File

@@ -1,9 +1,21 @@
import React from 'react'; import React from 'react';
import { Blip } from '../../model';
import { chartConfig } from '../../config'; import { chartConfig } from '../../config';
export const ChangedBlip = ({blip, ...props}) => { type VisualBlipProps = {
const centeredX = blip.x - chartConfig.blipSize/2, className: string,
centeredY = blip.y - chartConfig.blipSize/2; 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 ( return (
<rect <rect
@@ -18,9 +30,11 @@ export const ChangedBlip = ({blip, ...props}) => {
); );
}; };
export const NewBlip = ({blip, ...props}) => { export const NewBlip: React.FC<
const centeredX = blip.x - chartConfig.blipSize/2, {blip: Blip} & VisualBlipProps
centeredY = blip.y - chartConfig.blipSize/2; > = ({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. 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 ( return (
<circle <circle
r={chartConfig.blipSize / 2} r={chartConfig.blipSize / 2}
cx={blip.x} cx={blip.coordinates.x}
cy={blip.y} cy={blip.coordinates.y}
{...props} {...props}
/> />
); );

View File

@@ -1,8 +1,9 @@
import React from 'react'; import React from 'react';
import * as d3 from 'd3'; import * as d3 from 'd3';
import { chartConfig } from '../../config'; 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 ? const startAngle = quadrantPosition == 1 ?
3 * Math.PI / 2 3 * Math.PI / 2
: (quadrantPosition - 2) * 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 // order from top-right clockwise
const gradientAttributes = [ const gradientAttributes = [
{x: 0, y: 0, cx: 1, cy: 1, r: 1}, {x: 0, y: 0, cx: 1, cy: 1, r: 1},
@@ -65,3 +69,5 @@ export default function QuadrantRings ({ quadrant, xScale}) {
</g> </g>
); );
} }
export default QuadrantRings;

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import * as d3 from "d3"; import * as d3 from "d3";
import ReactTooltip from 'react-tooltip'; import ReactTooltip from 'react-tooltip';
import { Ring } from '../../model'; import { Item, Ring } from '../../model';
import { chartConfig, quadrantsMap } from '../../config'; import { chartConfig, quadrantsMap } from '../../config';
import { YAxis, XAxis } from './Axes'; import { YAxis, XAxis } from './Axes';
import QuadrantRings from './QuadrantRings'; import QuadrantRings from './QuadrantRings';
@@ -9,7 +9,11 @@ import BlipPoints from './BlipPoints';
import './chart.scss'; 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, const ringRadius = chartConfig.ringsAttributes[ring - 1].radius,
previousRingRadius = ring == 1 ? 0 : chartConfig.ringsAttributes[ring - 2].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() const xScale = d3.scaleLinear()
.domain(chartConfig.scale) .domain(chartConfig.scale)
.range([0, chartConfig.size]); .range([0, chartConfig.size]);
@@ -48,17 +55,19 @@ export default function RadarChart({ blips }) {
<XAxis scale={xScale}/> <XAxis scale={xScale}/>
</g> </g>
{Object.keys(quadrantsMap).map((id, index) => ( {[...quadrantsMap.values()].map((value, index) => (
<QuadrantRings key={index} quadrant={quadrantsMap[id]} xScale={xScale} /> <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} /> <RingLabel key={index} ring={ring} xScale={xScale} yScale={yScale} />
))} ))}
<BlipPoints blips={blips} xScale={xScale} yScale={yScale}/> <BlipPoints items={items} xScale={xScale} yScale={yScale}/>
</svg> </svg>
<ReactTooltip className="tooltip" offset={{top: -5}}/> <ReactTooltip className="tooltip" offset={{top: -5}}/>
</div> </div>
); );
} }
export default RadarChart;

View File

@@ -19,7 +19,9 @@ type PageIndexProps = {
export default function PageIndex({ leaving, onLeave, items, releases }: PageIndexProps) { export default function PageIndex({ leaving, onLeave, items, releases }: PageIndexProps) {
const newestRelease = releases.slice(-1)[0]; const newestRelease = releases.slice(-1)[0];
const numberOfReleases = releases.length; const numberOfReleases = releases.length;
// @ts-ignore
const showChart = homepageContent === HomepageOption.chart || homepageContent === HomepageOption.both; const showChart = homepageContent === HomepageOption.chart || homepageContent === HomepageOption.both;
// @ts-ignore
const showColumns = homepageContent === HomepageOption.columns || homepageContent === HomepageOption.both; const showColumns = homepageContent === HomepageOption.columns || homepageContent === HomepageOption.both;
return ( return (
<Fadeable leaving={leaving} onLeave={onLeave}> <Fadeable leaving={leaving} onLeave={onLeave}>
@@ -28,7 +30,7 @@ export default function PageIndex({ leaving, onLeave, items, releases }: PageInd
<HeroHeadline alt={`Version #${numberOfReleases}`}>{radarName}</HeroHeadline> <HeroHeadline alt={`Version #${numberOfReleases}`}>{radarName}</HeroHeadline>
</div> </div>
{showChart && ( {showChart && (
<RadarGrid blips={featuredOnly(items)} /> <RadarGrid items={featuredOnly(items)} />
)} )}
{showColumns && ( {showColumns && (
<QuadrantGrid items={featuredOnly(items)} /> <QuadrantGrid items={featuredOnly(items)} />

View File

@@ -191,7 +191,7 @@ export default function PageItem({pageName, items, leaving, onLeave}: PageItemPr
<div className='item-page__nav'> <div className='item-page__nav'>
<div className='item-page__nav__inner'> <div className='item-page__nav__inner'>
<div className='item-page__header' style={getAnimationState('navHeader')}> <div className='item-page__header' style={getAnimationState('navHeader')}>
<h3 className='headline'>{quadrantsMap[item.quadrant].displayName}</h3> <h3 className='headline'>{quadrantsMap.get(item.quadrant).displayName}</h3>
</div> </div>
<ItemList items={itemsInRing} activeItem={item} headerStyle={getAnimationState('navHeader')} <ItemList items={itemsInRing} activeItem={item} headerStyle={getAnimationState('navHeader')}

View File

@@ -40,7 +40,7 @@ export default function PageItemMobile({ pageName, items, leaving, onLeave }: Pa
<div className='mobile-item-page__header'> <div className='mobile-item-page__header'>
<div className='split'> <div className='split'>
<div className='split__left'> <div className='split__left'>
<h3 className='headline'>{quadrantsMap[item.quadrant].displayName}</h3> <h3 className='headline'>{quadrantsMap.get(item.quadrant).displayName}</h3>
<h1 className='hero-headline hero-headline--inverse'>{item.title}</h1> <h1 className='hero-headline hero-headline--inverse'>{item.title}</h1>
</div> </div>
<div className='split__right'> <div className='split__right'>
@@ -59,7 +59,7 @@ export default function PageItemMobile({ pageName, items, leaving, onLeave }: Pa
<ItemList items={itemsInRing} activeItem={item}> <ItemList items={itemsInRing} activeItem={item}>
<div className='split'> <div className='split'>
<div className='split__left'> <div className='split__left'>
<h3 className='headline'>{quadrantsMap[item.quadrant].displayName}</h3> <h3 className='headline'>{quadrantsMap.get(item.quadrant).displayName}</h3>
</div> </div>
<div className='split__right'> <div className='split__right'>
<IconLink pageName={item.quadrant} icon="pie" text="Zoom in" /> <IconLink pageName={item.quadrant} icon="pie" text="Zoom in" />

View File

@@ -16,7 +16,7 @@ const containsSearchTerm = (text = '', term = '') => {
}; };
type PageOverviewProps = { type PageOverviewProps = {
rings: readonly ('all' | Ring)[]; rings: readonly Ring[];
search: string; search: string;
items: Item[]; items: Item[];
leaving: boolean; leaving: boolean;
@@ -24,13 +24,10 @@ type PageOverviewProps = {
}; };
export default function PageOverview({ rings, search: searchProp, items, leaving, onLeave }: PageOverviewProps) { export default function PageOverview({ rings, search: searchProp, items, leaving, onLeave }: PageOverviewProps) {
const [ring, setRing] = useState<Ring | 'all'>('all'); const [selectedRing, setRing] = useState<Ring>(Ring.all);
const [search, setSearch] = useState(searchProp); const [search, setSearch] = useState(searchProp);
useEffect(() => { useEffect(() => {
if (rings.length > 0) {
setRing(rings[0]);
}
setSearch(searchProp); setSearch(searchProp);
}, [rings, searchProp]); }, [rings, searchProp]);
@@ -38,9 +35,10 @@ export default function PageOverview({ rings, search: searchProp, items, leaving
setRing(ring); setRing(ring);
}; };
const isRingActive = (ringName: string) => ring === ringName; const isRingActive = (ring: Ring) => selectedRing === ring;
const itemMatchesRing = (item: Item) => ring === 'all' || item.ring === ring; // TODO get to the bottom of this
const itemMatchesRing = (item: Item) => selectedRing === Ring.all || Ring[item.ring] === selectedRing;
const itemMatchesSearch = (item: Item) => { const itemMatchesSearch = (item: Item) => {
return search.trim() === '' || containsSearchTerm(item.title, search) || containsSearchTerm(item.body, search) || containsSearchTerm(item.info, search); return search.trim() === '' || containsSearchTerm(item.title, search) || containsSearchTerm(item.body, search) || containsSearchTerm(item.info, search);
@@ -75,10 +73,10 @@ export default function PageOverview({ rings, search: searchProp, items, leaving
</div> </div>
<div className='split__right'> <div className='split__right'>
<div className='nav'> <div className='nav'>
{Object.keys(rings).map((key) => ( {rings.map((ring) => (
<div className='nav__item' key={Ring[key]}> <div className='nav__item' key={ring}>
<Badge big onClick={handleRingClick(Ring[key])} type={isRingActive(Ring[key]) ? Ring[key] : 'empty'}> <Badge big onClick={handleRingClick(ring)} type={isRingActive(ring) ? ring : null}>
{Ring[key]} {Ring[ring]}
</Badge> </Badge>
</div> </div>
))} ))}
@@ -109,9 +107,10 @@ export default function PageOverview({ rings, search: searchProp, items, leaving
</div> </div>
<div className='split__right'> <div className='split__right'>
<div className='nav nav--relations'> <div className='nav nav--relations'>
<div className='nav__item'>{quadrantsMap[item.quadrant].displayName}</div> <div className='nav__item'>{quadrantsMap.get(item.quadrant).displayName}</div>
<div className='nav__item'> <div className='nav__item'>
<Badge type={item.ring}>{item.ring}</Badge> {/* TODO get to the bottom of this */}
<Badge type={Ring[item.ring]}>{item.ring}</Badge>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -19,9 +19,9 @@ export default function PageQuadrant({ leaving, onLeave, pageName, items }: Page
const groups = groupByQuadrants(featuredOnly(items)); const groups = groupByQuadrants(featuredOnly(items));
return ( return (
<Fadeable leaving={leaving} onLeave={onLeave}> <Fadeable leaving={leaving} onLeave={onLeave}>
<SetTitle title={quadrantsMap[pageName].displayName} /> <SetTitle title={quadrantsMap.get(pageName).displayName} />
<HeadlineGroup> <HeadlineGroup>
<HeroHeadline>{quadrantsMap[pageName].displayName}</HeroHeadline> <HeroHeadline>{quadrantsMap.get(pageName).displayName}</HeroHeadline>
</HeadlineGroup> </HeadlineGroup>
<QuadrantSection groups={groups} quadrantName={pageName} big showTitle={false} /> <QuadrantSection groups={groups} quadrantName={pageName} big showTitle={false} />
</Fadeable> </Fadeable>

View File

@@ -13,5 +13,5 @@ const renderQuadrant = (quadrantName: string, groups: Group) => {
export default function QuadrantGrid({ items }: { items: Item[] }) { export default function QuadrantGrid({ items }: { items: Item[] }) {
const groups = groupByQuadrants(items); const groups = groupByQuadrants(items);
return <div className='quadrant-grid'>{Object.keys(quadrantsMap).map((quadrantName) => renderQuadrant(quadrantName, groups))}</div>; return <div className='quadrant-grid'>{[...quadrantsMap.keys()].map((quadrantName) => renderQuadrant(quadrantName, groups))}</div>;
} }

View File

@@ -55,7 +55,7 @@ export default function QuadrantSection({ quadrantName, groups, big = false, sho
<div className='split'> <div className='split'>
{showTitle && ( {showTitle && (
<div className="split__left"> <div className="split__left">
<h4 className="headline">{quadrantsMap[quadrantName].displayName}</h4> <h4 className="headline">{quadrantsMap.get(quadrantName).displayName}</h4>
</div> </div>
)} )}
{!big && ( {!big && (

View File

@@ -2,10 +2,14 @@ import React from 'react';
import RadarChart from '../Chart/RadarChart'; import RadarChart from '../Chart/RadarChart';
import IconLink from '../IconLink/IconLink'; import IconLink from '../IconLink/IconLink';
import { quadrantsMap } from '../../config'; import { quadrantsMap } from '../../config';
import { Item, QuadrantConfig } from '../../model';
import './radar-grid.scss'; import './radar-grid.scss';
const QuadrantLabel = ({quadrant}) => { const QuadrantLabel: React.FC<{
quadrant: QuadrantConfig
}> = ({quadrant}) => {
const stylesMap = [ const stylesMap = [
{top: 0, left: 0}, {top: 0, left: 0},
{top: 0, right: 0}, {top: 0, right: 0},
@@ -30,7 +34,7 @@ const QuadrantLabel = ({quadrant}) => {
); );
}; };
const Legend = () => { const Legend: React.FC = () => {
return ( return (
<div className="radar-legend"> <div className="radar-legend">
<div className="wrapper"> <div className="wrapper">
@@ -49,15 +53,19 @@ const Legend = () => {
); );
} }
export default function RadarGrid({ blips }) { const RadarGrid: React.FC<
{items: Item[]}
> = ({ items }) => {
return ( return (
<div className="radar-grid"> <div className="radar-grid">
<RadarChart blips={blips} /> <RadarChart items={items} />
{Object.keys(quadrantsMap).map((id, index) => ( {[...quadrantsMap.values()].map((value, index) => (
<QuadrantLabel key={index} quadrant={quadrantsMap[id]} /> <QuadrantLabel key={index} quadrant={value} />
))} ))}
<Legend /> <Legend />
</div> </div>
); );
} }
export default RadarGrid;

View File

@@ -35,7 +35,7 @@ const getPageByName = (items: Item[], pageName: string): page => {
if (pageName === 'help-and-about-tech-radar') { if (pageName === 'help-and-about-tech-radar') {
return page.help; return page.help;
} }
if (Object.keys(quadrantsMap).includes(pageName)) { if (quadrantsMap.has(pageName)) {
return page.quadrant; return page.quadrant;
} }
if (getItemPageNames(items).includes(pageName)) { if (getItemPageNames(items).includes(pageName)) {
@@ -75,7 +75,7 @@ export default function Router({pageName, items, releases, search}: RouterProps)
case page.index: case page.index:
return <PageIndex leaving={leaving} items={items} onLeave={handlePageLeave} releases={releases}/>; return <PageIndex leaving={leaving} items={items} onLeave={handlePageLeave} releases={releases}/>;
case page.overview: case page.overview:
return <PageOverview items={items} rings={Object.values(Ring)} search={search} leaving={leaving} return <PageOverview items={items} rings={[Ring.all, Ring.adopt, Ring.assess, Ring.hold, Ring.trial]} search={search} leaving={leaving}
onLeave={handlePageLeave}/>; onLeave={handlePageLeave}/>;
case page.help: case page.help:
return <PageHelp leaving={leaving} onLeave={handlePageLeave}/>; return <PageHelp leaving={leaving} onLeave={handlePageLeave}/>;

View File

@@ -1,4 +1,4 @@
import {Item, HomepageOption} from './model'; import {Item, HomepageOption, QuadrantConfig} from './model';
export const radarName = process.env.RADAR_NAME || 'AOE Technology Radar' export const radarName = process.env.RADAR_NAME || 'AOE Technology Radar'
export const radarNameShort = radarName; export const radarNameShort = radarName;
@@ -6,40 +6,40 @@ export const radarNameShort = radarName;
export const homepageContent = HomepageOption.both; // by defaul show both versions so that people can choose which one they like more (or keep both) export const homepageContent = HomepageOption.both; // by defaul show both versions so that people can choose which one they like more (or keep both)
// Quadrants positions start from the top left and go clockwise // Quadrants positions start from the top left and go clockwise
export const quadrantsMap = { export const quadrantsMap: Map<string, QuadrantConfig> = new Map([
'languages-and-frameworks': { ['languages-and-frameworks', {
id: 'languages-and-frameworks', id: 'languages-and-frameworks',
displayName: 'Languages & Frameworks', displayName: 'Languages & Frameworks',
colour: '#84BFA4', colour: '#84BFA4',
txtColour: '#444444', txtColour: '#444444',
position: 1, position: 1,
description: "We've placed development languages (such as Scala or Golang) here, as well as more low-level development frameworks (such as Play or Symfony), which are useful for implementing custom software of all kinds." description: "We've placed development languages (such as Scala or Golang) here, as well as more low-level development frameworks (such as Play or Symfony), which are useful for implementing custom software of all kinds."
}, }],
'methods-and-patterns': { ['methods-and-patterns', {
id: 'methods-and-patterns', id: 'methods-and-patterns',
displayName: 'Methods & Patterns', displayName: 'Methods & Patterns',
colour: '#248EA6', colour: '#248EA6',
txtColour: 'white', txtColour: 'white',
position: 2, position: 2,
description: 'Here we put information on methods and patterns concerning development, continuous x, testing, organization, architecture, etc.' description: 'Here we put information on methods and patterns concerning development, continuous x, testing, organization, architecture, etc.'
}, }],
'platforms-and-aoe-services': { ['platforms-and-aoe-services', {
id: 'platforms-and-aoe-services', id: 'platforms-and-aoe-services',
displayName: 'Platforms and Operations', displayName: 'Platforms and Operations',
colour: '#F25244', colour: '#F25244',
txtColour: '#444444', txtColour: '#444444',
position: 3, position: 3,
description: 'Here we include infrastructure platforms and services. We also use this category to communicate news about AOE services that we want all AOE teams to be aware of.' description: 'Here we include infrastructure platforms and services. We also use this category to communicate news about AOE services that we want all AOE teams to be aware of.'
}, }],
'tools': { ['tools', {
id: 'tools', id: 'tools',
displayName: 'Tools', displayName: 'Tools',
colour: '#F2A25C', colour: '#F2A25C',
txtColour: 'white', txtColour: 'white',
position: 4, position: 4,
description: 'Here we put different software tools - from small helpers to bigger software projects' description: 'Here we put different software tools - from small helpers to bigger software projects'
} }]
}; ]);
export const chartConfig = { export const chartConfig = {
size: 800, //in px size: 800, //in px

View File

@@ -34,6 +34,14 @@ export type Item = ItemAttributes & {
revisions: Revision[] revisions: Revision[]
} }
export type Blip = Item & {
quadrantPosition: number
ringPosition: number
colour: string
txtColour: string
coordinates?: Point
}
export type Revision = ItemAttributes & { export type Revision = ItemAttributes & {
body: string body: string
fileName: string fileName: string
@@ -44,6 +52,15 @@ export type Quadrant = {
[name: string]: Item[] [name: string]: Item[]
} }
export type QuadrantConfig = {
id: string,
displayName: string,
colour: string,
txtColour: string,
position: number,
description: string
}
export type Radar = { export type Radar = {
items: Item[] items: Item[]
releases: string[] releases: string[]
@@ -53,6 +70,11 @@ export type Group = {
[quadrant: string]: Quadrant [quadrant: string]: Quadrant
} }
export type Point = {
x: number,
y: number
}
export const featuredOnly = (items: Item[]) => items.filter(item => item.featured); export const featuredOnly = (items: Item[]) => items.filter(item => item.featured);
export const groupByQuadrants = (items: Item[]): Group => export const groupByQuadrants = (items: Item[]): Group =>

View File

@@ -10,12 +10,12 @@ import {quadrantsMap} from "../src/config";
console.log('starting static') console.log('starting static')
const radar = await createRadar(); const radar = await createRadar();
copyFileSync('build/index.html', 'build/overview.html') copyFileSync('build/index.html', 'build/overview.html');
copyFileSync('build/index.html', 'build/help-and-about-tech-radar.html') copyFileSync('build/index.html', 'build/help-and-about-tech-radar.html');
Object.keys(quadrantsMap).forEach(quadrant => { [...quadrantsMap.keys()].forEach(quadrant => {
copyFileSync('build/index.html', 'build/' + quadrant + '.html') copyFileSync('build/index.html', 'build/' + quadrant + '.html');
mkdirSync('build/' + quadrant) mkdirSync('build/' + quadrant);
}) })
radar.items.forEach(item => { radar.items.forEach(item => {
copyFileSync('build/index.html', 'build/' + item.quadrant + '/' + item.name + '.html') copyFileSync('build/index.html', 'build/' + item.quadrant + '/' + item.name + '.html')