前言
这个项目比较复杂, 预计要分三部分来讲, 项目也还未完工, 怕工期拖长了, 前面忘了,这里先做下记录。
项目在线体验地址: charts
项目源码地址在Library文件夹下: 源码
项目用到的技术
react-dnd
react-dnd-html5-backend
react
echarts
echarts-for-react
数据结构
嵌套对象: 外层对象的属性是每个图表的id, 内层对象保存图表的各项属性。
长这样:
chartsObj = {
line1: {
width: x,
height: x,
top: x,
left: x,
}
...
}
这里使用的react的hook:useReducer
创建的对象。
开始没想到会有这么多type, 后面重构换成switch-case
:
const [chartsObj, dispatch] = React.useReducer((state, action) => {
const { id, left, top, active, type, width, height, view, value } = action
if (type === 'move') {
return { ...state, [id]: { ...state[id], left, top } }
}
if (type === "delete") {
delete state[id]
return { ...state }
}
if (type === 'activeClass') {
Object.keys(state).map(item => {
if (item === id) {
state[item].active = true
return item
}
state[item].active = false
return item
})
return { ...state }
}
if (type === 'changeview') {
return { ...state, [id]: { ...state[id], [view]: value } }
}
return { ...state, [id]: { id, type, active, left, top, width, height } }
}, {});
创建图表
当点击左侧菜单栏时生成对应的基本图表: 类似点击柱图生成柱图。
思路:
-
封装对应图表的echarts基本样式。
-
当点击item时, 使用
dispatch
通知state做变更。 -
数据驱动视图, 生成对应的图表。
实现:
这里使用echarts-for-react
将echarts引入项目中的, 用过echarts的都知道, echarts主要的定义都在option属性上, 我们只要把定义好的option传进去就ok了,不懂echarts的道友可以先去echarts官网学习下, 官网api很详细: echarts
import ReactEcharts from 'echarts-for-react';
<ReactEcharts
option={option}
theme="Imooc"
style={{ width: `${width}px`, height: `${height}px` }}
ref={chartRef}
/>
封装好图表之后, 下一步就是点击事件改变数据源, 使用useReducer提供的dispatch:
const createChart = (item) => {
switch (item.key) {
case "lineBasic":
dispatch({ type: 'lineBasic', id: `lineBasic${Object.keys(chartsObj).length}`, active: false, left: 20, top: 20, width: 300, height: 250 })
break
case "barBasic":
dispatch({ type: 'barBasic', id: `barBasic${Object.keys(chartsObj).length}`, left: 20, top: 20, width: 300, height: 250 })
break
case "pieBasic":
dispatch({ type: 'pieBasic', id: `pieBasic${Object.keys(chartsObj).length}`, left: 20, top: 20, width: 300, height: 250 })
break
default:
return
}
}
遍历state,生成图表:
{Object.keys(chartsObj).map(v => {
const { left, top, id, type, active, width, height } = chartsObj[v]
return (
<Chart
type={type}
key={id}
id={id}
left={left}
top={top}
width={width}
height={height}
active={active}
deleteChart={deleteChart}
selectChart={selectChart}
>
</Chart>
)
})}
支持拖拽
拖拽主要使用的react-dnd
, 也尝试了react-beautiful-dnd, react-sortable-hoc
,最后发现还是react-dnd
最强大。
使用react-dnd
的道友一定注意, 这里有个大坑: 引入react-dnd报错, 原因是react-dnd插件内部也引入了react。如果和我们的react版本不一致就会报错。解决方法:
在webpack.config.js
的resolve下配置下alias:
alias: {
'@': paths.appSrc,
react: path.resolve('./node_modules/react'), //解决react两个版本的问题
}
react-dnd
依赖react-dnd-html5-backend
做扩展, 所以必须一起安装。
- 在项目的根目录下:
//app.js
import { useDrop, DndProvider } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
export default function RouteConfigExample () {
return (
<DndProvider backend={HTML5Backend}>
<BrowserRouter>
<Switch>
{RouteGlobal.map((route, i) => (
<RouteWithSubRoutes key={i} {...route} />
))}
</Switch>
</BrowserRouter>
</DndProvider>
...
- 在准备作为拖拽区域的组件内:
const [, drop] = useDrop({
accept: 'box',
drop (item, monitor) {
const delta = monitor.getDifferenceFromInitialOffset()
const left = Math.round(item.left + delta.x)
const top = Math.round(item.top + delta.y)
moveBox(item.id, left, top)
return undefined
},
})
const moveBox = (id, left, top) => {
dispatch({ type: 'move', id, left, top })
}
...
<div ref={drop} className="charts-middle">
<Chart>
</div
- 设置拖拽源:
const [{ isDragging }, drag] = useDrag({
item: { id, left, top, type: 'box' },
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
})
...
<div
ref={drag}
style={{ ...style, left, top }}
>
<ReactEcharts
option={option}
theme="Imooc"
style={{ width: `${width}px`, height: `${height}px` }}
ref={chartRef}
/>
</div>
全屏操作
操作全屏主要用到了requestFullscreen
和 exitFullScreen
两个api, 各个浏览器的兼容性不一样:
const fullscreen = () => {
let ele = document.querySelector('.charts-box')
if (ele.requestFullscreen) {
ele.requestFullscreen();
} else if (ele.mozRequestFullScreen) {
ele.mozRequestFullScreen();
} else if (ele.webkitRequestFullscreen) {
ele.webkitRequestFullscreen();
} else if (ele.msRequestFullscreen) {
ele.msRequestFullscreen();
}
setFullFlag(true)
}
const exitFullscreen = () => {
if (document.exitFullScreen) {
document.exitFullScreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
setFullFlag(false)
}
...
{
fullFlag
?
<Button className="charts-left-button" onClick={exitFullscreen}><Icon type="fullscreen-exit" /></Button>
:
<Button className="charts-left-button" onClick={fullscreen}><Icon type="fullscreen" /></Button>
}
...
后话
具体的操作可到线上demo体验, 下步准备实现:
图表样式支持配置;
数据源可以配置,使用真实数据源, 展示真实数据;