天天看点

echarts 堆叠柱状图3d效果_ECharts 堆积木(砖块)游戏

echarts 堆叠柱状图3d效果_ECharts 堆积木(砖块)游戏

最近突发奇想,用 3D 的堆叠柱图,做了一个搭积木的小游戏。

主要思路

  1. 用一个几乎透明的 series-bar3D 铺满整个 grid3D,作为操作区,监听鼠标点击事件、完成堆积木的操作;
  2. 用多层数据为 0 的 series-bar3D 放在操作层 bar3D 下方,堆积木时,按照从下向上的顺序,更新其数据 series-bar3D.data(包括数值和样式,即 value 和 itemStyle);
  3. 用一个 series-heatmap 制作菜单,也是监听鼠标点击事件,实现撤销、重做、重置、修改积木样式(高度、颜色和透明度)等功能。

效果演示

echarts 堆叠柱状图3d效果_ECharts 堆积木(砖块)游戏

家里的笔记本屏幕小,菜单按钮上的文字几乎全都显示不全了……

关键代码

  • 生成数据的部分
generateData = (length) => {    let ret = {        x: [],        y: [],        boxWidth: length,        boxDepth: length,        boxHeight: length,        operatingSeriesData: [],        brickSeriesData: []    };    let brickSeriesDataItem = [];    for (let i = 0; i < length; i++) {        ret.x.push('x_' + i);        ret.y.push('y_' + i);        for (let j = 0; j < length; j++) {            ret.operatingSeriesData.push([i, j, 1]);            brickSeriesDataItem.push({                value: [i, j, 0]            });        }    }    for (let i = 0; i < length; i++) {        ret.brickSeriesData[i] = JSON.parse(JSON.stringify(brickSeriesDataItem));    }        return ret;};
           

柱状图堆叠,相同 stack 值的柱状图系列数据会有叠加。注意不同系列需要叠加的数据项在数组中的索引必须是一样的。

https://echarts.apache.org/zh/option-gl.html#series-bar3D.stack

由于一开始对 3D 堆叠柱图的堆叠机制了解不够深入(自以为是,没仔细看配置项手册,大家不要学我哈-  -),所以一上来就把所有可能用到的砖块数据都生成出来了……也不管最终是否会用到。这里还有优化的空间……

  • series-heatmap.data 生成部分
generateMenuData = (colorList, sizeList) => {        let ret = [];        for (let i = 0; i < sizeList.length; i++) {        ret.push({            value: [i, 1, sizeList[i]],            name: 'size',            label: {                show: true,                color: 'black'            },            itemStyle: {                color: 'gray'            }        });    }    for (let i = 0; i < colorList.length + 1; i++) {                if (i === colorList.length) {                        ret.push({                value: [i, 0, 1],                name: 'empty',                label: {                    show: true,                    color: 'black'                },                itemStyle: {                    color: '#FFF',                    opacity: 0.1                }            });            continue;        }                ret.push({            value: [i, 0, 1],            name: 'color',            label: {                show: true,                color: 'black'            },            itemStyle: {                color: colorList[i]            }        });    }    ret.push({        value: [0, 2, 1],        name: 'undo',        label: {            show: true,            color: 'black'        },        itemStyle: {            color: 'gray'        }    }, {        value: [1, 2, 1],        name: 'redo',        label: {            show: true,            color: 'black'        },        itemStyle: {            color: 'gray'        }    }, {        value: [2, 2, 1],        name: 'reset',        label: {            show: true,            color: 'black'        },        itemStyle: {            color: 'gray'        }    }, {        value: [3, 2, 1],        name: 'save',        label: {            show: true,            color: 'black'        },        itemStyle: {            color: 'gray'        }    }, {        value: [4, 2, 1],        name: 'load',        label: {            show: true,            color: 'black'        },        itemStyle: {            color: 'gray'        }    });    return ret;};
           
  • option.series 生成
generateSeries = (src) => {    ret = [];    for (let i = 0; i < src.boxHeight; i++) {        ret.push({            type: 'bar3D',            name: 'bricks',            color: 'LawnGreen',            data: src.brickSeriesData[i],            bevelSize: i === 0 ? 0 : 0.2,            bevelSmoothness: i === 0 ? 0 : 2,            barSize: [1, 1],            stack: 'stack',            silent: true,            shading: 'lambert',            itemStyle: {                opacity: i === 0? 1: 0            }        });    }    ret.push({        type: 'bar3D',        name: 'operatingSeries',        data: src.operatingSeriesData,        barSize: [1, 1],        stack: 'stack',        color: '#FFA',        shading: 'lambert',        label: {            emphasis: {                show: false            }                    },        itemStyle: {            opacity: 0.01        },        emphasis: {            itemStyle: {                opacity: 1            }        }    });    ret.push({        type: 'heatmap',        name: 'menu',        tooltip: {            formatter: params => {                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 最后一条记录“剪切”到 redoList    let 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 最后一条记录“剪切”到 undoList    let 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') {            // load            alert('开发中…');            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 Gallery 例子,强烈建议 PC 查看