# React 可視化

在學(xué)習(xí)使用過React一段時間后面氓,感覺React的使用還是比較舒適的兵钮。了解React的組件生命周期,路由舌界,及通信你會發(fā)現(xiàn)React對于開發(fā)真的很便利掘譬。

引言
偶然間,看見一篇博客關(guān)于React可視化的呻拌,自己本身這半年多使用React開發(fā)也有一些感觸葱轩,就借鑒這篇博客寫下這篇隨文。

參考: React可視化的
antd のdemo: antd-demo

推薦

腳手架

React組件庫

React 可視化

回到正題藐握。這篇主要是介紹些React中的可視化使用靴拱。

說到可視化,無外乎使用原生的canvas猾普、svg或一些可視化庫進行可視化開發(fā)袜炕。

canvas

class Graphic extends React.Component {
  constructor(){
    super();
    this.state = {
      color: 'green'
    }
    //this.onChange = this.onChange.bind(this);
  }

  componentDidMount(){
    const context = ReactDOM.findDOMNode(document.getElementById('canvas')).getContext('2d');
    this.paint(context);
  }
componentDidUpdate(){
    const context = ReactDOM.findDOMNode(document.getElementById('canvas')).getContext('2d');
  context.clearRect(0, 0, 200, 200);
    this.paint(context);
  }
  paint(context) {
    context.save();
    context.fillStyle = this.state.color;
    context.fillRect(0, 0, 100, 100);
    context.restore();
  }
  onChange(e){
    this.setState({
      color: e.target.value
    });
  }
  render(){
    return (
      <div>
        <canvas id='canvas' width='200' height='200' style= ></canvas>
        <input type='color' value={this.state.color} onChange={::this.onChange}/>
       </div>
    )
  }
}
ReactDOM.render(
  <Graphic />,
  document.getElementById('root')
);

svg

class SVG extends React.Component {
  constructor(){
    super();
  
  }
  render(){
    return (
      <svg width='200' height='200' viewBox='0 0 200 200'>
           <circle cx='50' cy='50' r='20'/>
           <circle cx='100' cy='50' r='20'/>
           <circle cx='75' cy='100' r='40'/>
           <circle cx='60' cy='90' r='10' fill='#fff' />
           <circle cx='90' cy='90' r='10' fill='#fff' />
      </svg>
    )
  }
}
ReactDOM.render(
  <SVG />,
  document.getElementById('root')
);

可視化組件包裝

利用一些可視化庫插件進行包裝

Echarts

  • echarts配置文件
  • echarts渲染

options

const LineScore = (data) => {
    const option = {
    title: {
        text: '近年消費趨勢'
    },
    tooltip: {
        trigger: 'axis'
    },
    legend: {
        data:['clothes','foods','home','travel']
    },
    grid: {
        left: '3%',
        right: '4%',
        bottom: '3%',
        containLabel: true
    },
    toolbox: {
        feature: {
            saveAsImage: {}
        }
    },
    xAxis: {
        type: 'category',
        boundaryGap: false,
        data: ['2010','2011','2012','2013','2014','2015','2016']
    },
    yAxis: {
        type: 'value',
        name:'¥(元)'
    },
    series: [
        {
            name:'clothes',
            type:'line',
            smooth: true,
            data:[1200, 1320, 1010, 1340, 1900, 2300, 2100]
        },
        {
            name:'foods',
            type:'line',
            smooth: true,
            data:[2200, 1820, 1910, 2340, 2900, 3300, 3200]
        },
        {
            name:'home',
            type:'line',
            smooth: true,
            data:[800, 1020, 1210, 1540, 1800, 2300, 3100]
        },
        {
            name:'travel',
            type:'line',
            smooth: true,
            data:[600, 920, 1010, 1340, 1700, 2300, 2230]
        }
    ]
    };
 
    return option
}
export {
    LineScore
}

使用

import {LineScore} from './chartsOption'
import {EchartsChart} from 'components'
    
const ChartDom = (props) => {
    const chart_options = LineScore()
    return(
        <div style={{height:'300px'}}>
            <EchartsChart options={chart_options}/>
        </div>
    )
}

ReactDOM.render(
  <ChartDom />,
  document.getElementById('root')
);

組件開發(fā)

EchartsChart
|- index.js   組件的入口文件
|- index.less  組件的樣式
|- main.js  組件的可視化庫包裝
  • index.js , 里面使用的一些判斷非空 及組件渲染優(yōu)化,想了解都可以fork我的antd-demo
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import ReactDM from 'react-dom'
import classnames from 'classnames'
import Chart from './main'
import styles from './index.less'
import {tools, shouldComponentUpdate} from 'utils'

class EchatsChart extends Component {
    constructor(props) {
        super();
        this.state = {} 
    }

    componentDidMount() {
        this.container = ReactDM.findDOMNode(this.refs.echarts)
        if(this.props.options && !tools.emptyObj(this.props.options)) {

            this.renderChart(this.props)
        }
    }

    componentWillReceiveProps(nextProps) {
        if('options' in nextProps && nextProps.options != this.props.options) {
            
            this.renderChart(nextProps)
        }
    }

    shouldComponentUpdate = shouldComponentUpdate

    renderChart(props) {
        new Chart({
            container:this.container,
            ...props
        })
    }

    render() {
        return (
            <div className={styles.echarts_chart}>
                <div className={styles.echarts_chart_box} ref='echarts'>
                </div>
            </div>
        )
    }
}

export default EchatsChart;
  • main.js ,組件的核心初家,可視化實現(xiàn)
/*
  chart base on echarts
*/
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import echarts from 'echarts'
import {tools, request} from 'utils'

class Chart extends Component {
  constructor(config) {
    super()
    this.config = config;
    this.container = config.container;
    
    this.init()
  }
  
  init() {
    const _this = this
    if(_this.config.showMapName) { //判斷是否需要展示地圖
        
        async function getMap(){
              return request({
                url: `/data/map/${_this.config.showMapName}.json`,
                method: 'get',
                data: {},
              })
            }
        
        getMap().then(res => {
            console.log(res)            
                res && echarts.registerMap(_this.config.showMapName, res);
                _this.renderChart()
            });     
            
    }
    else {
        _this.renderChart()
    }
  }
  
  renderChart() {
    const myChart = echarts.init(this.container);

    if(this.config.options && !tools.emptyObj(this.config.options)) {
      this.config.options.color = ['#64ea91','#8fc9fb', '#d897eb', '#f69899', '#f8c82e','#f797d6',  '#ca8622', '#bda29a','#6e7074', '#546570', '#c4ccd3']
      myChart.setOption(this.config.options);
    }

    window.addEventListener("resize",function(){ //響應(yīng)式
      myChart.resize();
    });

    const _this = this
    for (let key in this.config) { //添加事件監(jiān)聽
      if(/^on[a-zA-Z]*$/.test(key)) {
        const even = key.substring(2);
        myChart.on(even, function (params) {
          _this.config[key] && _this.config[key](params)
        });
      }
    } 
  }
}
Chart.PropTypes = {
  container: PropTypes.object,
  showMapName:PropTypes.string,
  options: PropTypes.object,
  customProp(props) {
    if(!props.options) {
      return new Error('You echarts chart need a options!')
    }
  }
}

export default Chart

我以前編寫echarts類型得組件偎窘,是將一種類型單獨編寫組件的,但后來嫌經(jīng)常需要做些重復(fù)的編碼工作溜在,所以就合并了陌知。如果你需要單獨編寫,當然這樣個性化掖肋,獨立性更好仆葡,維護好∨嘧瘢可以查看https://zhuanlan.zhihu.com/p/28331793

d3JS

當然現(xiàn)在d3已經(jīng)到v4了浙芙,支持canvas了登刺。我這實現(xiàn)和React可視化的不同。

  • D3支持數(shù)據(jù)和節(jié)點綁定嗡呼,數(shù)據(jù)變化纸俭,相應(yīng)的節(jié)點也發(fā)生變化。React推崇單向數(shù)據(jù)流南窗,數(shù)據(jù)從父組件到子組件揍很,每個子組件只實現(xiàn)簡單的一個模塊
  • D3實現(xiàn)一套selector機制, 能夠讓開發(fā)址直接操作DOM節(jié)點万伤、SVG節(jié)點窒悔。React使用虛擬DOM和高性能DOM diff算法,開發(fā)者無須關(guān)注節(jié)點操作敌买。
import d3 from 'd3'

/*
    base on  d3.js-v3
*/

class AtlasChart {
    constructor(props) {
        this.props = props;
        this.container = props.container;
        this.orgData = props.atlasData;
        this.width = (props.options && props.options.width) || this.container.clientWidth;
        this.height = (props.options && props.options.height) || this.container.clientHeight;

      const defaultOption = {
        zoom: {
          scale:0.8, 
          x:this.width/2, 
          y:this.height/2
        }
      }
        this.options = Object.assign(defaultOption, props.options);

        this.init();
    }

    init() {
        const chartData = this.orgData;
        this.renderChart(chartData);
    }

    renderChart(chartData) {
        const _this = this;
        const W = this.width,
            H = this.height,
            rx = W / 2,
            ry = H / 2; 
        const n = {
      margin: 0,
      radiusR: 130
    };
    const Roate = _this.options.zoom.rotate;

        const color = ['#1f77b4', '#778ae6', '#b46bc5', '#eda61d', '#c3d41b', '#91dc8a', '#24a6da', '#aec7e8', '#ff9896', '#2ca02c'];

        d3.select(this.container).html('');

        var cluster = d3.layout.cluster()
    .size([360, ry - 30])
    .separation(function(e, t) {
      return (e.parent == t.parent ? 1 : 2) / e.depth
    })
    .sort(null);

    var lineScale = d3.scale.linear().domain([0, 2]).range([10, 7]);
        var diagonal = d3.svg.diagonal.radial()
        .projection(function(d) { return [d.y, d.x / 180 * Math.PI]; });

        function zoom() {
            // console.log(d3.event)
      _this.options.zoom.x = d3.event.translate[0];
      _this.options.zoom.y = d3.event.translate[1];
      _this.options.zoom.scale = d3.event.scale;
    
      svg_center.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
    }

        const zoomListener = d3.behavior.zoom()
      .scaleExtent([0.1, 3])
      .scale(_this.options.zoom.scale)
      .translate([_this.options.zoom.x,_this.options.zoom.y])
      .on("zoom", zoom);

    // 自定義提示框
    let tip = d3.select(this.container)
      .append("div")
      .attr('class','tooltips');
        
        let svg = d3.select(this.container)
            .append('svg')
            .attr('width', W)
            .attr('height', H)
            .attr('id', 'atlas-chart')
            .call(zoomListener)
            .on('dblclick.zoom', null);

        let svg_center = svg.append('g')
            .attr('id','svg_center')
            .attr("transform", "translate(" + [_this.options.zoom.x, _this.options.zoom.y] + ")scale(" + _this.options.zoom.scale + ")" );

        let nodes = cluster.nodes(chartData);

        nodes.forEach(function(d) {
      d.y = n.radiusR * d.depth,
      d.depth != 0 && (d.x += Roate,
      d.x >= 360 ? d.x -= 360 : d.x < 0 && (d.x += 360))
    });

        let link = svg_center.selectAll("path.link")
      .data(cluster.links(nodes))
      .enter().append("svg:path")
      .attr("class", "link")
      .attr("d", diagonal)
      .style('stroke', d => {
        return d.target.color ? d.target.color : color[d.target.group]
      });

    let node = svg_center.selectAll("g.node")
      .data(nodes)
      .enter().append("svg:g")
      .attr("class", "node")
      .attr("transform", function(d) { return d.parent ? "rotate(" + (d.x - 90) + ")translate(" + d.y + ")" : ''; })
      .on('mouseover', (d) => {
        d3.event.stopPropagation();
        tip.html(`${d.node_name}`)
          .style('left', `${d3.mouse(this.container)[0] + 20}px`)
          .style('top', `${d3.mouse(this.container)[1] + 20}px`)
          .style('display', 'block')
      })
      .on('mousemove', () => {
        tip.style('left', `${d3.mouse(this.container)[0] + 20}px`)
          .style('top', `${d3.mouse(this.container)[1] + 20}px`)
      })
      .on('mouseout', () => {
        tip.style('display', 'none')
      });

    node.append("svg:circle")
      .attr("r", 6)
      .attr('class', d => {
        return d.depth == 1 || d.depth == 0 ? 'node_breath': ''
      })
      .style('fill', d => {
        return d.color? d.color : color[d.group]
      });

    node.append("svg:text")
        .attr('class', 'node-text')
      .attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
      .attr("dy", d => {
        return d.depth?".31em":'-1.5em'
      })
      .attr("text-anchor", function(d) { return d.parent ? d.x < 180 ? "start" : "end" : "start"; })
      .attr("transform", function(d) { 
        let t =  "rotate(180)"
        if(d.depth) {
            t = d.x < 180 ? null : "rotate(180)"; 
        }
        else {
            t = 'translate(' + (-(d.node_name.length * 12 / 2)) + ")";
        }

        return t
      })
      .text(function(d) { 
        return d.node_name.length > 10 ? d.node_name.slice(0, 10) + '...' : d.node_name         
      });

    }
}

export default AtlasChart

以上代碼實現(xiàn)的是一種關(guān)系圖譜简珠。


Recharts

這是一種將各種圖表進行細分為標簽組件進行組合形成可視化。
http://recharts.org/#/en-US/api

Recharts優(yōu)點

  • 聲明式標簽
  • 貼近原生SVG配置
  • 接口式API虹钮,解決各種個性化需求
const {LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend} = Recharts;
const data = [
      {name: 'Page A', uv: 4000, pv: 2400, amt: 2400},
      {name: 'Page B', uv: 3000, pv: 1398, amt: 2210},
      {name: 'Page C', uv: 2000, pv: 9800, amt: 2290},
      {name: 'Page D', uv: 2780, pv: 3908, amt: 2000},
      {name: 'Page E', uv: 1890, pv: 4800, amt: 2181},
      {name: 'Page F', uv: 2390, pv: 3800, amt: 2500},
      {name: 'Page G', uv: 3490, pv: 4300, amt: 2100},
];
const SimpleLineChart = React.createClass({
    render () {
    return (
        <LineChart width={600} height={300} data={data}
            margin={{top: 5, right: 30, left: 20, bottom: 5}}>
       <XAxis dataKey="name"/>
       <YAxis/>
       <CartesianGrid strokeDasharray="3 3"/>
       <Tooltip/>
       <Legend />
       <Line type="monotone" dataKey="pv" stroke="#8884d8" activeDot={{r: 8}}/>
       <Line type="monotone" dataKey="uv" stroke="#82ca9d" />
      </LineChart>
    );
  }
})

ReactDOM.render(
  <SimpleLineChart />,
  document.getElementById('container')
);

如果想了解下Recharts的幾大特性聋庵,可以看看https://zhuanlan.zhihu.com/p/20641029

題外

組件化這些思想不只是在可視化組件中可以應(yīng)用,很多組件也可以考慮利用這種思想來實現(xiàn)芙粱,例如表格組件就可以抽取Table 和 Column 兩個組件祭玉,然后大家使用表格也非常簡單:

<Table data={data}>
  <Column name="名稱" dataKey="name"/>
  <Column name="數(shù)量" dataKey="count" align="right" th={<SortableTh order="asc" onChange={handleSort}/>}/>
  <Column name="金額" dataKey="amt" td="float" align="right"/>
</Table>

最后

放出我的學(xué)習(xí)antd時做的一個小demo,里面有這上面的可視化組件代碼,當然還有一些其他antd的開發(fā)套路春畔。

antd-demo

歡迎指導(dǎo)O(∩_∩)O哈哈~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脱货,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子律姨,更是在濱河造成了極大的恐慌振峻,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件线召,死亡現(xiàn)場離奇詭異铺韧,居然都是意外死亡,警方通過查閱死者的電腦和手機缓淹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門哈打,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人讯壶,你說我怎么就攤上這事料仗。” “怎么了伏蚊?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵立轧,是天一觀的道長。 經(jīng)常有香客問我,道長氛改,這世上最難降的妖魔是什么帐萎? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮胜卤,結(jié)果婚禮上疆导,老公的妹妹穿的比我還像新娘。我一直安慰自己葛躏,他們只是感情好澈段,可當我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著舰攒,像睡著了一般败富。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上摩窃,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天兽叮,我揣著相機與錄音,去河邊找鬼猾愿。 笑死充择,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的匪蟀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼宰僧,長吁一口氣:“原來是場噩夢啊……” “哼材彪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起琴儿,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤段化,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后造成,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體显熏,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年晒屎,在試婚紗的時候發(fā)現(xiàn)自己被綠了喘蟆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡鼓鲁,死狀恐怖愤诱,靈堂內(nèi)的尸體忽然破棺而出圣勒,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布谣殊,位于F島的核電站锅锨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜斜筐,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蛀缝。 院中可真熱鬧顷链,春花似錦、人聲如沸内斯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俘闯。三九已至潭苞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間真朗,已是汗流浹背此疹。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遮婶,地道東北人蝗碎。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像旗扑,于是被迫代替她去往敵國和親蹦骑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,193評論 25 707
  • 持續(xù)更新中...... 一套企業(yè)級的 UI 設(shè)計語言和 React 實現(xiàn)臀防。 https://mobile.ant....
    日不落000閱讀 5,697評論 0 35
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫眠菇、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,109評論 4 62
  • 項目一直沒定下來方向,大概就是定了一個UITabBarController的樣子是中間帶圈的 哈哈 現(xiàn)在可能很流行...
    夏點閱讀 585評論 1 2
  • “慎獨”一詞致燥,出自秦漢之際儒家著作《禮記?中庸》一書:“莫見乎隱登疗,莫顯乎微,故君子慎其獨也嫌蚤》妫”所謂慎獨,就是在別人...
    尹潤中閱讀 710評論 0 1