Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
234 lines (221 sloc) 6.68 KB
// Copyright (c) 2016 - 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import React from 'react';
import PropTypes from 'prop-types';
import {pie as pieBuilder} from 'd3-shape';
import {AnimationPropType} from 'animation';
import ArcSeries from 'plot/series/arc-series';
import LabelSeries from 'plot/series/label-series';
import XYPlot from 'plot/xy-plot';
import {DISCRETE_COLOR_RANGE} from 'theme';
import {MarginPropType, getRadialLayoutMargin} from 'utils/chart-utils';
import {getRadialDomain} from 'utils/series-utils';
import {getCombinedClassName} from 'utils/styling-utils';
const predefinedClassName = 'rv-radial-chart';
const DEFAULT_RADIUS_MARGIN = 15;
/**
* Create the list of wedges to render.
* @param {Object} props
props.data {Object} - tree structured data (each node has a name anc an array of children)
* @returns {Array} Array of nodes.
*/
function getWedgesToRender({data, getAngle}) {
const pie = pieBuilder()
.sort(null)
.value(getAngle);
const pieData = pie(data).reverse();
return pieData.map((row, index) => {
return {
...row.data,
angle0: row.startAngle,
angle: row.endAngle,
radius0: row.data.innerRadius || 0,
radius: row.data.radius || 1,
color: row.data.color || index
};
});
}
function generateLabels(mappedData, accessors, labelsRadiusMultiplier = 1.1) {
const {getLabel, getSubLabel} = accessors;
return mappedData.reduce((res, row) => {
const {angle, angle0, radius} = row;
const centeredAngle = (angle + angle0) / 2;
// unfortunate, but true fact: d3 starts its radians at 12 oclock rather than 3
// and move clockwise rather than counter clockwise. why why why!
const updatedAngle = -1 * centeredAngle + Math.PI / 2;
const newLabels = [];
if (getLabel(row)) {
newLabels.push({
angle: updatedAngle,
radius: radius * labelsRadiusMultiplier,
label: getLabel(row)
});
}
if (getSubLabel(row)) {
newLabels.push({
angle: updatedAngle,
radius: radius * labelsRadiusMultiplier,
label: getSubLabel(row),
style: {fontSize: 10},
yOffset: 12
});
}
return res.concat(newLabels);
}, []);
// could add force direction here to make sure the labels dont overlap
}
/**
* Get the max radius so the chart can extend to the margin.
* @param {Number} width - container width
* @param {Number} height - container height
* @return {Number} radius
*/
function getMaxRadius(width, height) {
return Math.min(width, height) / 2 - DEFAULT_RADIUS_MARGIN;
}
function RadialChart(props) {
const {
animation,
className,
children,
colorType,
data,
getAngle,
getLabel,
getSubLabel,
height,
hideRootNode,
innerRadius,
labelsAboveChildren,
labelsRadiusMultiplier,
labelsStyle,
margin,
onMouseLeave,
onMouseEnter,
radius,
showLabels,
style,
width
} = props;
const mappedData = getWedgesToRender({
data,
height,
hideRootNode,
width,
getAngle
});
const radialDomain = getRadialDomain(mappedData);
const arcProps = {
colorType,
...props,
animation,
radiusDomain: [0, radialDomain],
data: mappedData,
radiusNoFallBack: true,
style,
arcClassName: 'rv-radial-chart__series--pie__slice'
};
if (radius) {
arcProps.radiusDomain = [0, 1];
arcProps.radiusRange = [innerRadius || 0, radius];
arcProps.radiusType = 'linear';
}
const maxRadius = radius ? radius : getMaxRadius(width, height);
const defaultMargin = getRadialLayoutMargin(width, height, maxRadius);
const labels = generateLabels(
mappedData,
{
getLabel,
getSubLabel
},
labelsRadiusMultiplier
);
return (
<XYPlot
height={height}
width={width}
margin={{
...defaultMargin,
...margin
}}
className={getCombinedClassName(className, predefinedClassName)}
onMouseLeave={onMouseLeave}
onMouseEnter={onMouseEnter}
xDomain={[-radialDomain, radialDomain]}
yDomain={[-radialDomain, radialDomain]}
>
<ArcSeries {...arcProps} getAngle={d => d.angle} />
{showLabels &&
!labelsAboveChildren && (
<LabelSeries data={labels} style={labelsStyle} />
)}
{children}
{showLabels &&
labelsAboveChildren && (
<LabelSeries data={labels} style={labelsStyle} />
)}
</XYPlot>
);
}
RadialChart.displayName = 'RadialChart';
RadialChart.propTypes = {
animation: AnimationPropType,
className: PropTypes.string,
colorType: PropTypes.string,
data: PropTypes.arrayOf(
PropTypes.shape({
angle: PropTypes.number,
className: PropTypes.string,
label: PropTypes.string,
radius: PropTypes.number,
style: PropTypes.object
})
).isRequired,
getAngle: PropTypes.func,
getAngle0: PropTypes.func,
padAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number]),
getRadius: PropTypes.func,
getRadius0: PropTypes.func,
getLabel: PropTypes.func,
height: PropTypes.number.isRequired,
labelsAboveChildren: PropTypes.bool,
labelsStyle: PropTypes.object,
margin: MarginPropType,
onValueClick: PropTypes.func,
onValueMouseOver: PropTypes.func,
onValueMouseOut: PropTypes.func,
showLabels: PropTypes.bool,
style: PropTypes.object,
subLabel: PropTypes.func,
width: PropTypes.number.isRequired
};
RadialChart.defaultProps = {
className: '',
colorType: 'category',
colorRange: DISCRETE_COLOR_RANGE,
padAngle: 0,
getAngle: d => d.angle,
getAngle0: d => d.angle0,
getRadius: d => d.radius,
getRadius0: d => d.radius0,
getLabel: d => d.label,
getSubLabel: d => d.subLabel
};
export default RadialChart;