import React, {Component} from 'react'
|
import PropTypes from 'prop-types'
|
import { is, fromJS } from 'immutable'
|
import { Chart } from '@antv/g2'
|
import DataSet from '@antv/data-set'
|
import { Spin, Empty, notification } from 'antd'
|
|
// import searchLine from '../../share/searchLine'
|
import Api from '@/api'
|
import Utils from '@/utils/utils.js'
|
import UtilsDM from '@/utils/utils-datamanage.js'
|
import './index.scss'
|
|
class LineChart extends Component {
|
static propTpyes = {
|
BID: PropTypes.any, // 父级Id
|
data: PropTypes.array, // 统一查询数据
|
config: PropTypes.object, // 组件配置信息
|
mainSearch: PropTypes.any, // 外层搜索条件
|
menuType: PropTypes.any, // 菜单类型
|
dataManager: PropTypes.any, // 数据权限
|
}
|
|
state = {
|
config: null, // 图表配置信息
|
empty: true, // 图表数据为空
|
loading: false, // 数据加载状态
|
chartId: Utils.getuuid(), // 图表Id
|
title: '', // 组件标题
|
sync: false, // 是否统一请求数据
|
plot: null, // 图表设置
|
data: null, // 数据
|
search: null, // 搜索条件
|
showHeader: false // 存在标题、搜索、或统计数据时显示
|
}
|
|
UNSAFE_componentWillMount () {
|
const { config, data, initdata } = this.props
|
let _config = fromJS(config).toJS()
|
|
let _data = null
|
let _sync = config.setting.sync === 'true'
|
|
if (config.setting.sync === 'true' && data) {
|
_data = data[config.dataName] || []
|
_sync = false
|
} else if (config.setting.sync === 'true' && initdata) {
|
_data = initdata || []
|
_sync = false
|
}
|
|
let showHeader = false
|
if (config.plot.title || config.search.length > 0) {
|
showHeader = true
|
_config.plot.height = _config.plot.height - 80
|
} else {
|
_config.plot.height = _config.plot.height - 30
|
}
|
|
if (_config.style) {
|
_config.style = {..._config.style, minHeight: (config.plot.height || 400)}
|
} else {
|
_config.style = {minHeight: (config.plot.height || 400)}
|
}
|
|
this.setState({
|
config: _config,
|
data: _data,
|
arr_field: _config.columns.map(col => col.field).join(','),
|
plot: _config.plot,
|
sync: _sync,
|
title: config.plot.title,
|
search: Utils.initMainSearch(config.search),
|
showHeader
|
}, () => {
|
if (config.setting.sync !== 'true' && config.setting.onload === 'true') {
|
this.loadData()
|
} else if (config.setting.sync === 'true' && _data) {
|
this.handleData()
|
}
|
})
|
}
|
|
/**
|
* @description 校验图表的按钮组,如果为统计图表,计算图表字段
|
*/
|
componentDidMount () {
|
|
}
|
|
/**
|
* @description 图表数据更新,刷新内容
|
*/
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
const { sync, config } = this.state
|
|
if (sync && !is(fromJS(this.props.data), fromJS(nextProps.data))) {
|
let _data = []
|
if (nextProps.data && nextProps.data[config.dataName]) {
|
_data = nextProps.data[config.dataName] || []
|
}
|
|
this.setState({sync: false, data: _data}, () => {
|
this.handleData()
|
})
|
} else if (!is(fromJS(this.props.mainSearch), fromJS(nextProps.mainSearch))) {
|
if (config.setting.syncRefresh === 'true') {
|
this.setState({}, () => {
|
this.loadData()
|
})
|
}
|
}
|
}
|
|
shouldComponentUpdate (nextProps, nextState) {
|
return !is(fromJS(this.state), fromJS(nextState))
|
}
|
|
handleData = () => {
|
let _element = document.getElementById(this.state.chartId)
|
if (_element) {
|
_element.innerHTML = ''
|
}
|
this.pierender()
|
}
|
|
async loadData () {
|
const { mainSearch, BID, menuType, dataManager } = this.props
|
const { config, arr_field, search } = this.state
|
|
let searches = fromJS(search).toJS()
|
if (mainSearch && mainSearch.length > 0) { // 主表搜索条件
|
let keys = searches.map(item => item.key)
|
mainSearch.forEach(item => {
|
if (!keys.includes(item.key)) {
|
searches.push(item)
|
}
|
})
|
}
|
|
this.setState({
|
loading: true
|
})
|
|
let _orderBy = config.setting.order || ''
|
let param = UtilsDM.getQueryDataParams(config.setting, arr_field, searches, _orderBy, '', '', BID, menuType, dataManager)
|
|
let result = await Api.genericInterface(param)
|
if (result.status) {
|
this.setState({
|
data: result.data,
|
loading: false
|
}, () => {
|
this.handleData()
|
})
|
} else {
|
this.setState({
|
loading: false
|
})
|
notification.error({
|
top: 92,
|
message: result.message,
|
duration: 10
|
})
|
}
|
}
|
|
/**
|
* @description 图表数据预处理
|
* 1、通过显示列进行数据类型转换
|
* 2、重复数据:取平均值、累计、去重
|
* 3、柱状图数据补齐
|
*/
|
getdata = () => {
|
const { data, plot, config } = this.state
|
|
if (!data) {
|
this.setState({empty: true})
|
return []
|
}
|
|
let decimal = 0
|
config.columns.forEach(col => {
|
if (plot.Yaxis === col.field && /Decimal/ig.test(col.datatype)) {
|
decimal = +col.datatype.replace(/^Decimal\(18,/ig, '').replace(/\)/ig, '')
|
}
|
})
|
|
let _data = []
|
let _cdata = fromJS(data).toJS()
|
|
if (plot.repeat === 'average') {
|
let _mdata = new Map()
|
_cdata.forEach(item => {
|
if (typeof(item[plot.Yaxis]) !== 'number') {
|
item[plot.Yaxis] = parseFloat(item[plot.Yaxis])
|
if (isNaN(item[plot.Yaxis])) {
|
item[plot.Yaxis] = 0
|
}
|
}
|
|
if (item[plot.Xaxis] && !_mdata.has(item[plot.Xaxis])) {
|
item.$count = 1
|
_mdata.set(item[plot.Xaxis], item)
|
} else if (item[plot.Xaxis]) {
|
let _item = _mdata.get(item[plot.Xaxis])
|
_item.$count++
|
_item[plot.Yaxis] += item[plot.Yaxis]
|
_mdata.set(item[plot.Xaxis], _item)
|
}
|
})
|
|
_data = [..._mdata.values()]
|
_data = _data.map(item => {
|
item[plot.Yaxis] = item[plot.Yaxis] / item.$count
|
item[plot.Yaxis] = item[plot.Yaxis].toFixed(decimal)
|
item[plot.Yaxis] = +item[plot.Yaxis]
|
|
return item
|
})
|
} else if (plot.repeat === 'cumsum') {
|
let _mdata = new Map()
|
_cdata.forEach(item => {
|
if (typeof(item[plot.Yaxis]) !== 'number') {
|
item[plot.Yaxis] = parseFloat(item[plot.Yaxis])
|
if (isNaN(item[plot.Yaxis])) {
|
item[plot.Yaxis] = 0
|
}
|
}
|
|
if (item[plot.Xaxis] && !_mdata.has(item[plot.Xaxis])) {
|
_mdata.set(item[plot.Xaxis], item)
|
} else if (item[plot.Xaxis]) {
|
let _item = _mdata.get(item[plot.Xaxis])
|
_item[plot.Yaxis] += item[plot.Yaxis]
|
_mdata.set(item[plot.Xaxis], _item)
|
}
|
})
|
|
_data = [..._mdata.values()]
|
_data = _data.map(item => {
|
item[plot.Yaxis] = item[plot.Yaxis].toFixed(decimal)
|
item[plot.Yaxis] = +item[plot.Yaxis]
|
return item
|
})
|
} else { // plot.repeat === 'unrepeat'
|
let _mdata = new Map()
|
_cdata.forEach(item => {
|
if (item[plot.Xaxis] && !_mdata.has(item[plot.Xaxis])) {
|
if (typeof(item[plot.Yaxis]) !== 'number') {
|
item[plot.Yaxis] = parseFloat(item[plot.Yaxis])
|
if (isNaN(item[plot.Yaxis])) {
|
item[plot.Yaxis] = 0
|
}
|
}
|
|
item[plot.Yaxis] = item[plot.Yaxis].toFixed(decimal)
|
item[plot.Yaxis] = +item[plot.Yaxis]
|
|
_mdata.set(item[plot.Xaxis], item)
|
}
|
})
|
|
_data = [..._mdata.values()]
|
}
|
|
this.setState({empty: _data.length === 0})
|
return _data
|
}
|
|
/**
|
* @description 饼图渲染
|
*/
|
pierender = () => {
|
const { plot, chartId } = this.state
|
let color = plot.color || 'rgba(0, 0, 0, 0.85)'
|
|
let X_axis = plot.Xaxis || 'x'
|
let Y_axis = plot.Yaxis || 'y'
|
|
let data = this.getdata(X_axis, Y_axis)
|
|
const ds = new DataSet()
|
const dv = ds.createView().source(data)
|
|
const chart = new Chart({
|
container: chartId,
|
autoFit: true,
|
height: plot.height || 400
|
})
|
|
if (plot.shape !== 'nightingale' && plot.show !== 'value') {
|
dv.transform({
|
type: 'percent',
|
field: Y_axis,
|
dimension: X_axis,
|
as: 'percent'
|
})
|
|
chart.scale('percent', {
|
formatter: (val) => {
|
val = val * 100 + '%'
|
return val
|
}
|
})
|
|
Y_axis = 'percent' // 显示百分比
|
}
|
chart.data(dv.rows)
|
|
if (plot.shape === 'nightingale') {
|
chart.coordinate('polar', {
|
innerRadius: plot.innerRadius ? (plot.innerRadius / 100) : 0,
|
radius: plot.radius ? (plot.radius / 100) : 0.75,
|
})
|
} else {
|
chart.coordinate('theta', {
|
innerRadius: plot.shape !== 'pie' && plot.innerRadius ? (plot.innerRadius / 100) : 0,
|
radius: plot.radius ? (plot.radius / 100) : 0.75,
|
})
|
}
|
|
if (!plot.legend || plot.legend === 'hidden') {
|
chart.legend(false)
|
} else if (plot.shape === 'nightingale') {
|
chart.legend(X_axis, {
|
position: plot.legend,
|
itemName: {
|
style: {
|
fill: color,
|
}
|
}
|
})
|
} else {
|
chart.legend({
|
position: plot.legend,
|
itemName: {
|
style: {
|
fill: color,
|
}
|
}
|
})
|
}
|
|
if (plot.tooltip !== 'true') {
|
chart.tooltip(false)
|
} else {
|
chart.tooltip({
|
showTitle: false,
|
showMarkers: false
|
})
|
}
|
|
if (plot.shape !== 'nightingale') {
|
let _chart = chart
|
.interval()
|
.adjust('stack')
|
.position(Y_axis)
|
.color(X_axis)
|
.tooltip(`${X_axis}*${Y_axis}`, (name, value) => {
|
if (plot.show !== 'value') {
|
value = (value * 100).toFixed(2) + '%'
|
}
|
return {
|
name: name,
|
value: value
|
}
|
})
|
if (plot.label !== 'false') {
|
if (plot.label === 'inner') {
|
_chart.label(Y_axis, {
|
offset: -30,
|
content: (data) => {
|
let _val = ''
|
if (plot.show !== 'value') {
|
_val = `${(data[Y_axis] * 100).toFixed(2)}%`
|
} else {
|
_val = `${data[Y_axis]}`
|
}
|
return _val
|
},
|
style: {
|
textAlign: 'center',
|
fontSize: 16,
|
shadowBlur: 2,
|
shadowColor: 'rgba(0, 0, 0, .45)',
|
fill: '#fff',
|
}
|
})
|
} else {
|
_chart.label(Y_axis, {
|
layout: { type: 'pie-spider' },
|
labelHeight: 20,
|
content: (data) => {
|
let _val = ''
|
if (plot.show !== 'value') {
|
_val = `${(data[Y_axis] * 100).toFixed(2)}%`
|
} else {
|
_val = `${data[Y_axis]}`
|
}
|
|
return `${data[X_axis]}: ${_val}`
|
},
|
labelLine: {
|
style: {
|
lineWidth: 0.5,
|
},
|
},
|
style: {
|
fill: color
|
}
|
})
|
}
|
}
|
chart.interaction('element-active')
|
} else {
|
chart.axis(false)
|
chart.interaction('element-highlight')
|
let _chart = chart
|
.interval()
|
.position(`${X_axis}*${Y_axis}`)
|
.color(X_axis)
|
|
if (plot.label !== 'false') {
|
let _label = {}
|
if (plot.label === 'inner') {
|
_label.offset = -15
|
} else {
|
_label.style = {
|
fill: color
|
}
|
}
|
|
_chart.label(X_axis, _label)
|
.style({
|
lineWidth: 1,
|
stroke: '#fff',
|
})
|
}
|
}
|
|
chart.render()
|
}
|
|
render() {
|
const { showHeader, config, loading, title, empty } = this.state
|
|
return (
|
<div className="custom-pie-chart-plot-box" style={config.style}>
|
{loading ?
|
<div className="loading-mask">
|
<div className="ant-spin-blur"></div>
|
<Spin />
|
</div> : null
|
}
|
{showHeader ? <div className="chart-header" style={config.headerStyle}>
|
<span className="chart-title">{title}</span>
|
{/* <searchLine /> */}
|
</div> : null}
|
<div className="canvas-wrap">
|
<div className={'canvas' + (empty ? ' empty' : '')} id={this.state.chartId}></div>
|
</div>
|
{empty ? <Empty description={false}/> : null}
|
</div>
|
)
|
}
|
}
|
|
export default LineChart
|