前言
项目实现了: 生成图表, 任意拖拽, 改变图表大小, 样式(输入input按回车enter), 双击删除, 可全屏的, 高度自定义的数据可视化大屏。
项目体验地址:数据可视化大屏
项目源码地址: 源码
技术栈: react + antd + echarts + react-hooks + react-dnd
项目预览

实现
生成图表, 实现拖拽, 全屏可参考上一篇文章: 生成图表
封装图表
可参考echarts的社区: echarts
这里面有很多大佬制作了很多不错的echarts图表, 博主偷懒, 都是从大佬那嫖来的图表。
import ReactEcharts from 'echarts-for-react';
<ReactEcharts
option={option}
theme="Imooc"
style={{ width: `${width}px`, height: `${height}px` }}
ref={chartRef}
/>
通过echarts-for-react
和社区里拿到图表样式option, 我们可以很轻松的封装多种图表的样式,博主做上面几套样式花了不到20分钟。
改变图表样式同步更新视图
项目在视图同步更新这卡了一会, 这里的难点是在我们改变了右侧的状态后, 让图表重渲染。
开始想自己用echarts 的rerender。 后来发现echarts-for-react
给我们提供了获取图表dom并提供了api。
通过ref我们拿到库提供的getEchartsInstance
方法, 这上面有echarts自己的所有方法, 通过重新setOption就可以实现图表rerender。
实现思路:
当我们在右侧更新状态按下回车键(enter)触发dispatch, 改变数据中心对应的数据,此时我们的chart组件也会rerender, 触发useEffect
钩子函数,完成视图的同步更新。
const chartRef = React.useRef()
React.useEffect(() => {
chartRef.current.getEchartsInstance().setOption(option)
}, [option])
return (
<div
className={ClassNames({ 'active': active })}
onDoubleClick={(e) => deleteChart(id)}
onClick={(e) => selectChart(e, id)}
ref={drag}
style={{ ...style, left, top }}
>
<ReactEcharts
option={option}
theme="Imooc"
style={{ width: `${width}px`, height: `${height}px` }}
ref={chartRef}
/>
</div>
)
图表样式栏
当我们选中一个图表时, 获取图表的option, 并解析, 转化成对应的dom标签。
这里主要的问题是, option是深层对象结构, 我们需要一层层的循环遍历, 获取到对象属性。
思虑良久, 还是没想到很好的解决方案。 最后只能通过最笨的方法, 一层层的解析判断,当是不能展示在侧边栏的数据都给过滤掉。
这样的实现很蠢:
<Menu
className="tab-menu"
theme="dark"
mode="inline"
openKeys={openKeys}
onOpenChange={(v) => onOpenChange(v)}
style={{ width: '100%', height: '100%' }}
>
{
Object.keys(view).map(v => {
if (v === 'data' || v === 'series') {
return null
}
return (
<SubMenu
className="tab-submenu"
key={v}
title={
<span>
<span>{v}</span>
</span>
}
>
{
Object.keys(view[v]).map(item => {
if (item === 'data' || item === 'series') {
return null
}
if (typeof view[v][item] === 'object') {
return (
<SubMenu
className="tab-subitemmenu"
key={item}
title={
<span>
<span>{item}</span>
</span>
}
>
{
Object.keys(view[v][item]).map(subitem => {
return (
<Menu.Item key={subitem} className="tab-submenuitem">
<span style={{ marginRight: '5px' }}>{subitem}</span>
<Input
onPressEnter={(e) => onPressEnter(e, subitem, item, v)}
className="submenuitem-input"
onChange={e => inputOnChange(e, subitem, item, v)}
value={view[v][subitem]}
/>
</Menu.Item>
)
})
}
</SubMenu>
)
}
return (
<Menu.Item key={item} className="tab-menuitem">
<span style={{ marginRight: '5px' }}>{item}</span>
<Input
onPressEnter={(e) => onPressEnter(e, item, v)}
className="menuitem-input"
onChange={e => inputOnChange(e, item, v)}
value={view[v][item]}
/>
</Menu.Item>
)
})
}
</SubMenu>
)
})
}
</Menu>
选中样式
当我们点击一个图表时给它添加class: active, 点击其他地方移除样式。
思路:
这里我使用了一个classnames
插件。 它允许我们这样:
className={ClassNames({ 'active': active })}
其中的active是一个布尔值,当为true时,添加active样式, false时, 移除active样式。
实现:
我在每个图表的state里增加active一项,默认都为false, 当点击时, 将其变为true, 并让其他的都为false。
注意: 要阻止冒泡。
case 'activeClass': //给选中的图表添加选中的样式
Object.keys(state).map(item => {
if (item === id) {
state[item].active = true
return item
}
state[item].active = false
return item
})
return { ...state }
...
const selectChart = (e, id) => { //设置选中图表, 并添加被选中的样式
e.stopPropagation()
setSelect(chartsObj[id])
dispatch({ type: "activeClass", id })
}
当点击非图表区域时, 再dispatch将所有的都变为false:
const cancelSelect = () => { //取消选中样式
setSelect({})
dispatch({ type: "activeClass" })
}
后话
后面要做的:
最主要就是把数据跑通, 可配置数据源, 并自定义生成大屏, 保存在服务器上。
次要是调整图表样式(目前这些封装的基本是网上copy下来的, 后续开发些合适的图表)