react-native 使用 ART 制作图表

先看效果图


阶梯图表.png

实现效果


Simulator Screen Shot - iPhone X.png

由于图表比较简单,有一些需要自定义的地方,使用第三方框架需要熟悉接口,相对于自己画,时间成本都差不多,画出来更便于需求变更修改。(第三方表格框架推荐 victory-native react-native-charts-wrapper 。相对来说victory-native要好一点,react-native-charts-wrapper由于是对原生平台的封装,有些属性设置效果在两个平台是不一致的)。

画图可以使用 react-native-svg 。由于表格比较简单,只包含线条、文字、矩形色块,使用 ART 已经很方便了。

ART 的基本使用

iOS 需要先本地添加 ART
先看一下基础用法


e.png
import React from 'react';
import { StyleSheet, View, ART, Dimensions } from 'react-native';
import PropTypes from 'prop-types'

const {Surface, Text, Path, Shape} = ART;

export default class Chart extends React.Component {

    static defaultProps = {
        height: 230,
        width: Dimensions.get('window').width,
    }

    static propTypes = {
        height: PropTypes.number,
        width: PropTypes.number
    }

    render() {
        const path = Path()
            .moveTo(10,40)
            .lineTo(300,40);

        const rectpath = Path()
            .moveTo(10,60)
            .lineTo(110,60)
            .lineTo(110,160)
            .lineTo(10,160)
            .close();
        return (
            <View style={styles.container}>
                <Surface width={this.props.width} height={this.props.height}>
                    <Text strokeWidth={0.5} x={16} y={0}  alignment = "left"  fill="#ccc" font={{
                        fontSize: 12,
                        fontFamily: 'Arial',
                        fontWeight: 100,
                    }}>年化利率(%)</Text>
                    <Shape d={path} stroke={'red'} strokeWidth={1} />
                    <Shape d={rectpath} fill={'#000'} stroke={'red'} strokeWidth={1} />
                </Surface>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        backgroundColor: '#fff'
    },
});

画图

import React from 'react';
import { StyleSheet, View, ART, Dimensions } from 'react-native';
import PropTypes from 'prop-types'
const {Surface, Path, Shape} = ART;

const mTop = 30;
const mLeft = 50;
const mBottom = 40;
const mRight = 20

export default class Chart extends React.Component {

    static defaultProps = {
        height: 230,
        width: Dimensions.get('window').width,
        data: {
            step_info: [
                {
                    "x":"锁定期1",
                    "y":"12.00"
                },
                {
                    "x":"锁定期2",
                    "y": "12.50"
                },
                {
                    "x":"锁定期3",
                    "y": "13.00"
                },
                {
                    "x":"锁定期4",
                    "y": "13.50"
                },
                {
                    "x":"锁定期5",
                    "y": "14.00"
                },
                {
                    "x":"锁定期6",
                    "y": "14.50"
                }
            ],
            apr: '12',
            step_apr: '0.5',
            apr_max: '14.5',
            reward_apr: '0',
            first_apr: '0'
        }
    }

    static propTypes = {
        height: PropTypes.number,
        width: PropTypes.number
    }

    render() {
        const {width, height} = this.props
        let chartWidth = width-mRight-mLeft
        let chartHeight = height-mBottom-mTop
        const path = Path()
            .moveTo(mLeft,mTop)
            .lineTo(mLeft+chartWidth,mTop)
            .lineTo(mLeft+chartWidth,mTop+chartHeight)
            .lineTo(mLeft,mTop+chartHeight)
            .close();
        return (
            <View style={styles.container}>
                <Surface width={width} height={height}>
                    <ART.Text strokeWidth={0.5} x={16} y={0} fill="#ccc" font={{
                        fontSize: 14,
                        fontFamily: 'Arial',
                        fontWeight: 'normal',
                    }}>年化利率(%)</ART.Text>
                    <ART.Text strokeWidth={0.5} alignment={'right'} x={mLeft+chartWidth} y={mTop+chartHeight+20} fill="#ccc" font={{
                        fontSize: 14,
                        fontFamily: 'Arial',
                        fontWeight: 'normal',
                    }}>持有时长(天数)</ART.Text>
                    <Shape d={path} stroke={'ccc'} strokeWidth={1} />
                    {this._renderChartData()}
                </Surface>
            </View>
        );
    }

    _renderChartData(){
        const {width, height} = this.props
        let chartWidth = width-mRight-mLeft
        let chartHeight = height-mBottom-mTop

        let dataWidth = chartWidth/this.props.data.step_info.length

        let reverseInfo = this.props.data.step_info.slice().reverse()
        let maxApr = parseFloat(this.props.data.apr_max)
        let minApr = parseFloat(this.props.data.apr) - parseFloat(this.props.data.step_apr)
        let lineCount = (maxApr - minApr) / parseFloat(this.props.data.step_apr)
        let step_height = chartHeight/(lineCount)

        let result = this.props.data.step_info.map((e, i)=>{
            let lines = []

            //
            let minValue = parseFloat(this.props.data.apr)-parseFloat(this.props.data.step_apr)
            let x = mLeft+dataWidth * i
            let y =mTop + (parseFloat(reverseInfo[i].y)-minValue) / (parseFloat(this.props.data.apr_max) -minValue) *chartHeight-step_height
            let infoText = `${e.y}`
            if (i===0 && parseFloat(this.props.data.first_apr) > 0) {
                y = mTop + (parseFloat(reverseInfo[i].y)-parseFloat(this.props.data.first_apr)-minValue) / (parseFloat(this.props.data.apr_max) -minValue) *chartHeight-step_height
                infoText = `${e.y}+${this.props.data.first_apr}`
            } else if ( parseFloat(this.props.data.reward_apr) > 0 ){
                infoText = `${e.y}+${this.props.data.reward_apr}`
            }
            let dataHeight = chartHeight - y + mTop
            const path = ART.Path()
                .moveTo(mLeft,mTop+(i)*step_height)
                .lineTo(mLeft+chartWidth,mTop+(i)*step_height)
            // girdLine
            lines.push(<Shape d={path} stroke="#ccc" strokeWidth={0.5} />)

            //xaxis
            lines.push(
                this._renderXLabel(x+0.5*dataWidth, mTop+chartHeight+3, e.x)
            )

            // top line
            lines.push(
                this._renderBarTopLine(x, y, dataWidth)
            )

            // info
            lines.push(
                this._renderInfoLabel(x+0.5*dataWidth, y-14, infoText)
            )

            // rect
            lines.push(
                this._renderAData(x, y, dataWidth, dataHeight)
            )

            return lines
        })


        // yaxis
        let ys = []
        for (let i = maxApr; i > minApr; i-=parseFloat(this.props.data.step_apr)) {
            let yValue = i
            ys.push(
                this._renderYLabel(mLeft-20, mTop+(1-(i-minApr)/(maxApr-minApr))*chartHeight-7, yValue+parseFloat(this.props.data.reward_apr))
            )
        }
        result = result.concat(ys)

        return result
    }

    _renderYLabel(x, y, value){
        return (
            <ART.Text strokeWidth={1}
                      x={x}
                      y={y}
                      fill="#ccc"
                      font={{
                          fontSize: 12,
                          fontFamily: 'Arial',
                          fontWeight: 100
                      }}
                      alignment='center'
            >{this._formatChartApr(value)}</ART.Text>
        )
    }

    _renderXLabel(x, y, value){
        return (
            <ART.Text strokeWidth={1}
                      x={x}
                      y={y}
                      fill="#ccc"
                      font={{
                          fontSize: 12,
                          fontFamily: 'Arial',
                          fontWeight: 100
                      }}
                      alignment='center'
            >{value}</ART.Text>
        )
    }

    _renderBarTopLine(x, y, width){
        const path = ART.Path()
            .moveTo(x,y)
            .lineTo(x+width,y)
        return <Shape d={path} stroke="#FF6E00" strokeWidth={2} />
    }

    _renderInfoLabel(x, y, value){
        return (
            <ART.Text strokeWidth={1}
                      x={x}
                      y={y}
                      fill="#FF6E00"
                      font={{
                          fontSize: 10,
                          fontFamily: 'Arial',
                          fontWeight: 100
                      }}
                      alignment='center'
            >{value}</ART.Text>
        )
    }

    _renderAData(x, y, width, height){
        const path = new ART.Path()
            .moveTo(x, y)
            .lineTo(x+width, y)
            .lineTo(x+width,y+height)
            .lineTo(x,y+height)
            .close();

        return  <ART.Shape d={path} fill={'#FF6E0040'}/>

    }


    _formatChartApr(apr){
        return parseFloat(apr).toFixed(2).toString()
    }
}

const styles = StyleSheet.create({
    container: {
        backgroundColor: '#fff'
    },
});

GitHub 地址

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,663评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,125评论 3 414
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,506评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,614评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,402评论 6 404
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,934评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,021评论 3 440
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,168评论 0 287
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,690评论 1 333
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,596评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,784评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,288评论 5 357
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,027评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,404评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,662评论 1 280
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,398评论 3 390
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,743评论 2 370

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 16,000评论 3 119
  • React Native开发中常用三方组件大全 作者整理的一套常用的React Native开发中使用到的三方组件...
    光强_上海阅读 21,260评论 6 95
  • 那时候,她的男友在车间工作。她第一次去那里找他,赴晚间的约会。 她没有立刻走进车间,而是愣在了门口———车间的墙上...
    秋未完阅读 574评论 2 9
  • 生命是一条小河,你就像一艘小舟,命运就是撑船的船夫。 生命是一棵苹果 树,而你,就像那苹果树的种子。命运就是它的果...
    Qiu_Naruto阅读 188评论 0 1
  • 尊敬的公司领导: 我是________部门的________,于______年_____月______日成为公司的...
    庄唯1阅读 194评论 0 0