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,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; 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 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) => { const BlipPoints: React.FC<{
if (!blip.ring || !blip.quadrant) { 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 // skip the blip if it doesn't have a ring or quadrant assigned
return list; return list;
} }
let enrichedBlip = { ...blip, const quadrantConfig = quadrantsMap.get(item.quadrant);
quadrantPosition: quadrantsMap[blip.quadrant].position,
ringPosition: Ring[blip.ring], let blip: Blip = { ...item,
colour: quadrantsMap[blip.quadrant].colour, quadrantPosition: quadrantConfig.position,
txtColour: quadrantsMap[blip.quadrant].txtColour // TODO get to the bottom of this
// @ts-ignore
ringPosition: Ring[item.ring],
colour: quadrantConfig.colour,
txtColour: quadrantConfig.txtColour
}; };
let point; let point;
let counter = 1; let counter = 1;
do { do {
point = generateCoordinates(enrichedBlip, xScale, yScale); point = generateCoordinates(blip, xScale, yScale);
counter++; 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) 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 } while (counter < 100
&& (Math.abs(point.x - xScale(0)) < 15 && (Math.abs(point.x - xScale(0)) < 15
|| Math.abs(point.y - yScale(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; blip.coordinates = point;
enrichedBlip.y = point.y;
list.push(enrichedBlip); list.push(blip);
return list; 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 ( 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>
))} ))}
</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},
@@ -64,4 +68,6 @@ 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')