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