UNPKG

10.6 kBJavaScriptView Raw
1import React from "react";
2import { View } from "react-native";
3import {
4 Svg,
5 Circle,
6 Polygon,
7 Polyline,
8 Path,
9 Rect,
10 G
11} from "react-native-svg";
12import AbstractChart from "./abstract-chart";
13
14class LineChart extends AbstractChart {
15 getColor = (dataset, opacity) => {
16 return (dataset.color || this.props.chartConfig.color)(opacity);
17 };
18
19 getStrokeWidth = dataset => {
20 return dataset.strokeWidth || this.props.chartConfig.strokeWidth || 3;
21 };
22
23 getDatas = data =>
24 data.reduce((acc, item) => (item.data ? [...acc, ...item.data] : acc), []);
25
26 getPropsForDots = (x, i) => {
27 const { getDotProps, chartConfig = {} } = this.props;
28 if (typeof getDotProps === "function") {
29 return getDotProps(x, i);
30 }
31 const { propsForDots = {} } = chartConfig;
32 return { r: "4", ...propsForDots };
33 };
34 renderDots = config => {
35 const {
36 data,
37 width,
38 height,
39 paddingTop,
40 paddingRight,
41 onDataPointClick
42 } = config;
43 const output = [];
44 const datas = this.getDatas(data);
45 const baseHeight = this.calcBaseHeight(datas, height);
46 const { getDotColor, hidePointsAtIndex = [] } = this.props;
47 data.forEach(dataset => {
48 dataset.data.forEach((x, i) => {
49 if (hidePointsAtIndex.includes(i)) {
50 return;
51 }
52 const cx =
53 paddingRight + (i * (width - paddingRight)) / dataset.data.length;
54 const cy =
55 ((baseHeight - this.calcHeight(x, datas, height)) / 4) * 3 +
56 paddingTop;
57 const onPress = () => {
58 if (!onDataPointClick || hidePointsAtIndex.includes(i)) {
59 return;
60 }
61
62 onDataPointClick({
63 index: i,
64 value: x,
65 dataset,
66 x: cx,
67 y: cy,
68 getColor: opacity => this.getColor(dataset, opacity)
69 });
70 };
71
72 output.push(
73 <Circle
74 key={Math.random()}
75 cx={cx}
76 cy={cy}
77 fill={
78 typeof getDotColor === "function"
79 ? getDotColor(x, i)
80 : this.getColor(dataset, 0.9)
81 }
82 onPress={onPress}
83 {...this.getPropsForDots(x, i)}
84 />,
85 <Circle
86 key={Math.random()}
87 cx={cx}
88 cy={cy}
89 r="12"
90 fill="#fff"
91 fillOpacity={0}
92 onPress={onPress}
93 />
94 );
95 });
96 });
97 return output;
98 };
99
100 renderShadow = config => {
101 if (this.props.bezier) {
102 return this.renderBezierShadow(config);
103 }
104
105 const { data, width, height, paddingRight, paddingTop } = config;
106 const datas = this.getDatas(data);
107 const baseHeight = this.calcBaseHeight(datas, height);
108 return config.data.map((dataset, index) => {
109 return (
110 <Polygon
111 key={index}
112 points={
113 dataset.data
114 .map((d, i) => {
115 const x =
116 paddingRight +
117 (i * (width - paddingRight)) / dataset.data.length;
118 const y =
119 ((baseHeight - this.calcHeight(d, datas, height)) / 4) * 3 +
120 paddingTop;
121 return `${x},${y}`;
122 })
123 .join(" ") +
124 ` ${paddingRight +
125 ((width - paddingRight) / dataset.data.length) *
126 (dataset.data.length - 1)},${(height / 4) * 3 +
127 paddingTop} ${paddingRight},${(height / 4) * 3 + paddingTop}`
128 }
129 fill="url(#fillShadowGradient)"
130 strokeWidth={0}
131 />
132 );
133 });
134 };
135
136 renderLine = config => {
137 if (this.props.bezier) {
138 return this.renderBezierLine(config);
139 }
140
141 const { width, height, paddingRight, paddingTop, data } = config;
142 const output = [];
143 const datas = this.getDatas(data);
144 const baseHeight = this.calcBaseHeight(datas, height);
145 data.forEach((dataset, index) => {
146 const points = dataset.data.map((d, i) => {
147 const x =
148 (i * (width - paddingRight)) / dataset.data.length + paddingRight;
149 const y =
150 ((baseHeight - this.calcHeight(d, datas, height)) / 4) * 3 +
151 paddingTop;
152 return `${x},${y}`;
153 });
154
155 output.push(
156 <Polyline
157 key={index}
158 points={points.join(" ")}
159 fill="none"
160 stroke={this.getColor(dataset, 0.2)}
161 strokeWidth={this.getStrokeWidth(dataset)}
162 />
163 );
164 });
165
166 return output;
167 };
168
169 getBezierLinePoints = (dataset, config) => {
170 const { width, height, paddingRight, paddingTop, data } = config;
171 if (dataset.data.length === 0) {
172 return "M0,0";
173 }
174
175 const datas = this.getDatas(data);
176 const x = i =>
177 Math.floor(
178 paddingRight + (i * (width - paddingRight)) / dataset.data.length
179 );
180 const baseHeight = this.calcBaseHeight(datas, height);
181 const y = i => {
182 const yHeight = this.calcHeight(dataset.data[i], datas, height);
183 return Math.floor(((baseHeight - yHeight) / 4) * 3 + paddingTop);
184 };
185
186 return [`M${x(0)},${y(0)}`]
187 .concat(
188 dataset.data.slice(0, -1).map((_, i) => {
189 const x_mid = (x(i) + x(i + 1)) / 2;
190 const y_mid = (y(i) + y(i + 1)) / 2;
191 const cp_x1 = (x_mid + x(i)) / 2;
192 const cp_x2 = (x_mid + x(i + 1)) / 2;
193 return (
194 `Q ${cp_x1}, ${y(i)}, ${x_mid}, ${y_mid}` +
195 ` Q ${cp_x2}, ${y(i + 1)}, ${x(i + 1)}, ${y(i + 1)}`
196 );
197 })
198 )
199 .join(" ");
200 };
201
202 renderBezierLine = config => {
203 return config.data.map((dataset, index) => {
204 const result = this.getBezierLinePoints(dataset, config);
205 return (
206 <Path
207 key={index}
208 d={result}
209 fill="none"
210 stroke={this.getColor(dataset, 0.2)}
211 strokeWidth={this.getStrokeWidth(dataset)}
212 />
213 );
214 });
215 };
216
217 renderBezierShadow = config => {
218 const { width, height, paddingRight, paddingTop, data } = config;
219 return data.map((dataset, index) => {
220 const d =
221 this.getBezierLinePoints(dataset, config) +
222 ` L${paddingRight +
223 ((width - paddingRight) / dataset.data.length) *
224 (dataset.data.length - 1)},${(height / 4) * 3 +
225 paddingTop} L${paddingRight},${(height / 4) * 3 + paddingTop} Z`;
226 return (
227 <Path
228 key={index}
229 d={d}
230 fill="url(#fillShadowGradient)"
231 strokeWidth={0}
232 />
233 );
234 });
235 };
236
237 render() {
238 const {
239 width,
240 height,
241 data,
242 withShadow = true,
243 withDots = true,
244 withInnerLines = true,
245 withOuterLines = true,
246 withHorizontalLabels = true,
247 withVerticalLabels = true,
248 style = {},
249 decorator,
250 onDataPointClick,
251 verticalLabelRotation = 0,
252 horizontalLabelRotation = 0,
253 formatYLabel = yLabel => yLabel,
254 formatXLabel = xLabel => xLabel
255 } = this.props;
256 const { labels = [] } = data;
257 const {
258 borderRadius = 0,
259 paddingTop = 16,
260 paddingRight = 64,
261 margin = 0,
262 marginRight = 0,
263 paddingBottom = 0
264 } = style;
265 const config = {
266 width,
267 height,
268 verticalLabelRotation,
269 horizontalLabelRotation
270 };
271 const datas = this.getDatas(data.datasets);
272 return (
273 <View style={style}>
274 <Svg
275 height={height + paddingBottom}
276 width={width - margin * 2 - marginRight}
277 >
278 <G>
279 {this.renderDefs({
280 ...config,
281 ...this.props.chartConfig
282 })}
283 <Rect
284 width="100%"
285 height={height}
286 rx={borderRadius}
287 ry={borderRadius}
288 fill="url(#backgroundGradient)"
289 />
290 <G>
291 {withInnerLines
292 ? this.renderHorizontalLines({
293 ...config,
294 count: 4,
295 paddingTop,
296 paddingRight
297 })
298 : withOuterLines
299 ? this.renderHorizontalLine({
300 ...config,
301 paddingTop,
302 paddingRight
303 })
304 : null}
305 </G>
306 <G>
307 {withHorizontalLabels
308 ? this.renderHorizontalLabels({
309 ...config,
310 count: Math.min(...datas) === Math.max(...datas) ? 1 : 4,
311 data: datas,
312 paddingTop,
313 paddingRight,
314 formatYLabel
315 })
316 : null}
317 </G>
318 <G>
319 {withInnerLines
320 ? this.renderVerticalLines({
321 ...config,
322 data: data.datasets[0].data,
323 paddingTop,
324 paddingRight
325 })
326 : withOuterLines
327 ? this.renderVerticalLine({
328 ...config,
329 paddingTop,
330 paddingRight
331 })
332 : null}
333 </G>
334 <G>
335 {withVerticalLabels
336 ? this.renderVerticalLabels({
337 ...config,
338 labels,
339 paddingRight,
340 paddingTop,
341 formatXLabel
342 })
343 : null}
344 </G>
345 <G>
346 {this.renderLine({
347 ...config,
348 paddingRight,
349 paddingTop,
350 data: data.datasets
351 })}
352 </G>
353 <G>
354 {withShadow &&
355 this.renderShadow({
356 ...config,
357 data: data.datasets,
358 paddingRight,
359 paddingTop
360 })}
361 </G>
362 <G>
363 {withDots &&
364 this.renderDots({
365 ...config,
366 data: data.datasets,
367 paddingTop,
368 paddingRight,
369 onDataPointClick
370 })}
371 </G>
372 <G>
373 {decorator &&
374 decorator({
375 ...config,
376 data: data.datasets,
377 paddingTop,
378 paddingRight
379 })}
380 </G>
381 </G>
382 </Svg>
383 </View>
384 );
385 }
386}
387
388export default LineChart;
389
\No newline at end of file