Add types
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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)} />
|
||||||
|
|||||||
@@ -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')}
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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}/>;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
22
src/model.ts
22
src/model.ts
@@ -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 =>
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user