跳到主要内容
基于 ECharts 与 Three.js 的碳排放可视化大屏实现 | 极客日志
HTML / CSS 大前端
基于 ECharts 与 Three.js 的碳排放可视化大屏实现 本项目基于 ECharts 和 Three.js 构建碳排放可视化大屏,涵盖教室、实验室及宿舍的能耗数据展示。系统采用 Flex 布局实现响应式界面,集成柱状图、折线图、饼图及 3D 地球模型。代码包含 HTML 结构、CSS 样式及核心 JS 逻辑,支持动态切换图表类型与时间维度,适用于校园能源管理场景。
效果展示
项目架构
整体布局采用 Flexbox 响应式设计,分为左、中、右三栏。左侧和右侧主要承载各类图表模块,中间区域展示核心数据指标与 3D 地球模型。
代码分析
1. 主界面结构 (view.html)
页面头部包含标题与实时时钟,主体部分通过 div.box 包裹三列内容。引入了 ECharts 和 Three.js 库,并使用了 Jinja2 模板语法加载静态资源。
<!DOCTYPE html >
<html lang ="en" >
<head >
<meta charset ="UTF-8" >
<title > Title</title >
<link rel ="stylesheet" href ="{{ url_for('static', filename='css/view.css') }}" >
</head >
<body >
<header >
<div class ="container" >
<h1 > 碳排放可视化大屏</ >
教室
实验室
宿舍
无纸化考试次数
12
没想好
419
没想好
10.5
没想好
4.7
没想好
教室
实验室
宿舍
早
晚
h1
</div >
<div class ="showTime" >
</div >
<script >
var t = null ;
t = setTimeout (time, 1000 );
function time ( ) {
clearTimeout (t);
var a = new Date ();
var y = a.getFullYear ();
var mt = a.getMonth () + 1 ;
var day = a.getDate ();
var h = a.getHours ();
var m = a.getMinutes ();
var s = a.getSeconds ();
document .querySelector (".showTime" ).innerHTML = '当前时间:' + y + "年" + mt + "月" + day + "-" + h + "时" + m + "分" + s + "秒" ;
t = setTimeout (time, 1000 );
}
</script >
</header >
<section class ="box" >
<div class ="column" >
<div class ="public bar right-chart-module" >
<div class ="chart-title" >
<h2 >
</h2 >
<div class ="chart-tabs" >
<button class ="tab-btn active" data-type ="classroom" >
</button >
<button class ="tab-btn" data-type ="lab" >
</button >
<button class ="tab-btn" data-type ="dormitory" >
</button >
</div >
</div >
<div class ="char" id ="leftBarChart" >
</div >
<div class ="public-one" >
</div >
</div >
<div class ="public bar" >
<h2 >
</h2 >
<div class ="chart-tabs" >
<div class ="char" id ="horizontalBarChart" >
</div >
</div >
<div class ="public-one" >
</div >
</div >
</div >
<div class ="column" >
<div class ="num" >
<div class ="globe-container" >
<canvas id ="globe-canvas" >
</canvas >
</div >
<div class ="globe-data" >
<div class ="data-row" >
<div class ="data-item" >
<div class ="data-value" id ="global-temp" >
</div >
<div class ="data-label" >
</div >
</div >
<div class ="data-item" >
<div class ="data-value" id ="co2-level" >
</div >
<div class ="data-label" >
</div >
</div >
</div >
<div class ="data-row" >
<div class ="data-item" >
<div class ="data-value" id ="china-emission" >
</div >
<div class ="data-label" >
</div >
</div >
<div class ="data-item" >
<div class ="data-value" id ="world-emission" >
</div >
<div class ="data-label" >
</div >
</div >
</div >
</div >
</div >
</div >
<div class ="column" >
<div class ="public bar right-chart-module" >
<div class ="chart-title" >
<h2 >
</h2 >
<div class ="chart-tabs" >
<button class ="tab-btn active" data-type ="classroom" >
</button >
<button class ="tab-btn" data-type ="lab" >
</button >
<button class ="tab-btn" data-type ="dormitory" >
</button >
</div >
</div >
<div class ="char" id ="vehicleChart" >
</div >
<div class ="public-one" >
</div >
</div >
<div class ="public pie" >
<div class ="chart-title" >
<h2 >
</h2 >
<div class ="chart-tabs" >
<button class ="tab-btn active" data-time ="morning" >
</button >
<button class ="tab-btn" data-time ="evening" >
</button >
</div >
</div >
<div class ="char" id ="pieChart" >
</div >
<div class ="public-one" >
</div >
</div >
</div >
</section >
<script src ="{{ url_for('static', filename='js/echarts.js') }}" >
</script >
<script src ="{{ url_for('static', filename='js/echarts.min.js') }}" >
</script >
<script src ="{{ url_for('static', filename='js/view.js') }}" >
</script >
<script src ="{{ url_for('static', filename='js/flexible.js') }}" >
</script >
<script src ="{{ url_for('static', filename='js/jiazai.js') }}" >
</script >
<script >
document .addEventListener ('DOMContentLoaded' , () => {
setTimeout (() => window .dispatchEvent (new Event ('resize' )), 300 );
});
</script >
</body >
</html >
2. 样式设计 (view.css) 使用 Rem 单位配合媒体查询实现自适应布局。背景图固定定位,确保全屏显示无滚动条。图表容器添加了科技感边框装饰,文字颜色统一为白色或青色,适配深色主题。
* { margin : 0 ; padding : 0 ; box-sizing : border-box; }
li { list-style : none; }
@font-face { font-family : electronicFont; src : url (/static/font/ziti.TTF ); }
html { font-size : 100px ; overflow : hidden; width : 100% ; height : 100% ; }
body { font-family : Arial, Helvetica, sans-serif; margin : 0 ; padding : 0 ; background : url (/static/images/bei.jpg ) no-repeat; background-size : cover; background-attachment : fixed; line-height : 1.15 ; width : 100% ; height : 100% ; overflow : hidden; }
header { position : relative; height : 1.11rem ; background : url (/static/images/head.png ) no-repeat top center; background-size : 100% 100% ; width : 100% ; }
header .container h1 { font-size : 0.51rem ; color : #fff ; text-align : center; line-height : 1.07rem ; -webkit-text -stroke : 2px #00f7ff ; text-shadow : 0 0 10px rgba (0 , 247 , 255 , 0.5 ); }
header .showTime { position : absolute; right : 0.4rem ; top : 0 ; line-height : 1rem ; color : #2680ce ; font-size : 0.27rem ; font-family : 'electronicFont' ; white-space : nowrap; }
.box { display : flex; min-width : 1024px ; max-width : 1920px ; width : 100% ; height : calc (100% - 1.11rem ); margin : 0 auto; padding : 0.13rem ; overflow : hidden; }
.box .column { flex : 3 ; height : 100% ; display : flex; flex-direction : column; }
.box .column :nth-child (2 ) { flex : 5 ; margin : 0 0.13rem ; }
.box .public { position : relative; height : 5.6rem ; border : 1px solid rgba (25 , 186 , 139 , 0.17 ); background : url (/static/images/bian.png ) rgba (255 , 255 , 255 , 0.04 ); padding : 0 0.2rem 0.53rem ; margin-bottom : 0.2rem ; width : 100% ; }
@media screen and (max-width : 1024px ) { html { font-size : 42px !important ; } }
@media screen and (min-width : 1920px ) { html { font-size : 80px !important ; } }
3. 核心逻辑 (view.js) 这里包含了 ECharts 图表配置与 Three.js 3D 地球初始化。为了保持代码整洁,我将不同模块的逻辑封装在 IIFE 中。
柱状图模块 :支持点击 Tab 切换教室、实验室、宿舍数据。利用 updateChart 函数动态更新 Option 配置,注意处理了 resize 事件以保证窗口缩放后图表不变形。
折线图模块 (用水量):使用了平滑曲线和面积渐变填充,视觉上更具科技感。Tooltip 自定义了格式化输出,显示具体数值和单位。
饼图模块 :展示早晚交通工具统计,采用了玫瑰图样式(roseType),增加了动画延迟效果,让加载过程更流畅。
横向柱状图 :用于展示各学院无纸化考试次数,结合了进度条样式,直观对比数据差异。
3D 地球模块 :引入 Three.js 构建场景、相机和渲染器。加载地球纹理、法线贴图和高光贴图,添加环境光与方向光模拟真实光照。动画循环中持续旋转地球,并通过监听容器尺寸变化调整相机比例。
(function ( ){
const chartData = {
classroom :{
title :'教室每日用电量统计' ,
data :{
categories :['周一' ,'周二' ,'周三' ,'周四' ,'周五' ,'周六' ,'周日' ],
series :[
{name :'早' ,data :[6.28 ,9.55 ,8.69 ,8.63 ,6.64 ,4.17 ,2.69 ]},
{name :'晚' ,data :[5.71 ,8.65 ,5.32 ,5.91 ,5.77 ,3.17 ,1.85 ]}
]
},
colors :['#5470C6' ,'#91CC75' ]
},
lab :{
title :'实验室每日用电量统计' ,
data :{
categories :['周一' ,'周二' ,'周三' ,'周四' ,'周五' ,'周六' ,'周日' ],
series :[
{name :'早' ,data :[13.85 ,17.78 ,20.12 ,16.77 ,14.69 ,3.46 ,5.72 ]},
{name :'晚' ,data :[9.05 ,11.72 ,14.37 ,11.42 ,11.62 ,3.17 ,5.63 ]}
]
},
colors :['#EE6666' ,'#FAC858' ]
},
dormitory :{
title :'宿舍每日用电量统计' ,
data :{
categories :['周一' ,'周二' ,'周三' ,'周四' ,'周五' ,'周六' ,'周日' ],
series :[
{name :'早' ,data :[4.23 ,3.98 ,4.56 ,4.01 ,4.34 ,2.78 ,2.45 ]},
{name :'晚' ,data :[5.31 ,5.45 ,5.12 ,5.78 ,6.89 ,7.01 ,5.67 ]}
]
},
colors :['#73C0DE' ,'#3BA272' ]
}
};
document .addEventListener ('DOMContentLoaded' ,function ( ){
var barChartDom = document .querySelector ('.bar .char' );
var titleElement = document .querySelector ('.bar .chart-title h2' );
var tabButtons = document .querySelectorAll ('.bar .tab-btn' );
if (barChartDom && titleElement && tabButtons.length >0 ){
var barChart = echarts.init (barChartDom);
var currentType = 'classroom' ;
function updateChart (type ){
const data = chartData[type].data ;
const colors = chartData[type].colors ;
const barOption = {
color : colors,
title :{text : chartData[type].title ,left :'center' ,textStyle :{fontSize :16 ,color :'#fff' },top :-3 },
tooltip :{trigger :'axis' ,axisPointer :{type :'shadow' }},
legend :{data :['早' ,'晚' ],textStyle :{color :'#fff' },top :20 },
grid :{top :'20%' ,left :'3%' ,right :'4%' ,bottom :'3%' ,containLabel :true },
xAxis :{type :'category' ,data : data.categories ,axisLine :{lineStyle :{color :'#fff' }},axisLabel :{color :'#fff' ,interval :0 }},
yAxis :{type :'value' ,name :'用电量 (千瓦)' ,nameTextStyle :{color :'#fff' ,padding :[0 ,0 ,0 ,30 ]},axisLine :{show :true ,lineStyle :{color :'#fff' }},axisLabel :{color :'#fff' },splitLine :{lineStyle :{color :'rgba(255, 255, 255, 0.2)' }}},
series :[
{name :'早' ,type :'bar' ,barWidth :'30%' ,barGap :'50%' ,data : data.series [0 ].data ,},
{name :'晚' ,type :'bar' ,barWidth :'30%' ,data : data.series [1 ].data ,}
]
};
barChart.setOption (barOption);
}
updateChart (currentType);
tabButtons.forEach (button => {
button.addEventListener ('click' ,function ( ){
tabButtons.forEach (btn => btn.classList .remove ('active' ));
this .classList .add ('active' );
currentType = this .dataset .type ;
updateChart (currentType);
});
});
window .addEventListener ('resize' ,function ( ){ barChart.resize (); });
}
});
})();
function initGlobe ( ){
const scene = new THREE .Scene ();
const camera = new THREE .PerspectiveCamera (75 ,1 ,0.1 ,1000 );
const renderer = new THREE .WebGLRenderer ({canvas : document .getElementById ('globe-canvas' ),antialias :true ,alpha :true });
renderer.setSize (400 ,300 );
renderer.setClearColor (0x000000 ,0 );
const geometry = new THREE .SphereGeometry (5 ,64 ,64 );
const material = new THREE .MeshPhongMaterial ({color :0x156289 ,emissive :0x072534 ,shininess :5 ,transparent :true ,opacity :0.8 ,wireframe :false });
const textureLoader = new THREE .TextureLoader ();
material.map = textureLoader.load ('/static/images/img.png' );
material.bumpMap = textureLoader.load ('https://threejs.org/examples/textures/planets/earth_normal_2048.jpg' );
material.specularMap = textureLoader.load ('https://threejs.org/examples/textures/planets/earth_specular_2048.jpg' );
const earth = new THREE .Mesh (geometry, material);
scene.add (earth);
const ambientLight = new THREE .AmbientLight (0x333333 ); scene.add (ambientLight);
const directionalLight = new THREE .DirectionalLight (0xffffff ,1 ); directionalLight.position .set (5 ,3 ,5 ); scene.add (directionalLight);
camera.position .z = 12 ;
function animate ( ){
requestAnimationFrame (animate);
earth.rotation .y += 0.002 ;
renderer.render (scene, camera);
}
animate ();
function resizeGlobe ( ){
const container = document .querySelector ('.globe-container' );
const width = container.clientWidth ;
const height = container.clientHeight ;
camera.aspect = width / height;
camera.updateProjectionMatrix ();
renderer.setSize (width, height);
}
window .addEventListener ('resize' , resizeGlobe);
resizeGlobe ();
}
if (typeofTHREE==='undefined' ){
const script = document .createElement ('script' );
script.src = 'https://cdn.jsdelivr.net/npm/[email protected] /build/three.min.js' ;
script.onload = initGlobe;
document .head .appendChild (script);
}else {
initGlobe ();
}
4. 辅助脚本 flexible.js 负责根据设备像素比动态计算根字体大小,确保在不同分辨率屏幕上布局一致。jiazai.js 则处理资源加载后的缓存刷新,防止样式未生效的问题。
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online