最近突发奇想,用 3D 的堆叠柱图,做了一个搭积木的小游戏。
主要思路
- 用一个几乎透明的 series-bar3D 铺满整个 grid3D,作为操作区,监听鼠标点击事件、完成堆积木的操作;
- 用多层数据为 0 的 series-bar3D 放在操作层 bar3D 下方,堆积木时,按照从下向上的顺序,更新其数据 series-bar3D.data(包括数值和样式,即 value 和 itemStyle);
- 用一个 series-heatmap 制作菜单,也是监听鼠标点击事件,实现撤销、重做、重置、修改积木样式(高度、颜色和透明度)等功能。
效果演示
家里的笔记本屏幕小,菜单按钮上的文字几乎全都显示不全了……关键代码
generateData = (length) => {let ret = {x: [],y: [],boxWidth: length,boxDepth: length,boxHeight: length,operatingSeriesData: [],brickSeriesData: []};let brickSeriesDataItem = [];for (let i = 0; i };
由于一开始对 3D 堆叠柱图的堆叠机制了解不够深入(自以为是,没仔细看配置项手册[1],大家不要学我哈- -),所以一上来就把所有可能用到的砖块数据都生成出来了……也不管最终是否会用到。这里还有优化的空间……
generateMenuData = (colorList, sizeList) => {let ret = [];for (let i = 0; i };
generateSeries = (src) => {ret = [];for (let i = 0; i {if (params.name === 'color') {return `点击更换“积木”颜色为 ${params.color}`;}if (params.name === 'size') {return `点击更换“积木”高度为 ${params.value[2]}`;}return {undo: '撤销', redo: '重做', reset: '清空', save: '导出游戏数据,
供下次赋值给 loadData 使用', load: '功能开发中…' }[params.name]; }},label: {normal:{formatter: params => { if (params.name === 'color') {return params.color;}if (params.name === 'size') {return params.value[2]; }return params.name;}}},itemStyle: {borderColor: '#AAA',borderWidth: 4},data: generateMenuData(menuConfig.colorList, menuConfig.sizeList)});return ret;
};
通过 tooltip.formatter 和 label.normal.formatter 定义按钮的文字和提示框内容
// 撤销
undo = () => {if (history.undoList.length === 0) {alert('操作历史记录为空,撤销未执行…');return console.log('操作历史记录为空,撤销未执行…');}// undoList 最后一条记录“剪切”到 redoListlet historyObj = history.undoList.pop();history.redoList.push(historyObj);// 将上一步操作/重做的 series[seriesIndex].data[dataIndex] 重置为初始值let val = series[historyObj.seriesIndex].data[historyObj.dataIndex].value;val[2] = 0;series[historyObj.seriesIndex].data[historyObj.dataIndex] = {value: val};myChart.setOption({series: series});console.log('撤销成功');
};// 重做
redo = () => {if (history.redoList.length === 0) {alert('操作历史记录为空,重做未执行…');return console.log('操作历史记录为空,重做未执行…');}// redoList 最后一条记录“剪切”到 undoListlet historyObj = history.redoList.pop();history.undoList.push(historyObj);// 将上一步重置的 series[seriesIndex].data[dataIndex] 重设为撤销前的状态series[historyObj.seriesIndex].data[historyObj.dataIndex].value[2] = historyObj.brickConfig.size;series[historyObj.seriesIndex].data[historyObj.dataIndex].itemStyle = {color: historyObj.brickConfig.color,opacity: historyObj.brickConfig.opacity};myChart.setOption({series: series});console.log('重做成功');
};// 撤销/重做 所用的操作历史记录
let history = {undoList: [],redoList: []
};
// 监听鼠标点击事件
myChart.on('click', params => {// 菜单操作处理if (params.seriesName === 'menu') {if (params.name === 'color') {brickConfig.color = params.color;brickConfig.opacity = 1;myChart.setOption({title:{subtext: `当前颜色:${brickConfig.color}n当前尺寸:${brickConfig.size}n当前透明度:${brickConfig.opacity}`}});return console.log(`砖块颜色更换为${params.color}`);}if (params.name === 'empty') {brickConfig.color = params.color;brickConfig.opacity = 0;myChart.setOption({title:{subtext: `当前颜色:${brickConfig.color}n当前尺寸:${brickConfig.size}n当前透明度:${brickConfig.opacity}`}});return console.log(`砖块颜色更换为透明`);}if (params.name === 'size') {brickConfig.size = params.value[2];myChart.setOption({title:{subtext: `当前颜色:${brickConfig.color}n当前尺寸:${brickConfig.size}n当前透明度:${brickConfig.opacity}`}});return console.log(`砖块 size 更换为${params.value[2]}`);}if (params.name === 'load') {// loadalert('开发中…');return console.log('开发中…');}if (params.name === 'reset') {data = generateData(xLength);series = generateSeries(data);myChart.setOption({series: series});return console.log('清空数据成功');}if (params.name === 'save') {let uri = 'data:application/json;base64,';//console.log(data);window.location.href = uri + base64(JSON.stringify(data));return console.log('导出数据成功');}if (params.name === 'undo') {return undo();}if (params.name === 'redo') {return redo();}}//alert(`正在 (${params.data[0]}, ${params.data[1]}) 处堆积一个砖块`);// 堆积木(砖块)操作处理for (let i in series) {if (series[i].name === 'bricks' && series[i].data[params.data[0] * xLength + params.data[1]].value[2] === 0) {series[i].data[params.data[0] * xLength + params.data[1]].value[2] = brickConfig.size;series[i].data[params.data[0] * xLength + params.data[1]].itemStyle = {color: brickConfig.color,opacity: brickConfig.opacity};history.undoList.push({seriesIndex: i, dataIndex: params.data[0] * xLength + params.data[1], brickConfig: JSON.parse(JSON.stringify(brickConfig)) // 深拷贝});history.redoList = [];return myChart.setOption({series: series}); }}
});
主要就是通过 echartsInstance.on 绑定事件处理函数,也就是 myChart.on('click', function(){}) 的形式。
ECharts Gallerygallery.echartsjs.com
参考
- ^柱状图堆叠,相同 stack 值的柱状图系列数据会有叠加。注意不同系列需要叠加的数据项在数组中的索引必须是一样的。 https://echarts.apache.org/zh/option-gl.html#series-bar3D.stack