echarts生成的圖表在three.js中的應用
最近羣裏有幾個人問,如何把echarts的圖表貼在three.js的模型上。這個問題其實很簡單,因爲二者都是渲染成canvas的,直接用echarts生成的canvas當作貼圖就可以了。
方法確定可行,那麼我們就直接開始擼代碼。
先搭建一個three的基本場景起來,這裏不在複述。
然後新建一個平面,我們把圖片貼在這個平面上即可。
addPlane() {
var geometry = new THREE.PlaneGeometry(10,10);
var material = new THREE.MeshBasicMaterial({
side: THREE.DoubleSide,
// transparent:true
});
this.plane = new THREE.Mesh(geometry, material);
this.scene.add(this.plane);
}
擺好相機的角度,此時場景中是一個白板。
然後打開echarts的官網,找到案例,來個儀表盤吧。代碼拷貝下來。跑起來。
爲了方便演示,我在body中創建了2個div,分別作爲three和圖表的容器。實際開發中圖表的容器不需要顯示出來的,也不需要添加到body中的。
<div id="webgl" style="width:512px;height: 512px;float: left;"></div>
<div id="echart" style="width:512px;height: 512px;margin-left: 620px;"></div>
var myChart = echarts.init(document.getElementById('echart'));
option = {
tooltip: {
formatter: "{a} <br/>{b} : {c}%"
},
//toolbox會在右上角生成兩個功能按鈕,咱們不需要,直接幹掉。
// toolbox: {
// feature: {
// restore: {},
// saveAsImage: {}
// }
// },
series: [
{
name: '業務指標',
type: 'gauge',
detail: { formatter: '{value}%' },
data: [{ value: 50, name: '完成率' }]
}
]
};
option.series[0].data[0].value = (Math.random() * 100).toFixed(2) - 0;
myChart.setOption(option, true);
const dom = document.getElementById("webgl");
const scene = new Basescene(dom);
scene.addPlane();
此時看到下面頁面:
方法一:CanvasTexture
three.js有一個api:CanvasTexture。可以傳入一個canvas對象,用這個方法可以完成上面的任務。
CanvasTexture( canvas : HTMLElement, mapping : Constant, wrapS : Constant, wrapT : Constant, magFilter : Constant, minFilter : Constant, format : Constant, type : Constant, anisotropy : Number )
changeTextureT(texture){
this.plane.material.map = new THREE.CanvasTexture(texture)
this.plane.material.needsUpdate = true
var thiscancas = document.getElementById("echart").getElementsByTagName('canvas')[0]
scene.changeTextureT(thiscancas)
}
運行結果如下,確實不清晰,和他們遇到的問題的一樣。嘗試把echarts繪製大點,但是這個是自適應的,導致儀表板很醜,不是想象的樣子,如果是自己繪製的表格,就可以這樣處理了。
方法二:getDataURL
既然echarts也是渲染canvas,看看api,應該有方法導出圖片。就是下面的api,而且有可選參數,可以設置分辨率。
changeTextureE(texture){
this.plane.material.map = new THREE.TextureLoader().load(texture)
this.plane.material.needsUpdate = true
}
var texture = myChart.getDataURL({
pixelRatio: 4,
backgroundColor: '#fff'
});
scene.changeTextureE(texture)
分辨率設置爲4確實清晰多了。
下面三個圖分別是分辨率1,分辨率4以及方法1繪製的效果對比。
3個圖區別很明顯,方法2>方法1。該使用什麼方法已經很明白了。
下面是動態圖片,開始沒有貼圖,然後貼上方法1生成的貼圖,接着閃一下,換成方法2分辨率4生成的貼圖。放大還是很清晰的。
最後問題:echarts的圖表很多都是有緩衝動畫的,如果也想在貼圖上實時刷新,可行嗎。幀率能跟上嗎。
全部代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>three.js使用Echarts貼圖</title>
<script src="../js/three.js"></script>
<script src="../js/controls/OrbitControls.js"></script>
<script src="./echarts.js"></script>
</head>
<body>
<div id="webgl" style="width:512px;height: 512px;float: left;"></div>
<div id="echart" style="width:512px;height: 512px;margin-left: 620px;"></div>
<script>
var myChart = echarts.init(document.getElementById('echart'));
option = {
tooltip: {
formatter: "{a} <br/>{b} : {c}%"
},
// toolbox: {
// feature: {
// restore: {},
// saveAsImage: {}
// }
// },
series: [
{
name: '業務指標',
type: 'gauge',
detail: { formatter: '{value}%' },
data: [{ value: 50, name: '完成率' }]
}
]
};
option.series[0].data[0].value = (Math.random() * 100).toFixed(2) - 0;
myChart.setOption(option, true);
class Basescene {
constructor(dom) {
this.id = (new Date()).getTime();
this.dom = dom;
this.divWidth = this.dom.offsetWidth;
this.divHeight = this.dom.offsetHeight;
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(45, this.divWidth / this.divHeight, 1, 2000);
this.renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true
});
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.init();
}
init() {
this.camera.position.set(0, 0, 20);
this.camera.lookAt(this.scene.position);
this.renderer.setClearColor(0x222222);
this.renderer.setSize(this.divWidth, this.divHeight);
this.dom.appendChild(this.renderer.domElement);
// this.scene.add(new THREE.AxesHelper(10));
this.animate();
this.addLight();
console.log(this.scene);
}
addLight() {
const light = new THREE.AmbientLight(0xffffff);
this.scene.add(light);
}
render() {
this.renderer.render(this.scene, this.camera);
}
animate = () => {
this.request = requestAnimationFrame(this.animate);
this.render();
}
addPlane() {
var geometry = new THREE.PlaneGeometry(10, 10);
var material = new THREE.MeshBasicMaterial({
side: THREE.DoubleSide,
// transparent:true
});
this.plane = new THREE.Mesh(geometry, material);
this.scene.add(this.plane);
}
changeTextureE(texture) {
this.plane.material.map = new THREE.TextureLoader().load(texture)
this.plane.material.needsUpdate = true
}
changeTextureT(texture) {
this.plane.material.map = new THREE.CanvasTexture(texture)
this.plane.material.needsUpdate = true
}
}
const dom = document.getElementById("webgl");
const scene = new Basescene(dom);
scene.addPlane();
setTimeout(() => {
var thiscancas = document.getElementById("echart").getElementsByTagName('canvas')[0]
scene.changeTextureT(thiscancas)
}, 2000);
setTimeout(() => {
var texture = myChart.getDataURL({
pixelRatio: 4,
backgroundColor: '#fff'
});
scene.changeTextureE(texture)
}, 4000);
</script>
</body>
</html>