king
2020-07-16 9a82bce9cf0ed4a51c1b0b0669eaa38cedbace07
src/tabviews/zshare/chartcomponent/index.jsx
@@ -3,30 +3,80 @@
import { is, fromJS } from 'immutable'
import { Chart } from '@antv/g2'
import DataSet from '@antv/data-set'
import { Spin, Empty, Select } from 'antd'
import asyncComponent from './asyncButtonComponent'
import Utils from '@/utils/utils.js'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import './index.scss'
const ExcelOutButton = asyncComponent(() => import('@/tabviews/zshare/actionList/exceloutbutton'))
const ExcelInButton = asyncComponent(() => import('@/tabviews/zshare/actionList/excelInbutton'))
class LineChart extends Component {
  static propTpyes = {
    BID: PropTypes.any,
    Tab: PropTypes.any,
    plot: PropTypes.object,
    data: PropTypes.array,
    config: PropTypes.object
    loading: PropTypes.bool,
    config: PropTypes.object,
    getexceloutparam: PropTypes.func
  }
  state = {
    dict: (!localStorage.getItem('lang') || localStorage.getItem('lang') === 'zh-CN') ? zhCN : enUS,
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    empty: true,
    actions: [],
    chartId: Utils.getuuid(),
    chartData: [],
    chartFields: [],
    selectFields: []
  }
  componentDidMount () {
    this.viewrender()
    const { plot, data, config } = this.props
    let _state = {}
    let actions = []
    config.action.forEach(item => {
      if (!plot.actions || plot.actions.length === 0) return
      if (!(item.OpenType === 'excelOut' || (item.OpenType === 'excelIn' && item.Ot === 'notRequired'))) return
      if (plot.actions.includes(item.uuid)) {
        actions.push(fromJS(item).toJS())
      }
    })
    if (plot.datatype === 'statistics' && (plot.chartType === 'line' || plot.chartType === 'bar')) {
      let result = this.getStaticMsg(data)
      _state.chartData = result.data
      _state.chartFields = result.chartFields
      _state.selectFields = result.selectFields
      _state.actions = actions
      this.setState(_state, () => {
        this.viewrender()
      })
    } else {
      this.setState({ actions })
      this.viewrender()
    }
  }
  UNSAFE_componentWillReceiveProps (nextProps) {
    const { plot } = this.props
    if (!is(fromJS(this.props.data), fromJS(nextProps.data))) {
      this.setState({}, () => {
        let _element = document.getElementById(this.props.plot.uuid)
      let _state = {}
      if (plot.datatype === 'statistics' && (plot.chartType === 'line' || plot.chartType === 'bar')) {
        let result = this.getStaticMsg(nextProps.data)
        _state.chartData = result.data
        _state.chartFields = result.chartFields
        _state.selectFields = result.selectFields
      }
      this.setState(_state, () => {
        let _element = document.getElementById(this.state.chartId)
        if (_element) {
          _element.innerHTML = ''
        }
@@ -35,13 +85,19 @@
    }
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  getdata = () => {
    const { data, plot, config } = this.props
    let vFields = plot.Yaxis && typeof(plot.Yaxis) === 'string' ? [plot.Yaxis] : plot.Yaxis
    let _columns = config.columns.filter(col => vFields.includes(col.field))
    if (!data) return []
    if (!data) {
      this.setState({empty: true})
      return []
    }
    let _data = []
    let _cdata = fromJS(data).toJS()
@@ -70,6 +126,7 @@
          vFields.forEach(field => {
            _item[field] += item[field]
          })
          _mdata.set(item[plot.Xaxis], _item)
        }
      })
@@ -104,6 +161,7 @@
          vFields.forEach(field => {
            _item[field] += item[field]
          })
          _mdata.set(item[plot.Xaxis], _item)
        }
      })
@@ -139,6 +197,171 @@
      _data = [..._mdata.values()]
    }
    if (plot.correction && plot.chartType === 'bar' && _data.length > 0 && _data.length < plot.correction) {
      if (plot.enabled !== 'true' || (plot.customs && plot.customs.filter(cell => cell.chartType !== 'bar').length === 0)) {
        let _num = plot.correction - _data.length
        for (let i = 0; i < _num; i++) {
          let _val = Array( i + 2 ).join(' ')
          let _cell = {}
          _cell[plot.Xaxis] = _val
          _columns.forEach(col => {
            _cell[col.field] = ''
          })
          _data.push(_cell)
        }
      }
    }
    this.setState({empty: _data.length === 0})
    return _data
  }
  getStaticMsg = (data) => {
    const { plot, config } = this.props
    let _column = config.columns.filter(col => plot.InfoValue === col.field)[0]
    let percent = false
    let decimal = 0
    if (_column && _column.format === 'percent') {
      percent = true
    }
    if (_column) {
      decimal = _column.decimal
    }
    if (!data) {
      this.setState({empty: true})
      return {data: [], chartFields: [], selectFields: []}
    }
    let _data = []
    let _cdata = fromJS(data).toJS()
    let _chartFields = []
    let _selectFields = []
    if (plot.repeat === 'average') {
      let _mdata = new Map()
      _cdata.forEach(item => {
        if (!item[plot.InfoType] || !item[plot.Xaxis]) return
        _chartFields.push(item[plot.InfoType])
        item.$uuid = item[plot.InfoType] + item[plot.Xaxis]
        if (typeof(item[plot.InfoValue]) !== 'number') {
          item[plot.InfoValue] = parseFloat(plot.InfoValue)
          if (isNaN(item[plot.InfoValue])) {
            item[plot.InfoValue] = 0
          }
        }
        if (percent) {
          item[plot.InfoValue] = item[plot.InfoValue] * 100
        }
        if (!_mdata.has(item.$uuid)) {
          item.$count = 1
          _mdata.set(item.$uuid, item)
        } else {
          let _item = _mdata.get(item.$uuid)
          _item.$count++
          _item[plot.InfoValue] += item[plot.InfoValue]
          _mdata.set(item.$uuid, _item)
        }
      })
      _data = [..._mdata.values()]
      _data = _data.map(item => {
        item[plot.InfoValue] = item[plot.InfoValue] / item.$count
        item[plot.InfoValue] = item[plot.InfoValue].toFixed(decimal)
        item[plot.InfoValue] = +item[plot.InfoValue]
        return item
      })
    } else if (plot.repeat === 'cumsum') {
      let _mdata = new Map()
      _cdata.forEach(item => {
        if (!item[plot.InfoType] || !item[plot.Xaxis]) return
        _chartFields.push(item[plot.InfoType])
        item.$uuid = item[plot.InfoType] + item[plot.Xaxis]
        if (typeof(item[plot.InfoValue]) !== 'number') {
          item[plot.InfoValue] = parseFloat(plot.InfoValue)
          if (isNaN(item[plot.InfoValue])) {
            item[plot.InfoValue] = 0
          }
        }
        if (percent) {
          item[plot.InfoValue] = item[plot.InfoValue] * 100
        }
        if (!_mdata.has(item.$uuid)) {
          _mdata.set(item.$uuid, item)
        } else {
          let _item = _mdata.get(item.$uuid)
          _item[plot.InfoValue] += item[plot.InfoValue]
          _mdata.set(item.$uuid, _item)
        }
      })
      _data = [..._mdata.values()]
      _data = _data.map(item => {
        item[plot.InfoValue] = item[plot.InfoValue].toFixed(decimal)
        item[plot.InfoValue] = +item[plot.InfoValue]
        return item
      })
    } else { // plot.repeat === 'unrepeat'
      let _mdata = new Map()
      _cdata.forEach(item => {
        if (!item[plot.InfoType] || !item[plot.Xaxis]) return
        _chartFields.push(item[plot.InfoType])
        item.$uuid = item[plot.InfoType] + item[plot.Xaxis]
        if (!_mdata.has(item.$uuid)) {
          if (typeof(item[plot.InfoValue]) !== 'number') {
            item[plot.InfoValue] = parseFloat(plot.InfoValue)
            if (isNaN(item[plot.InfoValue])) {
              item[plot.InfoValue] = 0
            }
          }
          if (percent) {
            item[plot.InfoValue] = item[plot.InfoValue] * 100
          }
          item[plot.InfoValue] = item[plot.InfoValue].toFixed(decimal)
          item[plot.InfoValue] = +item[plot.InfoValue]
          _mdata.set(item.$uuid, item)
        }
      })
      _data = [..._mdata.values()]
    }
    _chartFields = Array.from(new Set(_chartFields))
    if (plot.InfoDefNumber >= _chartFields.length) {
      _selectFields = _chartFields
    } else {
      _selectFields = _chartFields.slice(0, plot.InfoDefNumber)
    }
    return {data: _data, chartFields: _chartFields, selectFields: _selectFields}
  }
  getStaticData = () => {
    const { plot } = this.props
    const { chartData, chartFields, selectFields } = this.state
    let _data = []
    if (selectFields.length === chartFields.length) {
      _data = chartData
    } else {
      _data = chartData.filter(item => selectFields.includes(item[plot.InfoType]))
    }
    this.setState({empty: _data.length === 0})
    return _data
  }
@@ -157,54 +380,81 @@
  linerender = () => {
    const { plot, config } = this.props
    let transfield = {}
    config.columns.forEach(col => {
      if (col.field) {
        transfield[col.field] = col.label
      }
    })
    // const colors = ['#f49d37', '#f03838', '#35d1d1', '#5be56b', '#4e7af0', '#ebcc21']
    let X_axis = plot.Xaxis
    let Y_axis = plot.Yaxis
    let _data = []
    let _valfield = 'value'
    let _typefield = 'key'
    let data = this.getdata()
    if (plot.datatype === 'statistics') {
      _valfield = plot.InfoValue
      _typefield = plot.InfoType
    const ds = new DataSet()
    const dv = ds.createView().source(data)
    dv.transform({
      type: 'fold',
      fields: [...Y_axis],
      key: 'key', // key字段
      value: 'value', // value字段
      // retains: [], // 保留字段集,默认为除 fields 以外的所有字段
    })
    if (plot.Xaxis) {
      dv.transform({
        type: 'map',
        callback(row) {
          row.key = transfield[row.key]
          return row
        },
      _data = this.getStaticData()
    } else {
      let transfield = {}
      config.columns.forEach(col => {
        if (col.field) {
          transfield[col.field] = col.label
        }
      })
      let data = this.getdata()
      if (plot.enabled === 'true') {
        this.customrender(data, transfield)
        return
      }
      const ds = new DataSet()
      const dv = ds.createView().source(data)
      dv.transform({
        type: 'fold',
        fields: [...plot.Yaxis],
        key: 'key', // key字段
        value: _valfield, // value字段
        // retains: [], // 保留字段集,默认为除 fields 以外的所有字段
      })
      if (plot.Xaxis) {
        dv.transform({
          type: 'map',
          callback(row) {
            row.key = transfield[row.key]
            return row
          },
        })
      }
      _data = dv.rows
    }
    
    const chart = new Chart({
      container: plot.uuid,
      container: this.state.chartId,
      autoFit: true,
      height: plot.height || 400
    })
    chart.data(dv.rows)
    chart.data(_data)
    if (plot.coordinate !== 'polar') {
      chart.scale(X_axis, {
      chart.scale(plot.Xaxis, {
        range: [0, 1]
      })
    }
    chart.scale('value', {
    chart.scale(_valfield, {
      nice: true
    })
    // 坐标轴格式化
    chart.axis(plot.Xaxis, {
      label: {
        formatter: (val) => {
          if (!val || /^\s*$/.test(val)) return val
          let _val = `${val}`
          if (_val.length <= 10) return val
          return _val.substring(0, 7) + '...'
        }
      }
    })
    if (!plot.legend || plot.legend === 'hidden') {
@@ -236,19 +486,19 @@
    let _chart = chart
      .line()
      .position(`${X_axis}*value`)
      .color('key')
      .position(`${plot.Xaxis}*${_valfield}`)
      .color(_typefield)
      .shape(plot.shape || 'smooth')
    if (plot.label === 'true') {
      _chart.label('value')
      _chart.label(_valfield)
    }
    if (plot.point === 'true') {
      chart
        .point()
        .position(`${X_axis}*value`)
        .color('key')
        .position(`${plot.Xaxis}*${_valfield}`)
        .color(_typefield)
        .size(3)
        .shape('circle')
    }
@@ -256,57 +506,88 @@
    chart.render()
  }
  barrender = () => {
    const { plot, config } = this.props
  customrender = (data, transfield) => {
    const { plot } = this.props
    let transfield = {}
    config.columns.forEach(col => {
      if (col.field) {
        transfield[col.field] = col.label
    let barfields = []
    let fields = []
    let legends = []
    plot.customs.forEach(item => {
      item.name = transfield[item.field] || item.field
      if (item.axis === 'left') {
        item.index = 0
      } else if (item.axis === 'right') {
        item.index = 1
      } else {
        item.index = 2
      }
    })
    let X_axis = plot.Xaxis
    let Y_axis = plot.Yaxis
    let data = this.getdata()
      if (item.chartType === 'bar') {
        barfields.push(item.field)
        fields.unshift(item)
      } else {
        fields.push(item)
      }
      legends.push({
        value: item.name,
        name: item.name,
        marker: { symbol: item.chartType === 'bar' ? 'square' : 'hyphen', style: { stroke: item.color,fill: item.color, r: 5, lineWidth: 2 } }
      })
    })
    fields.sort((a, b) => a.index - b.index)
    const ds = new DataSet()
    const dv = ds.createView().source(data)
    dv.transform({
      type: 'fold',
      fields: [...Y_axis],
      key: 'key',
      value: 'value'
      type: 'map',
      callback(row) {
        fields.forEach(line => {
          row[line.name] = row[line.field]
        })
        return row
      }
    })
    if (plot.Xaxis) {
      dv.transform({
        type: 'map',
        callback(row) {
          row.key = transfield[row.key]
          return row
        },
      })
    }
    const chart = new Chart({
      container: plot.uuid,
      container: this.state.chartId,
      autoFit: true,
      height: plot.height || 400
    })
    chart.data(dv.rows)
    chart.scale('value', {
      nice: true
    if (plot.coordinate !== 'polar' && barfields.length === 0) {
      chart.scale(plot.Xaxis, {
        range: [0, 1]
      })
    } else {
      chart.scale(plot.Xaxis, {
        range: [0.05, 0.95]
      })
    }
    // 坐标轴格式化
    chart.axis(plot.Xaxis, {
      label: {
        formatter: (val) => {
          if (!val || /^\s*$/.test(val)) return val
          let _val = `${val}`
          if (_val.length <= 10) return val
          return _val.substring(0, 7) + '...'
        }
      }
    })
    if (!plot.legend || plot.legend === 'hidden') {
      chart.legend(false)
    } else {
      chart.legend({
        position: plot.legend
        custom: true,
        position: plot.legend,
        items: legends,
      })
    }
@@ -329,11 +610,174 @@
      })
    }
    fields.forEach((item, i) => {
      if (i === 0) {
        chart.axis(item.name, {
          grid: {},
          title: {},
          label: {}
        })
      } else if (i === 1 && item.axis !== 'unset') {
        chart.axis(item.name, {
          grid: null,
          title: {},
          label: {}
        })
      } else {
        chart.axis(item.name, {
          grid: null,
          title: null,
          label: null
        })
      }
      if (item.chartType === 'bar') {
        let _chart = chart
          .interval()
          .position(`${plot.Xaxis}*${item.name}`)
          .color(item.color)
          .shape(item.shape)
        if (item.label === 'true') {
          _chart.label(item.name)
        }
      } else if (item.chartType === 'line') {
        let _chart = chart
          .line()
          .position(`${plot.Xaxis}*${item.name}`)
          .color(item.color)
          .shape(item.shape)
        if (item.label === 'true') {
          _chart.label(item.name)
        }
        if (plot.point === 'true') {
          chart
            .point()
            .position(`${plot.Xaxis}*${item.name}`)
            .color(item.color)
            .size(3)
            .shape('circle')
        }
      }
    })
    chart.render()
  }
  barrender = () => {
    const { plot, config } = this.props
    let _data = []
    let _valfield = 'value'
    let _typefield = 'key'
    if (plot.datatype === 'statistics') {
      _valfield = plot.InfoValue
      _typefield = plot.InfoType
      _data = this.getStaticData()
    } else {
      let transfield = {}
      config.columns.forEach(col => {
        if (col.field) {
          transfield[col.field] = col.label
        }
      })
      let data = this.getdata()
      if (plot.enabled === 'true') {
        this.customrender(data, transfield)
        return
      }
      const ds = new DataSet()
      const dv = ds.createView().source(data)
      dv.transform({
        type: 'fold',
        fields: [...plot.Yaxis],
        key: 'key',
        value: _valfield
      })
      if (plot.Xaxis) {
        dv.transform({
          type: 'map',
          callback(row) {
            row.key = transfield[row.key]
            return row
          },
        })
      }
      _data = dv.rows
    }
    const chart = new Chart({
      container: this.state.chartId,
      autoFit: true,
      height: plot.height || 400
    })
    // dodge is not support linear attribute, please use category attribute! 时间格式
    if (_data[0] && _data[0][plot.Xaxis] && /^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}:\d{2})?/.test(_data[0][plot.Xaxis])) {
      _data[0][plot.Xaxis] += ' '
    }
    chart.data(_data)
    chart.scale(_valfield, {
      nice: true
    })
    // 坐标轴格式化
    chart.axis(plot.Xaxis, {
      label: {
        formatter: (val) => {
          if (!val || /^\s*$/.test(val)) return val
          let _val = `${val}`
          if (_val.length <= 10) return val
          return _val.substring(0, 7) + '...'
        }
      }
    })
    if (!plot.legend || plot.legend === 'hidden') {
      chart.legend(false)
    } else {
      chart.legend({
        position: plot.legend
      })
    }
    if (plot.tooltip !== 'true') {
      chart.tooltip(false)
    } else {
      chart.tooltip({
        showMarkers: false,
        shared: true
      })
    }
    if (plot.transpose === 'true') {
      chart.coordinate().transpose()
    }
    if (plot.coordinate === 'polar') {
      chart.coordinate('polar', {
        innerRadius: 0.1,
        radius: 0.8
      })
    }
    if (plot.adjust !== 'stack') {
      chart
      let _chart = chart
        .interval()
        .position(`${X_axis}*value`)
        .color('key')
        .position(`${plot.Xaxis}*${_valfield}`)
        .color(_typefield)
        .adjust([
          {
            type: 'dodge',
@@ -341,13 +785,21 @@
          }
        ])
        .shape(plot.shape || 'rect')
      if (plot.label === 'true') {
        _chart.label(_valfield)
      }
    } else if (plot.adjust === 'stack') {
      chart
      let _chart = chart
        .interval()
        .position(`${X_axis}*value`)
        .color('key')
        .position(`${plot.Xaxis}*${_valfield}`)
        .color(_typefield)
        .adjust('stack')
        .shape(plot.shape || 'rect')
      if (plot.label === 'true') {
        _chart.label(_valfield)
      }
    }
    chart.render()
@@ -362,8 +814,6 @@
        transfield[col.field] = col.label
      }
    })
    let X_axis = plot.Xaxis
    let Y_axis = plot.Yaxis
    let data = this.getdata()
@@ -377,14 +827,14 @@
    if (plot.pieshow !== 'value') {
      dv.transform({
        type: 'percent',
        field: Y_axis,
        dimension: X_axis,
        field: plot.Yaxis,
        dimension: plot.Xaxis,
        as: 'percent'
      })
    }
    
    const chart = new Chart({
      container: plot.uuid,
      container: this.state.chartId,
      autoFit: true,
      height: plot.height || 400
    })
@@ -427,8 +877,8 @@
        .interval()
        .adjust('stack')
        .position('percent')
        .color(X_axis)
        .tooltip(X_axis + '*percent', (item, percent) => {
        .color(plot.Xaxis)
        .tooltip(plot.Xaxis + '*percent', (item, percent) => {
          percent = (percent * 100).toFixed(2) + '%'
          return {
            name: item,
@@ -439,7 +889,14 @@
      if (plot.label === 'true') {
        let setting = {
          content: (data) => {
            return `${data[X_axis]}: ${(data.percent * 100).toFixed(2)}%`
            let val = data[plot.Xaxis]
            if (val) {
              val = `${val}`
              if (val.length > 10) {
                val = val.substring(0, 7) + '...'
              }
            }
            return `${val}: ${(data.percent * 100).toFixed(2)}%`
          }
        }
@@ -449,11 +906,6 @@
            type: 'overlap'
          }
          setting.offset = 0
          // setting.style = {
          //   textAlign: 'center',
          //   fontSize: 12,
          //   fill: '#535353'
          // }
        }
        _chart.label('percent', setting)
@@ -463,9 +915,9 @@
      let _chart = chart
        .interval()
        .adjust('stack')
        .position(Y_axis)
        .color(X_axis)
        .tooltip(X_axis + '*' + Y_axis, (item, value) => {
        .position(plot.Yaxis)
        .color(plot.Xaxis)
        .tooltip(plot.Xaxis + '*' + plot.Yaxis, (item, value) => {
          return {
            name: item,
            value: value
@@ -475,7 +927,14 @@
      if (plot.label === 'true') {
        let setting = {
          content: (data) => {
            return `${data[X_axis]}: ${data[Y_axis]}`
            let val = data[plot.Xaxis]
            if (val) {
              val = `${val}`
              if (val.length > 10) {
                val = val.substring(0, 7) + '...'
              }
            }
            return `${val}: ${data[plot.Yaxis]}`
          }
        }
@@ -487,20 +946,81 @@
          setting.offset = 0
        }
        _chart.label(Y_axis, setting)
        _chart.label(plot.Yaxis, setting)
      }
    }
    
    chart.render()
  }
  handleChange = (val) => {
    this.setState({selectFields: val}, () => {
      let _element = document.getElementById(this.state.chartId)
      if (_element) {
        _element.innerHTML = ''
      }
      this.viewrender()
    })
  }
  render() {
    const { plot } = this.props
    const { plot, loading, config, BID, Tab } = this.props
    const { empty, chartFields, selectFields, actions } = this.state
    return (
      <div className="line-chart-plot-box" style={{minHeight: plot.height ? plot.height + 50 : 450}}>
      <div className="line-chart-plot-box">
        {plot.title ? <p className="chart-title">{plot.title}</p> : null}
        <div className="canvas" id={plot.uuid}></div>
        {loading ?
          <div className="loading-mask">
            <div className="ant-spin-blur"></div>
            <Spin />
          </div> : null
        }
        {plot.datatype === 'statistics' && chartFields.length > 0 ? <Select
          mode="multiple"
          showSearch
          showArrow={true}
          value={selectFields}
          onChange={this.handleChange}
          maxTagCount={0}
          maxTagPlaceholder={(option) => <div className="type-label">{option.join('、')}</div>}
        >
          {chartFields.map((item, i) => <Select.Option key={i} value={item}>{item}</Select.Option>)}
        </Select> : null}
        <div className="canvas-wrap">
          <div className={'chart-action ' + (plot.title ? 'with-title' : '')}>
            {actions.map(item => {
              if (item.OpenType === 'excelOut') {
                return (
                  <ExcelOutButton
                    key={item.uuid}
                    BID={BID}
                    Tab={Tab}
                    btn={item}
                    show="icon"
                    setting={config.setting}
                    getexceloutparam={this.props.getexceloutparam}
                    updateStatus={() => {}}
                  />
                )
              } else {
                return (
                  <ExcelInButton
                    key={item.uuid}
                    BID={BID}
                    Tab={Tab}
                    btn={item}
                    show="icon"
                    setting={config.setting}
                    updateStatus={() => {}}
                  />
                )
              }
            })}
          </div>
          <div className={'canvas' + (empty ? ' empty' : '')} style={{minHeight: plot.height ? plot.height : 400}} id={this.state.chartId}></div>
        </div>
        {empty ? <Empty description={false}/> : null}
      </div>
    )
  }