1 | /**
|
2 | * Copyright (c) Facebook, Inc. and its affiliates.
|
3 | *
|
4 | * This source code is licensed under the MIT license found in the
|
5 | * LICENSE file in the root directory of this source tree.
|
6 | * @typechecks
|
7 | *
|
8 | * Example usage:
|
9 | * <Wedge
|
10 | * outerRadius={50}
|
11 | * startAngle={0}
|
12 | * endAngle={360}
|
13 | * fill="blue"
|
14 | * />
|
15 | *
|
16 | * Additional optional property:
|
17 | * (Int) innerRadius
|
18 | *
|
19 | */
|
20 |
|
21 | ;
|
22 |
|
23 | var assign = require('object-assign');
|
24 | var PropTypes = require('prop-types');
|
25 | var React = require('react');
|
26 | var ReactART = require('react-art');
|
27 |
|
28 | var createReactClass = require('create-react-class');
|
29 |
|
30 | var Shape = ReactART.Shape;
|
31 | var Path = ReactART.Path;
|
32 |
|
33 | /**
|
34 | * Wedge is a React component for drawing circles, wedges and arcs. Like other
|
35 | * ReactART components, it must be used in a <Surface>.
|
36 | */
|
37 | var Wedge = createReactClass({
|
38 | displayName: 'Wedge',
|
39 |
|
40 | propTypes: {
|
41 | outerRadius: PropTypes.number.isRequired,
|
42 | startAngle: PropTypes.number.isRequired,
|
43 | endAngle: PropTypes.number.isRequired,
|
44 | innerRadius: PropTypes.number,
|
45 | },
|
46 |
|
47 | circleRadians: Math.PI * 2,
|
48 |
|
49 | radiansPerDegree: Math.PI / 180,
|
50 |
|
51 | /**
|
52 | * _degreesToRadians(degrees)
|
53 | *
|
54 | * Helper function to convert degrees to radians
|
55 | *
|
56 | * @param {number} degrees
|
57 | * @return {number}
|
58 | */
|
59 | _degreesToRadians: function _degreesToRadians(degrees) {
|
60 | if (degrees !== 0 && degrees % 360 === 0) {
|
61 | // 360, 720, etc.
|
62 | return this.circleRadians;
|
63 | } else {
|
64 | return (degrees * this.radiansPerDegree) % this.circleRadians;
|
65 | }
|
66 | },
|
67 |
|
68 | /**
|
69 | * _createCirclePath(or, ir)
|
70 | *
|
71 | * Creates the ReactART Path for a complete circle.
|
72 | *
|
73 | * @param {number} or The outer radius of the circle
|
74 | * @param {number} ir The inner radius, greater than zero for a ring
|
75 | * @return {object}
|
76 | */
|
77 | _createCirclePath: function _createCirclePath(or, ir) {
|
78 | var path = Path();
|
79 |
|
80 | path
|
81 | .move(0, or)
|
82 | .arc(or * 2, 0, or)
|
83 | .arc(-or * 2, 0, or);
|
84 |
|
85 | if (ir) {
|
86 | path
|
87 | .move(or - ir, 0)
|
88 | .counterArc(ir * 2, 0, ir)
|
89 | .counterArc(-ir * 2, 0, ir);
|
90 | }
|
91 |
|
92 | path.close();
|
93 |
|
94 | return path;
|
95 | },
|
96 |
|
97 | /**
|
98 | * _createArcPath(sa, ea, ca, or, ir)
|
99 | *
|
100 | * Creates the ReactART Path for an arc or wedge.
|
101 | *
|
102 | * @param {number} startAngle The starting degrees relative to 12 o'clock
|
103 | * @param {number} endAngle The ending degrees relative to 12 o'clock
|
104 | * @param {number} or The outer radius in pixels
|
105 | * @param {number} ir The inner radius in pixels, greater than zero for an arc
|
106 | * @return {object}
|
107 | */
|
108 | _createArcPath: function _createArcPath(startAngle, endAngle, or, ir) {
|
109 | var path = Path();
|
110 |
|
111 | // angles in radians
|
112 | var sa = this._degreesToRadians(startAngle);
|
113 | var ea = this._degreesToRadians(endAngle);
|
114 |
|
115 | // central arc angle in radians
|
116 | var ca = sa > ea ? this.circleRadians - sa + ea : ea - sa;
|
117 |
|
118 | // cached sine and cosine values
|
119 | var ss = Math.sin(sa);
|
120 | var es = Math.sin(ea);
|
121 | var sc = Math.cos(sa);
|
122 | var ec = Math.cos(ea);
|
123 |
|
124 | // cached differences
|
125 | var ds = es - ss;
|
126 | var dc = ec - sc;
|
127 | var dr = ir - or;
|
128 |
|
129 | // if the angle is over pi radians (180 degrees)
|
130 | // we will need to let the drawing method know.
|
131 | var large = ca > Math.PI;
|
132 |
|
133 | // TODO (sema) Please improve theses comments to make the math
|
134 | // more understandable.
|
135 | //
|
136 | // Formula for a point on a circle at a specific angle with a center
|
137 | // at (0, 0):
|
138 | // x = radius * Math.sin(radians)
|
139 | // y = radius * Math.cos(radians)
|
140 | //
|
141 | // For our starting point, we offset the formula using the outer
|
142 | // radius because our origin is at (top, left).
|
143 | // In typical web layout fashion, we are drawing in quadrant IV
|
144 | // (a.k.a. Southeast) where x is positive and y is negative.
|
145 | //
|
146 | // The arguments for path.arc and path.counterArc used below are:
|
147 | // (endX, endY, radiusX, radiusY, largeAngle)
|
148 |
|
149 | path
|
150 | .move(or + or * ss, or - or * sc) // move to starting point
|
151 | .arc(or * ds, or * -dc, or, or, large) // outer arc
|
152 | .line(dr * es, dr * -ec); // width of arc or wedge
|
153 |
|
154 | if (ir) {
|
155 | path.counterArc(ir * -ds, ir * dc, ir, ir, large); // inner arc
|
156 | }
|
157 |
|
158 | return path;
|
159 | },
|
160 |
|
161 | render: function render() {
|
162 | // angles are provided in degrees
|
163 | var startAngle = this.props.startAngle;
|
164 | var endAngle = this.props.endAngle;
|
165 | if (startAngle - endAngle === 0) {
|
166 | return null;
|
167 | }
|
168 |
|
169 | // radii are provided in pixels
|
170 | var innerRadius = this.props.innerRadius || 0;
|
171 | var outerRadius = this.props.outerRadius;
|
172 |
|
173 | // sorted radii
|
174 | var ir = Math.min(innerRadius, outerRadius);
|
175 | var or = Math.max(innerRadius, outerRadius);
|
176 |
|
177 | var path;
|
178 | if (endAngle >= startAngle + 360) {
|
179 | path = this._createCirclePath(or, ir);
|
180 | } else {
|
181 | path = this._createArcPath(startAngle, endAngle, or, ir);
|
182 | }
|
183 |
|
184 | return React.createElement(Shape, assign({}, this.props, {d: path}));
|
185 | },
|
186 | });
|
187 |
|
188 | module.exports = Wedge;
|