@smartive/react-d3-radar
Version:
React-based Radar chart for D3
193 lines (179 loc) • 5.36 kB
Flow
// @flow
import React, {Component} from 'react';
import type {TickScale, RadarPoint, RadarVariable} from './types';
import RadarAxis from './RadarAxis';
import RadarCircle from './RadarCircle';
import RadarRings from './RadarRings';
import RadarSector from './RadarSector';
type Props = {
variables: Array<RadarVariable>,
width: number,
height: number,
padding: number,
domainMax: number,
style?: {},
onHover?: (point: RadarPoint | null) => void,
highlighted: ?RadarPoint,
scales: {[variableKey: string]: TickScale},
backgroundScale: TickScale,
offsetAngles: {[variableKey: string]: number},
voronoiDiagram: any,
radius: number,
highlightedPoint: ?{setKey: string, points: Array<RadarPoint>},
regularPoints: Array<{setKey: string, points: Array<RadarPoint>}>,
colors: {[setKey: string]: string},
};
const defaultRadarStyle = {
numRings: 4,
axisColor: '#cdcdcd',
ringColor: '#cdcdcd',
};
function getHovered(
event: MouseEvent,
height: number,
width: number,
padding: number,
radius: number,
voronoiDiagram: any,
) {
const innerHeight = height - padding * 2;
const innerWidth = width - padding * 2;
const diameter = radius * 2;
let {offsetX: clientX, offsetY: clientY} = event;
clientX -= padding;
clientY -= padding;
clientX -= (innerWidth - diameter) / 2;
clientY -= (innerHeight - diameter) / 2;
const site = voronoiDiagram.find(clientX, clientY, radius / 2);
if (!site) {
return null;
}
const {data} = site;
return data;
}
export default class RadarWrapper extends Component {
props: Props;
hoverMap = null;
componentDidMount() {
if (this.hoverMap) {
this.hoverMap.addEventListener('mousemove', (event: MouseEvent) => {
const {onHover} = this.props;
if (!onHover) {
return;
}
const {padding, height, width, radius, voronoiDiagram} = this.props;
onHover(
getHovered(event, height, width, padding, radius, voronoiDiagram),
);
});
}
}
render() {
const {
width,
height,
padding,
radius,
style,
highlighted,
scales,
variables,
offsetAngles,
domainMax,
highlightedPoint,
regularPoints,
backgroundScale,
colors,
} = this.props;
const diameter = radius * 2;
const {axisColor, ringColor, numRings} = {...defaultRadarStyle, ...style};
const innerHeight = height - padding * 2;
const innerWidth = width - padding * 2;
const ticks = backgroundScale.ticks(numRings).slice(1);
const tickFormat = backgroundScale.tickFormat(numRings);
return (
<svg width={width} height={height}>
<g
transform={`translate(${padding}, ${padding})`}
ref={c => {
this.hoverMap = c;
}}
>
<rect
width={diameter}
height={diameter}
fill={'transparent'}
transform={
`translate(${(innerWidth - diameter) / 2}, ${(innerHeight -
diameter) /
2})`
}
/>
<g transform={`translate(${innerWidth / 2}, ${innerHeight / 2})`}>
{variables.map(({key, color}, i) => {
if (color) {
return (
<RadarSector
key={key}
scale={backgroundScale}
offsetAngle={-Math.PI / 2 + offsetAngles[key] - Math.PI / variables.length}
endAngle={-Math.PI / 2 + offsetAngles[variables[(i + 1) % variables.length].key] - Math.PI / variables.length}
color={color}
domainMax={domainMax}
/>
)
}
})}
<RadarRings
ticks={ticks}
scale={backgroundScale}
color={ringColor}
format={tickFormat}
/>
{variables.map(({key, label}) => {
return (
<RadarAxis
key={key}
scale={scales[key]}
offsetAngle={offsetAngles[key]}
label={label}
domainMax={domainMax}
color={axisColor}
style={style}
/>
);
})}
{regularPoints.map(({setKey, points}) => {
return (
<RadarCircle
key={setKey}
points={points}
scales={scales}
offsetAngles={offsetAngles}
color={colors[setKey]}
isSelected={false}
selectedVariableKey={null}
/>
);
})}
{
highlightedPoint
? <RadarCircle
key={highlightedPoint.setKey}
points={highlightedPoint.points}
scales={scales}
offsetAngles={offsetAngles}
color={colors[highlightedPoint.setKey]}
isSelected={true}
selectedVariableKey={
highlighted ? highlighted.variableKey : null
}
/>
: null
}
</g>
</g>
</svg>
);
}
}