Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
248 lines (232 sloc) 7.69 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 Animation from 'animation';
import {arc as arcBuilder} from 'd3-shape';
import {ANIMATED_SERIES_PROPS} from 'utils/series-utils';
import AbstractSeries from './abstract-series';
import {
getAttributeFunctor,
getAttr0Functor,
extractScalePropsFromProps,
getMissingScaleProps,
getScalePropTypesByAttribute
} from 'utils/scales-utils';
import {getCombinedClassName} from 'utils/styling-utils';
const predefinedClassName = 'rv-xy-plot__series rv-xy-plot__series--arc';
const ATTRIBUTES = ['radius', 'angle'];
const defaultProps = {
...AbstractSeries.defaultProps,
center: {x: 0, y: 0},
arcClassName: '',
className: '',
style: {},
padAngle: 0
};
/**
* Prepare the internal representation of row for real use.
* This is necessary because d3 insists on starting at 12 oclock and moving
* clockwise, rather than starting at 3 oclock and moving counter clockwise
* as one might expect from polar
* @param {Object} row - coordinate object to be modifed
* @return {Object} angle corrected object
*/
function modifyRow(row) {
const {radius, angle, angle0} = row;
const truedAngle = -1 * angle + Math.PI / 2;
const truedAngle0 = -1 * angle0 + Math.PI / 2;
return {
...row,
x: radius * Math.cos(truedAngle),
y: radius * Math.sin(truedAngle),
angle: truedAngle,
angle0: truedAngle0
};
}
class ArcSeries extends AbstractSeries {
constructor(props) {
super(props);
const scaleProps = this._getAllScaleProps(props);
this.state = {scaleProps};
}
componentWillReceiveProps(nextProps) {
this.setState({scaleProps: this._getAllScaleProps(nextProps)});
}
/**
* Get the map of scales from the props.
* @param {Object} props Props.
* @param {Array} data Array of all data.
* @returns {Object} Map of scales.
* @private
*/
_getAllScaleProps(props) {
const defaultScaleProps = this._getDefaultScaleProps(props);
const userScaleProps = extractScalePropsFromProps(props, ATTRIBUTES);
const missingScaleProps = getMissingScaleProps(
{
...defaultScaleProps,
...userScaleProps
},
props.data,
ATTRIBUTES
);
return {
...defaultScaleProps,
...userScaleProps,
...missingScaleProps
};
}
/**
* Get the list of scale-related settings that should be applied by default.
* @param {Object} props Object of props.
* @returns {Object} Defaults.
* @private
*/
_getDefaultScaleProps(props) {
const {innerWidth, innerHeight} = props;
const radius = Math.min(innerWidth / 2, innerHeight / 2);
return {
radiusRange: [0, radius],
_radiusValue: radius,
angleType: 'literal'
};
}
render() {
const {
arcClassName,
animation,
className,
center,
data,
disableSeries,
hideSeries,
marginLeft,
marginTop,
padAngle,
style
} = this.props;
if (!data) {
return null;
}
if (animation) {
const cloneData = data.map(d => ({...d}));
return (
<g className="rv-xy-plot__series--arc__animation-wrapper">
<Animation
{...this.props}
animatedProps={ANIMATED_SERIES_PROPS}
data={cloneData}
>
<ArcSeries
{...this.props}
animation={null}
disableSeries={true}
data={cloneData}
/>
</Animation>
<ArcSeries
{...this.props}
animation={null}
hideSeries
style={{stroke: 'red'}}
/>
</g>
);
}
const {scaleProps} = this.state;
const {radiusDomain} = scaleProps;
// need to generate our own functors as abstract series doesnt have anythign for us
const radius = getAttributeFunctor(scaleProps, 'radius');
const radius0 = getAttr0Functor(scaleProps, 'radius');
const angle = getAttributeFunctor(scaleProps, 'angle');
const angle0 = getAttr0Functor(scaleProps, 'angle');
// but it does have good color support!
const fill =
this._getAttributeFunctor('fill') || this._getAttributeFunctor('color');
const stroke =
this._getAttributeFunctor('stroke') || this._getAttributeFunctor('color');
const opacity = this._getAttributeFunctor('opacity');
const x = this._getAttributeFunctor('x');
const y = this._getAttributeFunctor('y');
return (
<g
className={getCombinedClassName(predefinedClassName, className)}
onMouseOver={this._seriesMouseOverHandler}
onMouseOut={this._seriesMouseOutHandler}
onClick={this._seriesClickHandler}
onContextMenu={this._seriesRightClickHandler}
opacity={hideSeries ? 0 : 1}
pointerEvents={disableSeries ? 'none' : 'all'}
transform={`translate(${marginLeft + x(center)},${marginTop +
y(center)})`}
>
{data.map((row, i) => {
const noRadius = radiusDomain[1] === radiusDomain[0];
const arcArg = {
innerRadius: noRadius ? 0 : radius0(row),
outerRadius: radius(row),
startAngle: angle0(row) || 0,
endAngle: angle(row)
};
const arcedData = arcBuilder().padAngle(padAngle);
const rowStyle = row.style || {};
const rowClassName = row.className || '';
return (
<path
{...{
style: {
opacity: opacity && opacity(row),
stroke: stroke && stroke(row),
fill: fill && fill(row),
...style,
...rowStyle
},
onClick: e => this._valueClickHandler(modifyRow(row), e),
onContextMenu: e =>
this._valueRightClickHandler(modifyRow(row), e),
onMouseOver: e =>
this._valueMouseOverHandler(modifyRow(row), e),
onMouseOut: e => this._valueMouseOutHandler(modifyRow(row), e),
key: i,
className: `${predefinedClassName}-path ${arcClassName} ${rowClassName}`,
d: arcedData(arcArg)
}}
/>
);
})}
</g>
);
}
}
ArcSeries.propTypes = {
...AbstractSeries.propTypes,
...getScalePropTypesByAttribute('radius'),
...getScalePropTypesByAttribute('angle'),
center: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number
}),
arcClassName: PropTypes.string,
padAngle: PropTypes.oneOfType([PropTypes.func, PropTypes.number])
};
ArcSeries.defaultProps = defaultProps;
ArcSeries.displayName = 'ArcSeries';
export default ArcSeries;