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;

    }

}
``
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章