UNPKG

4.95 kBJavaScriptView Raw
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'use strict';
22
23var assign = require('object-assign');
24var PropTypes = require('prop-types');
25var React = require('react');
26var ReactART = require('react-art');
27
28var createReactClass = require('create-react-class');
29
30var Shape = ReactART.Shape;
31var 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 */
37var 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
188module.exports = Wedge;