最近突发奇想,用 3D 的堆叠柱图,做了一个搭积木的小游戏。
主要思路
效果演示
家里的笔记本屏幕小,菜单按钮上的文字几乎全都显示不全了……
关键代码
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 堆叠柱图的堆叠机制了解不够深入(自以为是,没仔细看配置项手册,大家不要学我哈- -),所以一上来就把所有可能用到的砖块数据都生成出来了……也不管最终是否会用到。这里还有优化的空间……
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;
};
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: '导出游戏数据,<br />供下次赋值给 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(){}) 的形式。
本文分享自 ZXand618的ECharts之旅 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!