ReactNative 手绘环形统计图
一、效果图
二、绘图使用了RN中的ART ,
对于每段的计算需要注意
1.角度计算应该转换为弧度,转换公式如下:
/**
* 角度转弧度
* @param angle
* @returns {number}
*/
degress2Radians(angle){
return angle / 180.0 * 3.1415926;
};
2.当前段的起始角度应该等于上一段的终端角度,对于第一段通常用0作为起始角度
这里直接给出起始角度的计算公式,假设数据是用数组传进来的
var stratAngle = 0;
var endAngle = 0;
if (i === 0) {
stratAngle = 0;
} else {
for (let j = 0; j < i; j++) {
stratAngle += itemArray[j].degress; //前面的角度累加
}
}
for (let j = 0; j <= i; j++) { //前面的角度累加
endAngle += itemArray[j].degress;
}
return (
<Wedge key={i}
outerRadius={outerRadius}
innerRadius={innerRadius}
startAngle={stratAngle}
endAngle={endAngle}
originX={chartWidth / 2 + outerRadius * Math.sin(this.degress2Radians(stratAngle))}
originY={chartHeight / 2 - outerRadius * Math.cos(this.degress2Radians(stratAngle))}
fill={itemArray[i].color}/>
);
})}
三、全部源码:
import React, {
Component,
} from 'react';
import {
View,
ART,
} from 'react-native';
import Wedge from './Wedge';
import PropTypes from 'prop-types';
const {Surface, Shape, Path} = ART;
/**
* Created by 刘胡来
* Date on 2019.04.26
* Copyright 2013 - 2019 QianTuo Inc. All Rights Reserved
* Desc:环形统计图
*/
export default class CircularChart extends Component {
static propTypes = {
itemArray: PropTypes.array,
chartWidth: PropTypes.number,
chartHeight: PropTypes.number,
outerRadius: PropTypes.number,
innerRadius: PropTypes.number,
}
constructor(props) {
super(props);
};
render() {
let {chartWidth, chartHeight, outerRadius,innerRadius,itemArray}=this.props;
return (
<View style={{flex: 1, backgroundColor: '#F5F5F9'}}>
<Surface width={chartWidth} height={chartHeight} style={{backgroundColor: 'yellow', marginTop: 10}}>
{itemArray.map((name, i) => {
var stratAngle = 0;
var endAngle = 0;
if (i === 0) {
stratAngle = 0;
} else {
for (let j = 0; j < i; j++) {
stratAngle += itemArray[j].degress;
}
}
for (let j = 0; j <= i; j++) {
endAngle += itemArray[j].degress;
}
return (
<Wedge key={i}
outerRadius={outerRadius}
innerRadius={innerRadius}
startAngle={stratAngle}
endAngle={endAngle}
originX={chartWidth / 2 + outerRadius * Math.sin(this.degress2Radians(stratAngle))}
originY={chartHeight / 2 - outerRadius * Math.cos(this.degress2Radians(stratAngle))}
fill={itemArray[i].color}/>
);
})}
</Surface>
</View>
)
};
/**
* 角度转弧度
* @param angle
* @returns {number}
*/
degress2Radians(angle){
return angle / 180.0 * 3.1415926;
};
}
四、Wedge的源码,是来源于网络,找不到网址了,大兄弟这里暂时借用你的,
import React, { Component } from 'react';
import { ART } from 'react-native';
const { Shape, Path } = ART;
import PropTypes from 'prop-types'
/**
* Wedge is a React component for drawing circles, wedges and arcs. Like other
* ReactART components, it must be used in a <Surface>.
*/
export default class Wedge extends Component<void, any, any> {
constructor(props : any) {
super(props);
(this:any).circleRadians = Math.PI * 2;
(this:any).radiansPerDegree = Math.PI / 180;
(this:any)._degreesToRadians = this._degreesToRadians.bind(this);
}
/**
* _degreesToRadians(degrees)
*
* Helper function to convert degrees to radians
*
* @param {number} degrees
* @return {number}
*/
_degreesToRadians(degrees : number) : number {
if (degrees !== 0 && degrees % 360 === 0) { // 360, 720, etc.
return (this:any).circleRadians;
}
return degrees * (this:any).radiansPerDegree % (this:any).circleRadians;
}
/**
* _createCirclePath(or, ir)
*
* Creates the ReactART Path for a complete circle.
*
* @param {number} or The outer radius of the circle
* @param {number} ir The inner radius, greater than zero for a ring
* @return {object}
*/
_createCirclePath(originX : number, originY : number, or : number, ir : number) : Path {
const path = new Path();
path.move(originX, or + originY)
.arc(or * 2, 0, or)
.arc(-or * 2, 0, or);
if (ir) {
path.move(or - ir, 0)
.counterArc(ir * 2, 0, ir)
.counterArc(-ir * 2, 0, ir);
}
path.close();
return path;
}
/**
* _createArcPath(sa, ea, ca, or, ir)
*
* Creates the ReactART Path for an arc or wedge.
*
* @param {number} startAngle The starting degrees relative to 12 o'clock
* @param {number} endAngle The ending degrees relative to 12 o'clock
* @param {number} or The outer radius in pixels
* @param {number} ir The inner radius in pixels, greater than zero for an arc
* @return {object}
*/
_createArcPath(originX : number, originY : number, startAngle : number, endAngle : number, or : number, ir : number) : Path {
const path = new Path();
// angles in radians
const sa = this._degreesToRadians(startAngle);
const ea = this._degreesToRadians(endAngle);
// central arc angle in radians
const ca = sa > ea ? (this:any).circleRadians - sa + ea : ea - sa;
// cached sine and cosine values
const ss = Math.sin(sa);
const es = Math.sin(ea);
const sc = Math.cos(sa);
const ec = Math.cos(ea);
// cached differences
const ds = es - ss;
const dc = ec - sc;
const dr = ir - or;
// if the angle is over pi radians (180 degrees)
// we will need to let the drawing method know.
const large = ca > Math.PI;
// TODO (sema) Please improve theses comments to make the math
// more understandable.
//
// Formula for a point on a circle at a specific angle with a center
// at (0, 0):
// x = radius * Math.sin(radians)
// y = radius * Math.cos(radians)
//
// For our starting point, we offset the formula using the outer
// radius because our origin is at (top, left).
// In typical web layout fashion, we are drawing in quadrant IV
// (a.k.a. Southeast) where x is positive and y is negative.
//
// The arguments for path.arc and path.counterArc used below are:
// (endX, endY, radiusX, radiusY, largeAngle)
path.move(originX, originY) // move to starting point
.arc(or * ds, or * -dc, or, or, large) // outer arc
.line(dr * es, dr * -ec); // width of arc or wedge
if (ir) {
path.counterArc(ir * -ds, ir * dc, ir, ir, large); // inner arc
}
return path;
}
render() : any {
// angles are provided in degrees
const startAngle = this.props.startAngle;
const endAngle = this.props.endAngle;
// if (startAngle - endAngle === 0) {
// return null;
// }
// radii are provided in pixels
const innerRadius = this.props.innerRadius || 0;
const outerRadius = this.props.outerRadius;
const { originX, originY } = this.props;
// sorted radii
const ir = Math.min(innerRadius, outerRadius);
const or = Math.max(innerRadius, outerRadius);
let path;
if (endAngle >= startAngle + 360) {
path = this._createCirclePath(originX, originY, or, ir);
} else {
path = this._createArcPath(originX, originY, startAngle, endAngle, or, ir);
}
return <Shape {...this.props} d={path} />;
}
}
Wedge.propTypes = {
outerRadius: PropTypes.number.isRequired, // 圆弧半径
startAngle: PropTypes.number.isRequired, // 开始角度
endAngle: PropTypes.number.isRequired, // 结束角度
originX: PropTypes.number, // 左边的距离 不是圆心的X
originY: PropTypes.number, // 上部的距离 不是圆心的Y
innerRadius: PropTypes.number, //内部半径 用户画弧
}
Wedge.defaultProps = {
originX: 0,
originY: 0,
}
五、使用方式:
import React from "react";
import {Button, View, Text, StyleSheet,Dimensions,
Image,TextInput,ListView,Alert,Animated,ART,
Easing,StatusBar,NativeModules,
TouchableOpacity} from 'react-native';
import BaseComponent from "../BaseComponent";
import Wedge from '../../uikit/art/Wedge';
import CircularChart from '../../uikit/art/CircularChart';
const {Surface, Shape, Path} = ART;
/**
* Created by 刘胡来
* Date on 2019.04.25
* Copyright 2013 - 2019 QianTuo Inc. All Rights Reserved
* Desc: 交易查询
*/
export default class TradeQuery extends BaseComponent{
constructor(props){
super(props);
this.testArray = [];
this.colors = ["yellow",'gray','green'];
this.total = 0;
// for(let i = 1; i <= 3; i ++){
// var item = new CircleItem();
// item.ratio = i * 100;
// this.total += i * 100;
// item.color = this.colors[i];
// this.testArray.push(item);
// }
//
//
// for(let i = 0; i < this.testArray.length; i ++){
// var item = this.testArray[i];
// let ratio = item.ratio;
// item.ratio = ratio / this.total;
// item.degress = item.ratio * 360.0;
//
// }
var item1 = new CircleItem();
item1.ratio = 100;
item1.degress = 100.0 / 600.0 * 360.0;
item1.color = 'purple';
this.testArray.push(item1);
var item2 = new CircleItem();
item2.ratio = 200;
item2.degress = 200.0 / 600.0 * 360.0;
item2.color = 'green';
this.testArray.push(item2);
var item3 = new CircleItem();
item3.ratio = 200;
item3.degress = 200.0 / 600.0 * 360.0;
item3.color = 'gray';
this.testArray.push(item3);
var item4 = new CircleItem();
item4.ratio = 100;
item4.degress = 100.0 / 600.0 * 360.0;
item4.color = 'white';
this.testArray.push(item4);
};
render(){
return(
<View style = {{flex:1,backgroundColor:'#F5F5F9'}}>
{this.setStatusBar('#1373EC')}
{this.buildTopNavigationBar('交易查询','#1373EC')}
{this.buildCircleChart()}
{/*<Surface width={300} height={300} style={{backgroundColor: 'yellow', marginTop: 10}}>*/}
{/*<Wedge*/}
{/*outerRadius={100}*/}
{/*innerRadius={90}*/}
{/*startAngle={0}*/}
{/*endAngle={60}*/}
{/*originX={150 + 100 * Math.sin(this.degress2Radians(0))}*/}
{/*originY={150 - 100 * Math.cos(this.degress2Radians(0))}*/}
{/*fill="purple" />*/}
{/*/!*<Wedge*!/*/}
{/*/!*outerRadius={100}*!/*/}
{/*/!*innerRadius={90}*!/*/}
{/*/!*startAngle={180}*!/*/}
{/*/!*endAngle={360}*!/*/}
{/*/!*originX={150}*!/*/}
{/*/!*originY={250} //左半边圆*!/*/}
{/*/!*fill="purple" />*!/*/}
{/*<Wedge*/}
{/*outerRadius={100}*/}
{/*innerRadius={90}*/}
{/*startAngle={60}*/}
{/*endAngle={180}*/}
{/*originX={150 + 100 * Math.sin(this.degress2Radians(60))}*/}
{/*originY={150 - 100 * Math.cos(this.degress2Radians(60))}*/}
{/*fill="green" />*/}
{/*<Wedge*/}
{/*outerRadius={100}*/}
{/*innerRadius={90}*/}
{/*startAngle={180}*/}
{/*endAngle={360}*/}
{/*originX={150 + 100 * Math.sin(this.degress2Radians(180))}*/}
{/*originY={150 - 100 * Math.cos(this.degress2Radians(180))}*/}
{/*fill="gray" />*/}
{/*{ this.testArray.map((name, i) => {*/}
{/*var posx = 0;*/}
{/*var endAngle = 0;*/}
{/*if(i === 0){*/}
{/*posx = 0;*/}
{/*}else{*/}
{/*for(let j = 0 ; j <i; j ++){*/}
{/*posx += this.testArray[j].degress;*/}
{/*}*/}
{/*}*/}
{/*for(let j = 0 ; j <=i; j ++){*/}
{/*endAngle += this.testArray[j].degress;*/}
{/*}*/}
{/*//startAngle = i === 0 ? 0: posx;*/}
{/*//console.log('122----------- start:'+startAngle +' end:'+endAngle + ' startX:'+posx);*/}
{/*return (*/}
{/*<Wedge key={i}*/}
{/*outerRadius={100}*/}
{/*innerRadius={90}*/}
{/*startAngle={posx}*/}
{/*endAngle={endAngle}*/}
{/*originX={150 + 100 * Math.sin(this.degress2Radians(posx))}*/}
{/*originY={150 - 100 * Math.cos(this.degress2Radians(posx))}*/}
{/*fill={this.testArray[i].color} />*/}
{/*);*/}
{/*})}*/}
{/*</Surface>*/}
</View>
)
}
buildCircleChart(){
let tabParams = {
itemArray:this.testArray,
chartWidth: 300,
chartHeight: 300,
outerRadius: 100,
innerRadius: 80,
};
return (
<CircularChart
{...tabParams}
/>
);
};
/**
* 角度转弧度
* @param angle
* @returns {number}
*/
degress2Radians(angle){
return angle / 180.0 * 3.1415926;
};
/**
* 值转角度
* @param value
* @returns {number}
*/
value2Degress(value){
return value / 360.0;
};
}
class CircleItem {
constructor(){
this.ratio = 0;
this.color = '#339900';
this.degress = 0;
}
}
``