Browse Source

【看板展示】【刷新不影响原有定时器事件/chart动态展示】

zhuth 8 years ago
parent
commit
ddbd051b64

+ 5 - 0
kanban-client/README.md

@@ -33,6 +33,11 @@
 * resize之后根据容器大小重新调整charts的展示方式
 * 字体大小梯度调整
 * bar/line图X轴名称倾斜条件调整
+##### 20170921
+* 增加bar/line的X轴名称位置的调整
+* bar/line动态新增测试
+* table如果刷新后的数据无改变,不再重新计算滚屏逻辑/chart图同理,可能在动态数据时会有用
+* line数据过多时动态展示
 #### 运行
 * 本地运行
 ```

+ 99 - 0
kanban-client/app/component/Layout.dev.js

@@ -0,0 +1,99 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { WidthProvider } from 'react-grid-layout';
+var ReactGridLayout = WidthProvider(require('react-grid-layout'));
+import '../assets/layoutStyle.css';
+
+import Form from '../src/Form/index.js';
+import Table from './Table.jsx';
+import Charts from '../src/Charts/ECharts.dev.js';
+
+
+class BasicLayout extends React.Component {
+
+  constructor(props) {
+    super(props);
+    this.newProps = props;
+    var layout = this.generateLayout();
+    this.state = {
+      children: props,
+      layout: layout
+    };
+  }
+
+  static propTypes = {
+    // onLayoutChange: PropTypes.func.require,
+  }
+
+  static defaultProps = {
+    className: "layout",
+    items: [],
+    onLayoutChange: (l) => { },
+    cols: 10, // 屏幕横宽最大值为10
+    margin: [1, 1], // 元素间隔为0
+    verticalCompact: false,
+    useCSSTransforms: false, // 不使用动画
+    autoSize: true
+  }
+
+  // 创建div元素
+  generateDOM() {
+    const { items } = this.newProps;
+
+    return items.map(function (item, i) {
+      let { type, config } = item;
+      if (type == 'form') {
+        return (<div key={i}><Form {...config} /></div>);
+      } else if (type == 'table') {
+        return (<div key={i}><Table {...config} /></div>);
+      } else if (type == 'charts') {
+        return <div key={i}><Charts {...config} /></div>
+      } else {
+        return (<div key={i}><span className="text">{i}</span></div>);
+      }
+    });
+  }
+
+  // 设置每个div的属性
+  generateLayout() {
+    const { items } = this.newProps;
+    return items.map(function (item, i) {
+      let { layout } = item;
+      let { x, y, w, h } = layout;
+      return { x: x, y: y, w: w, h: h, i: i.toString(), isDraggable: false, isResizable: false };
+    });
+  }
+
+  onLayoutChange(layout) {
+    this.newProps.onLayoutChange(layout);
+  }
+
+  componentDidUpdate() {
+  }
+
+  componentDidMount() {
+  }
+
+  componentWillReceiveProps(nextProps) {
+    this.newProps = nextProps;
+    let layout = this.generateLayout();
+
+    this.setState({
+      children: nextProps,
+      layout: layout
+    });
+  }
+
+  render() {
+    return (
+      <ReactGridLayout layout={this.state.layout} onLayoutChange={this.onLayoutChange(this.state.layout)}
+        {...this.state.children}>
+        {this.generateDOM()}
+      </ReactGridLayout>
+    );
+  }
+
+};
+
+module.exports = BasicLayout;
+

+ 27 - 19
kanban-client/app/component/Table.jsx

@@ -2,6 +2,7 @@ import React from 'react';
 import ReactDOM from 'react-dom';
 import Table from '../src/Table/index.js';
 import Animate from 'rc-animate';
+import {isEqual} from '../utils/BaseUtils.js';
 import '../assets/Table/index.less';
 import '../assets/Table/animation.less';
 
@@ -51,16 +52,16 @@ class TableModel extends React.Component {
 	// 调整行
 	adaptiveRowSize() {
 		let node = this.refs.body;
-		let title = node.getElementsByClassName('rc-table-title')[0] || {offsetHeight: 0};
+		let title = node.getElementsByClassName('rc-table-title')[0] || { offsetHeight: 0 };
 		let thead = node.getElementsByClassName('rc-table-thead')[0];
 		this.cHeight = node.offsetHeight - title.offsetHeight - 4;
 		thead.style.fontSize = `${this.rowHeight * .6}px`;
-		thead.style.height = `${this.cHeight/(this.rowCount + 1)}px`;
+		thead.style.height = `${this.cHeight / (this.rowCount + 1)}px`;
 		let count = this.state.data.length;
 		if (count == 0) { return; }
 		let trs = node.getElementsByClassName('fade-enter');
 		for (let i = 0; i < trs.length; i++) {
-			trs[i].style.height = `${this.cHeight/(this.rowCount + 1)}px`;
+			trs[i].style.height = `${this.cHeight / (this.rowCount + 1)}px`;
 			trs[i].style.fontSize = `${this.rowHeight * .6}px`;
 		}
 	}
@@ -68,7 +69,7 @@ class TableModel extends React.Component {
 	// 切换动画
 	switchAnimate() {
 		let node = this.refs.body;
-		let title = node.getElementsByClassName('rc-table-title')[0] || {offsetHeight: 0};
+		let title = node.getElementsByClassName('rc-table-title')[0] || { offsetHeight: 0 };
 		let rows = node.getElementsByClassName('rc-table-row');
 		let alignWidth = 0; // 该值等于((@horizontal-padding)*2+(border宽度*2))
 		if (rows.length > 0) {
@@ -95,7 +96,7 @@ class TableModel extends React.Component {
 					oldTds[j].style.maxWidth = `${newTds[j].offsetWidth - 2}px`;
 					oldTds[j].style.minWidth = `${newTds[j].offsetWidth - 2}px`;
 					oldTds[j].width = newTds[j].offsetWidth - 2;
-					oldTds[j].height = newTds[j].offsetHeight -2;
+					oldTds[j].height = newTds[j].offsetHeight - 2;
 				}
 			}
 		}
@@ -107,19 +108,19 @@ class TableModel extends React.Component {
 	splitData() {
 		const { fontSize, pageSize } = this.newProps;
 		let node = this.refs.body;
-		let title = node.getElementsByClassName('rc-table-title')[0] || {offsetHeight: 0};
+		let title = node.getElementsByClassName('rc-table-title')[0] || { offsetHeight: 0 };
 		let thead = node.getElementsByClassName('rc-table-thead')[0];
 		this.cHeight = node.offsetHeight - title.offsetHeight - thead.offsetHeight;
 		this.cWidth = node.offsetWidth;
-		this.rowCount = (pageSize + 0) || Math.round(this.cHeight/(fontSize / .6));
-		this.rowHeight = this.cHeight/this.rowCount;
+		this.rowCount = (pageSize + 0) || Math.round(this.cHeight / (fontSize / .6));
+		this.rowHeight = this.cHeight / this.rowCount;
 		let a = this.newProps.data;
 		let result = [];
 		let j = 0;
-		for(let i = 0; i<a.length;i = i + j){
+		for (let i = 0; i < a.length; i = i + j) {
 			let arr = [];
-			for(j = 0; j < this.rowCount && a[i+j]; j++) {
-				arr.push(a[i+j]);
+			for (j = 0; j < this.rowCount && a[i + j]; j++) {
+				arr.push(a[i + j]);
 			}
 			result.push(arr);
 		}
@@ -130,23 +131,23 @@ class TableModel extends React.Component {
 
 	setRefresh() {
 		this.changeData();
-		if(this.dataArr.length > 1) {
+		if (this.dataArr.length > 1) {
 			this.timing({
-				intervalFunction: function() {
+				intervalFunction: function () {
 					this.changeData();
 				}.bind(this),
 				intervalTime: this.newProps.refreshInterval * 1000 || 2000
 			});
 		}
 	}
-	
+
 	changeData() {
 		this.setState({
 			data: this.dataArr[this.dataIndex]
-		}, ()=>{
+		}, () => {
 			this.onShow();
 			this.dataIndex++;
-			if(this.dataIndex >= this.dataArr.length){
+			if (this.dataIndex >= this.dataArr.length) {
 				this.dataIndex = 0
 			}
 		});
@@ -169,11 +170,18 @@ class TableModel extends React.Component {
 		this.clearInterval();
 	}
 	componentWillReceiveProps(nextProps) {
+		if (isEqual(nextProps.data, this.newProps.data)) {
+			this.newProps = nextProps;
+			return;
+		}
 		this.clearInterval();
-		this.newProps = nextProps;
 		this.splitData();
 		this.setRefresh();
 	}
+
+	shouldComponentUpdate(nextProps, nextState) {
+			return true;
+	}
 	getBodyWrapper(body) {
 		return (
 			<Animate transitionName="fade" component="tbody" className={body.props.className}
@@ -187,7 +195,7 @@ class TableModel extends React.Component {
 
 	getTitle() {
 		const { title } = this.newProps;
-		return function(state) {
+		return function (state) {
 			return title;
 		}
 	}
@@ -195,7 +203,7 @@ class TableModel extends React.Component {
 	render() {
 		const { fontSize } = this.newProps;
 		return (
-			<div ref='body' style={{height: '100%', overflow: 'hidden', fontSize: fontSize}}>
+			<div ref='body' style={{ height: '100%', overflow: 'hidden', fontSize: fontSize }}>
 				<Table
 					prefixCls={this.newProps.prefixCls || 'rc-table'}
 					className={this.newProps.className}

+ 67 - 48
kanban-client/app/component/converter.js

@@ -2,22 +2,22 @@ function converter(data) {
     let { content } = data;
     let { title, items } = content;
     let itemsarr = items instanceof Array ? items : [items];
-	
+
     let me = this;
     let newItems = itemsarr.map(function (v, i) {
         let type = v.type;
-        if(type == 'form') {
+        if (type == 'form') {
             return formConfig(v);
-        }else if(type == 'table') {
+        } else if (type == 'table') {
             return tableConfig(v);
-        }else if(type == 'bar') {
+        } else if (type == 'bar') {
             return barConfig(v);
-        }else if(type == 'line') {
+        } else if (type == 'line') {
             return lineConfig(v);
-        }else if(type == 'pie') {
+        } else if (type == 'pie') {
             return pieConfig(v);
-        }else {
-            
+        } else {
+
         }
     });
     return {
@@ -37,7 +37,7 @@ function titleConfig(title) {
 
 function formConfig(model) {
     let { type, header, config, layout } = model;
-    let { fontSize, fieldstyle, valuestyle, columns, data} = config;
+    let { fontSize, fieldstyle, valuestyle, columns, data } = config;
     data = data.map((d) => {
         d.field = {
             text: d.field.text,
@@ -66,7 +66,7 @@ function formConfig(model) {
 
 function tableConfig(model) {
     let { type, config, layout } = model;
-    let { fontSize, title, cls, render, columns, data, rowHeight, interval} = config;
+    let { fontSize, title, cls, render, columns, data, rowHeight, interval } = config;
     return {
         type: 'table',
         config: {
@@ -80,7 +80,7 @@ function tableConfig(model) {
                 v.render = renderFunction(v.render);
                 return v;
             }),
-            data: data.map((v, i)=> {
+            data: data.map((v, i) => {
                 v.key = i;
                 return v;
             })
@@ -91,9 +91,9 @@ function tableConfig(model) {
 
 function barConfig(model) {
     let { type, config, layout } = model;
-    let { fontSize, title, subtitle, xtitle, xtype, xfields, ytitle, ytype, yfields, series} = config;
-    
-    let xf = (xfields instanceof Array) ? xfields : (xfields.replace(['['],'').replace([']'],'').split(','));
+    let { fontSize, title, subtitle, xtitle, xtype, xfields, ytitle, ytype, yfields, series } = config;
+
+    let xf = (xfields instanceof Array) ? xfields : (xfields.replace(['['], '').replace([']'], '').split(','));
     return {
         type: 'charts',
         config: {
@@ -128,11 +128,12 @@ function barConfig(model) {
                     name: xtitle,
                     nameGap: 5,
                     nameRotate: 270,
+                    nameLocation: 'end',
                     nameTextStyle: {
                         fontSize: getFontSize() * .7
                     },
                     axisLabel: {
-                        rotate:  getScreenSize().width * layout.w / xf.length / 100 < 80 ? 45 : 0,
+                        rotate: getScreenSize().width * layout.w / xf.length / 100 < 80 ? 45 : 0,
                         interval: 0,
                         textStyle: {
                             fontSize: getFontSize() * .7
@@ -161,8 +162,8 @@ function barConfig(model) {
 
 function lineConfig(model) {
     let { type, config, layout } = model;
-    let { fontSize, title, subtitle, xtitle, xtype, xfields, ytitle, ytype, yfields, series} = config;
-    let xf = (xfields instanceof Array) ? xfields : (xfields.replace(['['],'').replace([']'],'').split(','));
+    let { fontSize, title, subtitle, xtitle, xtype, xfields, ytitle, ytype, yfields, series } = config;
+    let xf = (xfields instanceof Array) ? xfields : (xfields.replace(['['], '').replace([']'], '').split(','));
     return {
         type: 'charts',
         config: {
@@ -189,16 +190,17 @@ function lineConfig(model) {
                     })
                 },
                 xAxis: [{
-                    type : xtype,
-                    data : xf,
+                    type: xtype,
+                    data: xf,
                     name: xtitle,
                     nameRotate: 270,
+                    nameLocation: 'end',
                     nameGap: 5,
                     nameTextStyle: {
                         fontSize: fontSize || getFontSize() * .7
                     },
                     axisLabel: {
-                        rotate:  getScreenSize().width * layout.w / xf.length / 100 < 80 ? 45 : 0,
+                        rotate: getScreenSize().width * layout.w / xf.length / 100 < 80 ? 45 : 0,
                         interval: 0,
                         textStyle: {
                             fontSize: fontSize || getFontSize() * .7
@@ -218,7 +220,16 @@ function lineConfig(model) {
                         }
                     }
                 }],
-                series: getLineSeries(fontSize, series)
+                series: getLineSeries(fontSize, series),
+                dataZoom: [
+                    {
+                        type: 'slider',
+                        show: false,
+                        xAxisIndex: [0],
+                        start: 0,
+                        endValue: Math.round(getScreenSize().width * layout.w / 100 / 60) >= series[0].data.length ? series[0].data.length - 1 : Math.round(getScreenSize().width * layout.w / 100 / 60),
+                    }
+                ],
             }
         },
         layout: getLayout(layout)
@@ -227,7 +238,7 @@ function lineConfig(model) {
 
 function pieConfig(model) {
     let { type, config, layout } = model;
-    let { fontSize, title, subtitle, series} = config;
+    let { fontSize, title, subtitle, series } = config;
     series = series.map((v, i) => {
         v.value = v.data;
         return v;
@@ -252,8 +263,8 @@ function pieConfig(model) {
 function renderFunction(funcStr) {
     let func = function (_v, _r, _i) { return { children: _v, props: {} } };
     try {
-        func = (new Function("return "+funcStr))();
-    }catch(ex) {
+        func = (new Function("return " + funcStr))();
+    } catch (ex) {
         // console.log('parsing failed', ex);
     }
     return func;
@@ -298,7 +309,7 @@ function getBarSeries(fontSize, layout, series) {
         barGap: 0
     }
     s = series.map((v, i) => {
-        let m = Object.assign({},model);
+        let m = Object.assign({}, model);
         m.name = v.name;
         m.data = v.data instanceof Array ? v.data : [v.data];
         return m;
@@ -310,6 +321,20 @@ function getLineSeries(fontSize, series) {
     let s = [];
     const model = {
         type: 'line',
+        smooth: false,
+        markLine: {
+            symbol: '',
+            label: {
+                normal: {
+                    show: false
+                }
+            },
+            data: [{
+                name: '起始位置',
+                yAxis: null,
+                xAxis: ''
+            }]
+        },
         label: {
             normal: {
                 show: true,
@@ -322,7 +347,7 @@ function getLineSeries(fontSize, series) {
         }
     }
     s = series.map((v, i) => {
-        let m = Object.assign({},model);
+        let m = Object.assign({}, model);
         m.name = v.name;
         m.data = v.data instanceof Array ? v.data : [v.data];
         return m;
@@ -334,8 +359,6 @@ function getPieSeries(fontSize, layout, series) {
     let data = series instanceof Array ? series : [series];
     const model = {
         type: 'pie',
-        radius: `${layout.w * .7}%`,
-        center: getScreenSize().height * layout.h / 100 > 400 ? ['50%', '60%'] : (layout.w * getScreenSize().width / 100 < 450 ? ['50%', '55%'] : ['35%', '50%']),
         label: {
             normal: {
                 textStyle: {
@@ -345,18 +368,14 @@ function getPieSeries(fontSize, layout, series) {
             }
         }
     }
-    let s = Object.assign({},model);
+    let s = Object.assign({}, model);
     s.name = '';
-    s.data = data.map((d, i) => {d.name = d.name || 'Unkonw';return d;});
+    s.data = data.map((d, i) => { d.name = d.name || 'Unkonw'; return d; });
     return [s];
 }
 
 function getPieLegend(fontSize, layout, series) {
     let legend = {
-        show: (layout.w * getScreenSize().width / 100 > 450 || layout.h * getScreenSize().height / 100 > 400),
-        top: (getScreenSize().height * layout.h / 100 > 400 ? '20%' : '15%'),
-        right: (getScreenSize().height * layout.h / 100 > 400 ? 'auto' : '5%'),
-        orient: (getScreenSize().height * layout.h / 100 > 400 ? 'horizontal' : 'vertical'),
         padding: 0,
         itemGap: layout.w / 10,
         textStyle: {
@@ -371,32 +390,32 @@ function getPieLegend(fontSize, layout, series) {
 
 function getLayout(layout) {
     let l = {};
-    for(let k in layout) {
-        l[k] = layout[k]/10
+    for (let k in layout) {
+        l[k] = layout[k] / 10
     }
     return l;
 }
 
 function parseStyleStr(str) {
-    if(!str) {
+    if (!str) {
         return {};
     }
-    if(typeof str == 'object') {
+    if (typeof str == 'object') {
         return str;
     }
-    str = str+'';
-    str = str.replace(/\s+/g,"");
+    str = str + '';
+    str = str.replace(/\s+/g, "");
     let arr = str.split(';');
-    let objArr = arr.map(function(v, i) {
+    let objArr = arr.map(function (v, i) {
         let arr = v.split(':');
         let obj = {};
         obj[arr[0]] = arr[1];
         return obj
     });
     let obj = {}
-    objArr.map(function(v,i) {
-        for(let k in v) {
-            if(k) {
+    objArr.map(function (v, i) {
+        for (let k in v) {
+            if (k) {
                 obj[k] = v[k]
             }
         }
@@ -408,12 +427,12 @@ function getScreenSize() {
     let root = document.getElementById('root');
     let height = root.offsetHeight;
     let width = root.offsetWidth;
-    return {height, width};
+    return { height, width };
 }
 
 function getFontSize() {
-    let {height, width} = getScreenSize();
-    return width / 300 * 2 + 16 + Math.round(width/1000) * 2;
+    let { height, width } = getScreenSize();
+    return width / 300 * 2 + 16 + Math.round(width / 1000) * 2;
 }
 
-export {converter};
+export { converter };

+ 3 - 4
kanban-client/app/component/factory.dev.js

@@ -1,18 +1,17 @@
 import React from 'react';
-import Container from './Layout.js';
+import Container from './Layout.dev.js';
 import Title from '../src/Title/Title.jsx';
 import MessageBox from '../src/MsgBox/MessageBox.jsx';
-import DateFormatter from '../utils/DateTimeUtils.js';
 import { converter } from './converter.js';
 import URL from '../constants/url.dev.json';
 
-import tempdata from '../data/tempdata.json';
+import tempdata from '../data/testline.json';
 
 class Factory extends React.Component {
 
     constructor(props) {
         super(props);
-        this.dev = 'local';
+        this.dev = 'local ';
         this.index = 0;
         this.state = {
             titleHeight: 0,

+ 0 - 1
kanban-client/app/component/factory.js

@@ -2,7 +2,6 @@ import React from 'react';
 import Container from './Layout.js';
 import Title from '../src/Title/Title.jsx';
 import MessageBox from '../src/MsgBox/MessageBox.jsx';
-import DateFormatter from '../utils/DateTimeUtils.js';
 import { converter } from './converter.js';
 import URL from '../constants/url.json';
 

+ 2 - 2
kanban-client/app/data/testbar.json

@@ -4,7 +4,7 @@
             "content": {
                 "items": {
                     "layout": {
-                        "w": 40,
+                        "w": 80,
                         "h": 100,
                         "y": 0,
                         "x": 0
@@ -38,7 +38,7 @@
                         "xtype": "category",
                         "subtitle": "柱状图",
                         "ytitle": "项目数量统计",
-                        "xfields": "[顾群2, 顾群, 杨若楠, 陈金金, 陈虎, USER1, USER2, USER3, USER4, uS]"
+                        "xfields": "[顾群2, 顾群, 杨若楠, 陈金金, 陈虎]"
                     },
                     "type": "bar"
                 }

+ 3 - 3
kanban-client/app/data/testline.json

@@ -5,8 +5,8 @@
                 "items": [
                     {
                         "layout": {
-                            "w": 40,
-                            "h": 40,
+                            "w": 100,
+                            "h": 100,
                             "y": 0,
                             "x": 0
                         },
@@ -15,7 +15,7 @@
                                 {
                                     "name": "已启动",
                                     "data": [
-                                        0,
+                                        "",
                                         2,
                                         1,
                                         0,

+ 1 - 0
kanban-client/app/main.dev.js

@@ -1,6 +1,7 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
 import Factory from './component/factory.dev.js';
+import DateFormatter from './utils/DateTimeUtils.js';
 
 var code = '547491E8D11';
 

+ 1 - 0
kanban-client/app/main.js

@@ -1,6 +1,7 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
 import Factory from './component/factory.js';
+import DateFormatter from './utils/DateTimeUtils.js';
 
 var code = window.location.search.substring(6);
 

+ 112 - 0
kanban-client/app/src/Charts/ECharts.dev.js

@@ -0,0 +1,112 @@
+import React, { Component } from 'react';
+import {isEqual} from '../../utils/BaseUtils.js';
+import ReactEcharts from 'echarts-for-react';
+import reset from './ResetCharts.js';
+import {dark} from './Theme/Theme.js';
+
+export class ReactEchart extends React.Component {
+    constructor(props) {
+        super(props);
+        this.newProps = props;
+        this.maxCount = 0;
+        this.state = ({
+            theme: this.newProps.theme,
+            option: this.newProps.option
+        });
+    }
+
+    static defaultProps = {
+        theme: 'dark',
+        showCount: 0
+    }
+    
+    fullData() {
+        let { option } = this.state;
+        // let count = option.series[0].data.length;
+        // while(count <= 22) {
+            option.series = option.series.map((s, i) => {
+                // s.data.push(Math.round(Math.random()*99 + 1));
+                s.data.push(null);
+                return s;
+            });
+            // option.xAxis[0].data.push(new Date().format('HH:mm:'+count));
+            option.xAxis[0].data.push('');
+            // count ++;
+        // }
+    }
+
+    splitData() {
+        let { option } = this.state;
+        let node = this.echarts_react.echartsElement;
+        let cWidth = node.offsetWidth;
+        this.showCount = this.newProps.showCount == -1 ? option.series[0].data.length : (this.newProps.showCount || Math.round(cWidth / 60));
+        option.series.map((s, i) => {
+            this.maxCount = s.data.length > this.maxCount ? s.data.length : this.maxCount;
+        });
+    }
+
+    reset() {
+        let node = this.echarts_react.echartsElement;
+        this.setState({
+            option: reset(this.newProps.option, node, this.showCount)
+        });
+    }
+
+    scroll() {
+        let { option } = this.state;
+        let series = option.series.map((s, i) => {
+            s.data.push(s.data[0]);
+            s.data.splice(0, 1);
+            return s;
+        });
+        option.series = series;
+        option.xAxis[0].data.push(option.xAxis[0].data[0]);
+        option.xAxis[0].data.splice(0, 1);
+        let node = this.echarts_react.echartsElement;
+        this.setState({
+            option: reset(option,node,this.showCount)
+        });
+    }
+
+    componentDidMount() {
+        let { option } = this.state;
+        if(option.series[0].type == 'line') {
+            this.splitData();
+            if(this.maxCount > this.showCount + 1) {
+                this.fullData();
+                this.RK = window.setInterval(this.scroll.bind(this), 1000);
+            }
+        }
+        this.reset();
+    }
+
+    componentDidUpdate() {
+    }
+
+    componentWillUnmount () {
+        if(this.RK) {
+            window.clearInterval(this.RK);
+        }
+    }
+
+    componentWillReceiveProps(nextProps) {
+        if(isEqual(nextProps.option.series, this.state.option.series)) {
+            this.newProps = nextProps;
+            return;
+        }
+        this.reset();
+    }
+
+    render() {
+        return (
+            <ReactEcharts ref={(e) => { this.echarts_react = e; }}
+            option={this.state.option}
+            style={{height: '100%', width: '100%'}}
+            className='rc-echarts'
+            theme={this.state.theme}
+             />
+        )
+    }
+}
+
+export default ReactEchart;

+ 55 - 4
kanban-client/app/src/Charts/ECharts.js

@@ -1,5 +1,5 @@
 import React, { Component } from 'react';
-// 导入echarts
+import {isEqual} from '../../utils/BaseUtils.js';
 import ReactEcharts from 'echarts-for-react';
 import reset from './ResetCharts.js';
 import {dark} from './Theme/Theme.js';
@@ -8,6 +8,7 @@ export class ReactEchart extends React.Component {
     constructor(props) {
         super(props);
         this.newProps = props;
+        this.maxCount = 0;
         this.state = ({
             theme: this.newProps.theme,
             option: this.newProps.option
@@ -15,17 +16,61 @@ export class ReactEchart extends React.Component {
     }
 
     static defaultProps = {
-        theme: 'dark'
+        theme: 'dark',
+        showCount: 0
+    }
+    
+    fullData() {
+        let { option } = this.state;
+        option.series = option.series.map((s, i) => {
+            s.data.push(null);
+            return s;
+        });
+        option.xAxis[0].data.push('');
+    }
+
+    setShowCount() {
+        let { option } = this.state;
+        let node = this.echarts_react.echartsElement;
+        let cWidth = node.offsetWidth;
+        this.showCount = this.newProps.showCount == -1 ? option.series[0].data.length : (this.newProps.showCount || Math.round(cWidth / 60));
+        option.series.map((s, i) => {
+            this.maxCount = s.data.length > this.maxCount ? s.data.length : this.maxCount;
+        });
     }
 
     reset() {
         let node = this.echarts_react.echartsElement;
         this.setState({
-            option: reset(this.newProps.option,node)
+            option: reset(this.newProps.option, node, this.showCount)
+        });
+    }
+
+    scroll() {
+        let { option } = this.state;
+        let series = option.series.map((s, i) => {
+            s.data.push(s.data[0]);
+            s.data.splice(0, 1);
+            return s;
+        });
+        option.series = series;
+        option.xAxis[0].data.push(option.xAxis[0].data[0]);
+        option.xAxis[0].data.splice(0, 1);
+        let node = this.echarts_react.echartsElement;
+        this.setState({
+            option: reset(option,node,this.showCount)
         });
     }
 
     componentDidMount() {
+        let { option } = this.state;
+        if(option.series[0].type == 'line') {
+            this.setShowCount();
+            if(this.maxCount > this.showCount + 1) {
+                this.fullData();
+                this.RK = window.setInterval(this.scroll.bind(this), 1000);
+            }
+        }
         this.reset();
     }
 
@@ -33,10 +78,16 @@ export class ReactEchart extends React.Component {
     }
 
     componentWillUnmount () {
+        if(this.RK) {
+            window.clearInterval(this.RK);
+        }
     }
 
     componentWillReceiveProps(nextProps) {
-        this.newProps = nextProps;
+        if(isEqual(nextProps.option.series, this.state.option.series)) {
+            this.newProps = nextProps;
+            return;
+        }
         this.reset();
     }
 

+ 21 - 8
kanban-client/app/src/Charts/ResetCharts.js

@@ -1,10 +1,10 @@
-export default function resetchart(option, node) {
+export default function resetchart(option, node, sc) {
     
     let type = option.series[0].type;
     if(type == 'bar') {
         return resetBarOption(option, node);
     }else if(type == 'line') {
-        return resetLineOption(option, node);
+        return resetLineOption(option, node, sc);
     }else if(type == 'pie') {
         return resetPieOption(option, node);
     }else {
@@ -17,23 +17,36 @@ function resetBarOption(option, node) {
     option.grid.top = node.offsetHeight < 310 ? '35%' : '28%';
     option.grid.bottom = node.offsetHeight < 310 ? '20%' : '16%';
     option.legend.top = node.offsetHeight < 310 ? '20%' : '18%';
+    if(node.offsetHeight >= 450 && node.offsetWidth / option.xAxis[0].data.length >= 80) {
+        option.xAxis[0].nameLocation = 'middle';
+        option.xAxis[0].nameRotate = 0;
+        option.xAxis[0].nameGap = node.offsetHeight/20;
+    }
     option.xAxis[0].axisLabel.rotate = node.offsetWidth / option.xAxis[0].data.length < 80 ? 45 : 0;
     return option;
 }
 
-function resetLineOption(option, node) {
+function resetLineOption(option, node, sc) {
+    console.log(node.offsetHeight);
     option.grid.top = node.offsetHeight < 310 ? '35%' : '28%';
     option.grid.bottom = node.offsetHeight < 310 ? '20%' : '16%';
     option.legend.top = node.offsetHeight < 310 ? '20%' : '18%';
+    if(node.offsetHeight >= 450 && node.offsetWidth / option.xAxis[0].data.length >= 80) {
+        option.xAxis[0].nameLocation = 'middle';
+        option.xAxis[0].nameRotate = 0;
+        option.xAxis[0].nameGap = node.offsetHeight/20;
+    }
     option.xAxis[0].axisLabel.rotate = node.offsetWidth / option.xAxis[0].data.length < 80 ? 45 : 0;
+    option.dataZoom[0].endValue = sc >= option.xAxis[0].data.length ? option.xAxis[0].data.length - 1 : sc;
     return option;
 }
 
 function resetPieOption(option, node) {
-    option.legend.show = (node.offsetWidth > 450 || node.offsetHeight > 400);
-    option.legend.top = (node.offsetHeight > 400 ? '20%' : '15%');
-    option.legend.right = (node.offsetHeight > 400 ? 'auto' : '5%');
-    option.legend.orient = (node.offsetHeight > 400 ? 'horizontal' : 'vertical');
-    option.series[0].center = node.offsetHeight > 400 ? ['50%', '60%'] : (node.offsetHeight < 450 ? ['50%', '55%'] : ['35%', '50%']);
+    option.legend.show = node.offsetHeight > 500;
+    option.legend.top = (node.offsetHeight > 500 ? '20%' : '15%');
+    option.legend.right = (node.offsetHeight > 500 ? 'auto' : '5%');
+    option.legend.orient = (node.offsetHeight > 500 ? 'horizontal' : 'vertical');
+    option.series[0].radius = `${node.offsetHeight/20 > 50 ? 50 : node.offsetHeight/20}%`;
+    option.series[0].center = node.offsetHeight > 500 ? ['50%', '65%'] : ['50%', '55%'] ;
     return option;
 }

+ 0 - 27
kanban-client/app/utils/ArrayUtils.js

@@ -1,27 +0,0 @@
-import firstBy from'thenby';
-
-/**
- * 对象数组排序
- * @param {*} arr 
- * @param {*} keys {key,direction}
- */
-function sort(arr, keys){
-    if(keys.length == 0) {return arr;}
-    let compare = firstBy(keys[0].key, keys[0].direction || 1);
-    for(let i = 1; i< keys.length; i++) {
-        compare = compare.thenBy(keys[i].key, keys[i].direction || 1);
-    }
-    arr.sort(compare);
-    return arr;
-}
-function remove(arr, val) {
-    var index = arr.indexOf(val);
-    if (index > -1) {
-        arr.splice(index, 1);
-    }
-    return arr;
-}
-
-// module.exports = sort;
-export { sort, remove};
-

+ 95 - 0
kanban-client/app/utils/BaseUtils.js

@@ -0,0 +1,95 @@
+import firstBy from'thenby';
+
+/**
+ * 对象数组排序
+ * @param {*} arr 
+ * @param {*} keys {key,direction}
+ */
+function sort(arr, keys){
+    if(keys.length == 0) {return arr;}
+    let compare = firstBy(keys[0].key, keys[0].direction || 1);
+    for(let i = 1; i< keys.length; i++) {
+        compare = compare.thenBy(keys[i].key, keys[i].direction || 1);
+    }
+    arr.sort(compare);
+    return arr;
+}
+/**
+ * 删除数组某个值
+ * @param {*} arr 
+ * @param {*} val 
+ */
+function remove(arr, val) {
+    var index = arr.indexOf(val);
+    if (index > -1) {
+        arr.splice(index, 1);
+    }
+    return arr;
+}
+/**
+ * 判断两个对象是否相等
+ * @param {*} a 
+ * @param {*} b 
+ */
+function isEqual(a,b){
+    //如果a和b本来就全等
+    if(a===b){
+        //判断是否为0和-0
+        return a !== 0 || 1/a ===1/b;
+    }
+    //判断是否为null和undefined
+    if(a==null||b==null){
+        return a===b;
+    }
+    //接下来判断a和b的数据类型
+    var classNameA=toString.call(a),
+    classNameB=toString.call(b);
+    //如果数据类型不相等,则返回false
+    if(classNameA !== classNameB){
+        return false;
+    }
+    //如果数据类型相等,再根据不同数据类型分别判断
+    switch(classNameA){
+        case '[object RegExp]':
+        case '[object String]':
+        //进行字符串转换比较
+        return '' + a ==='' + b;
+        case '[object Number]':
+        //进行数字转换比较,判断是否为NaN
+        if(+a !== +a){
+            return +b !== +b;
+        }
+        //判断是否为0或-0
+        return +a === 0?1/ +a === 1/b : +a === +b;
+        case '[object Date]':
+        case '[object Boolean]':
+        return +a === +b;
+    }
+    //如果是对象类型
+    if(classNameA == '[object Object]'){
+        //获取a和b的属性长度
+        var propsA = Object.getOwnPropertyNames(a),
+        propsB = Object.getOwnPropertyNames(b);
+        if(propsA.length != propsB.length){
+            return false;
+        }
+        for(var i=0;i<propsA.length;i++){
+            var propName=propsA[i];
+            //如果对应属性对应值不相等,则返回false
+            if(a[propName] !== b[propName]){
+                return false;
+            }
+        }
+        return true;
+    }
+    //如果是数组类型
+    if(classNameA == '[object Array]'){
+        if(a.toString() == b.toString()){
+            return true;
+        }
+        return false;
+    }
+}
+
+
+export { sort, remove, isEqual};