使用JSON配置ECharts实现数据可视化的完整指南与实战技巧
1. ECharts简介与基础概念
ECharts是一个由百度开源的、基于JavaScript的数据可视化库,它可以在PC端和移动设备上流畅运行,兼容当前绝大部分浏览器。ECharts提供了直观、交互丰富、可高度个性化定制的数据可视化图表。
1.1 为什么选择ECharts
- 功能强大:支持折线图、柱状图、散点图、饼图、K线图等常规图表,还支持三维可视化、地理数据可视化等高级功能。
- 高度可定制:通过JSON配置可以精确控制图表的每一个细节。
- 跨平台:支持多种浏览器和移动设备。
- 交互丰富:内置了丰富的交互组件,如数据缩放、数据视图、图例开关等。
1.2 ECharts基本工作原理
ECharts的核心工作原理是通过JSON配置对象来定义图表的所有属性和行为。用户只需要按照ECharts的规范编写配置对象,然后将其传递给ECharts实例,即可生成相应的图表。
// 基本使用示例 // 1. 准备一个具备大小的DOM容器 <div id="main" style="width: 600px;height:400px;"></div> // 2. 引入ECharts <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> // 3. 初始化ECharts实例 var myChart = echarts.init(document.getElementById('main')); // 4. 指定图表的配置项和数据 var option = { title: { text: 'ECharts 入门示例' }, tooltip: {}, legend: { data:['销量'] }, xAxis: { data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"] }, yAxis: {}, series: [{ name: '销量', type: 'bar', data: [5, 20, 36, 10, 10, 20] }] }; // 5. 使用刚指定的配置项和数据显示图表 myChart.setOption(option);
2. JSON配置的基本结构
ECharts的配置项是一个庞大的JavaScript对象,它包含了图表的所有配置信息。理解这个配置结构是掌握ECharts的关键。
2.1 核心配置项
option = { // 标题组件 title: { // 主标题 text: '主标题', // 副标题 subtext: '副标题', // 标题位置 left: 'center', top: 'top' }, // 提示框组件 tooltip: { trigger: 'item' // 触发方式 }, // 图例组件 legend: { data: ['系列1', '系列2'] }, // 网格组件 grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, // x轴配置 xAxis: { type: 'category', data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] }, // y轴配置 yAxis: { type: 'value' }, // 系列列表 series: [ { name: '系列1', type: 'line', // 图表类型 data: [120, 132, 101, 134, 90, 230, 210] }, { name: '系列2', type: 'bar', // 图表类型 data: [220, 182, 191, 234, 290, 330, 310] } ] };
2.2 配置项层级结构
ECharts的配置项具有清晰的层级结构,主要包含以下几个部分:
- 基础配置:如
title
、tooltip
、legend
等全局配置。 - 坐标轴配置:如
xAxis
、yAxis
、grid
等与坐标轴相关的配置。 - 系列配置:
series
数组,每个元素代表一个系列(一种图表类型)。 - 其他组件:如
toolbox
、dataZoom
、visualMap
等辅助组件。
每个部分都有其特定的配置项,可以根据需要进行组合和定制。
3. 常见图表的JSON配置方法
3.1 折线图配置
折线图是ECharts中最常用的图表类型之一,适合展示数据随时间或有序类别的变化趋势。
option = { title: { text: '月平均气温变化' }, tooltip: { trigger: 'axis' }, legend: { data: ['北京', '上海'] }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', boundaryGap: false, data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] }, yAxis: { type: 'value', axisLabel: { formatter: '{value} °C' } }, series: [ { name: '北京', type: 'line', data: [-4, -2, 5, 14, 20, 24, 26, 25, 20, 13, 4, -2], // 标记点 markPoint: { data: [ {type: 'max', name: '最大值'}, {type: 'min', name: '最小值'} ] }, // 标记线 markLine: { data: [ {type: 'average', name: '平均值'} ] }, // 线条样式 lineStyle: { width: 3, color: '#5470C6' }, // 区域填充样式 areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: 'rgba(84, 112, 198, 0.5)' }, { offset: 1, color: 'rgba(84, 112, 198, 0.1)' } ]) }, // 平滑曲线 smooth: true }, { name: '上海', type: 'line', data: [4, 6, 10, 16, 21, 25, 29, 29, 25, 19, 13, 7], markPoint: { data: [ {type: 'max', name: '最大值'}, {type: 'min', name: '最小值'} ] }, lineStyle: { width: 3, color: '#91CC75' }, smooth: true } ] };
3.2 柱状图配置
柱状图适合比较不同类别之间的数值差异。
option = { title: { text: '各季度销售数据' }, tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, legend: { data: ['产品A', '产品B', '产品C'] }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', data: ['Q1', 'Q2', 'Q3', 'Q4'] }, yAxis: { type: 'value', name: '销售额(万元)', axisLabel: { formatter: '{value}' } }, series: [ { name: '产品A', type: 'bar', data: [120, 132, 101, 134], // 柱状图样式 itemStyle: { color: '#5470C6', // 圆角 borderRadius: [5, 5, 0, 0] }, // 柱状图标签 label: { show: true, position: 'top', formatter: '{c} 万元' } }, { name: '产品B', type: 'bar', data: [220, 182, 191, 234], itemStyle: { color: '#91CC75', borderRadius: [5, 5, 0, 0] }, label: { show: true, position: 'top', formatter: '{c} 万元' } }, { name: '产品C', type: 'bar', data: [150, 232, 201, 154], itemStyle: { color: '#FAC858', borderRadius: [5, 5, 0, 0] }, label: { show: true, position: 'top', formatter: '{c} 万元' } } ] };
3.3 饼图配置
饼图适合展示部分与整体的关系。
option = { title: { text: '用户访问来源', subtext: '纯属虚构', left: 'center' }, tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c} ({d}%)' }, legend: { orient: 'vertical', left: 'left', data: ['直接访问', '搜索引擎', '邮件营销', '联盟广告', '视频广告'] }, series: [ { name: '访问来源', type: 'pie', radius: '50%', // 饼图半径 center: ['50%', '60%'], // 饼图中心位置 data: [ {value: 335, name: '直接访问'}, {value: 310, name: '搜索引擎'}, {value: 234, name: '邮件营销'}, {value: 135, name: '联盟广告'}, {value: 1548, name: '视频广告'} ], // 高亮样式 emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0, 0, 0, 0.5)' } }, // 标签样式 label: { formatter: '{b}: {c} ({d}%)' }, // 饼图样式 itemStyle: { // 自定义颜色 color: function(params) { var colorList = ['#5470C6', '#91CC75', '#FAC858', '#EE6666', '#73C0DE']; return colorList[params.dataIndex]; } }, // 环形图设置 // radius: ['40%', '70%'], // 取消注释此行可变为环形图 } ] };
3.4 散点图配置
散点图适合展示两个变量之间的关系。
option = { title: { text: '身高与体重关系散点图' }, tooltip: { trigger: 'axis', formatter: function (params) { return '身高: ' + params[0].value[0] + 'cm<br />体重: ' + params[0].value[1] + 'kg'; } }, grid: { left: '3%', right: '7%', bottom: '3%', containLabel: true }, xAxis: { name: '身高(cm)', type: 'value', scale: true, axisLabel: { formatter: '{value}' } }, yAxis: { name: '体重(kg)', type: 'value', scale: true, axisLabel: { formatter: '{value}' } }, series: [{ name: '男性', type: 'scatter', // 散点大小 symbolSize: 10, data: [ [170, 65], [175, 70], [180, 75], [185, 80], [190, 85], [172, 68], [177, 72], [182, 77], [187, 82], [192, 87], [168, 63], [173, 67], [178, 72], [183, 77], [188, 82] ], // 散点样式 itemStyle: { color: '#5470C6' } }, { name: '女性', type: 'scatter', symbolSize: 10, data: [ [160, 50], [165, 55], [170, 60], [175, 65], [180, 70], [162, 52], [167, 57], [172, 62], [177, 67], [182, 72], [158, 48], [163, 53], [168, 58], [173, 63], [178, 68] ], itemStyle: { color: '#EE6666' } }] };
4. 高级配置技巧
4.1 多图表组合
ECharts支持在一个画布上组合多种不同类型的图表,以展示更复杂的数据关系。
option = { title: { text: '销售与利润分析' }, tooltip: { trigger: 'axis', axisPointer: { type: 'cross', crossStyle: { color: '#999' } } }, toolbox: { feature: { dataView: {show: true, readOnly: false}, magicType: {show: true, type: ['line', 'bar']}, restore: {show: true}, saveAsImage: {show: true} } }, legend: { data: ['销售额', '利润', '利润率'] }, xAxis: [ { type: 'category', data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], axisPointer: { type: 'shadow' } } ], yAxis: [ { type: 'value', name: '销售额', min: 0, axisLabel: { formatter: '{value} 万元' } }, { type: 'value', name: '利润率', min: 0, max: 100, axisLabel: { formatter: '{value} %' } } ], series: [ { name: '销售额', type: 'bar', data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330] }, { name: '利润', type: 'bar', data: [20, 32, 11, 34, 10, 130, 110, 82, 91, 134, 190, 230] }, { name: '利润率', type: 'line', yAxisIndex: 1, // 使用第二个y轴 data: [16.7, 24.2, 10.9, 25.4, 11.1, 56.5, 52.4, 45.1, 47.6, 57.3, 65.5, 69.7], // 线上标记点 markPoint: { data: [ {type: 'max', name: '最大值'}, {type: 'min', name: '最小值'} ] } } ] };
4.2 动态数据更新
ECharts支持动态更新数据,实现实时数据可视化。
// 初始化图表 var myChart = echarts.init(document.getElementById('main')); // 初始配置 var option = { title: { text: '实时数据监控' }, tooltip: { trigger: 'axis' }, legend: { data: ['CPU使用率', '内存使用率'] }, xAxis: { type: 'category', boundaryGap: false, data: [] // 初始为空,后续动态添加 }, yAxis: { type: 'value', min: 0, max: 100, axisLabel: { formatter: '{value}%' } }, series: [ { name: 'CPU使用率', type: 'line', data: [] // 初始为空,后续动态添加 }, { name: '内存使用率', type: 'line', data: [] // 初始为空,后续动态添加 } ] }; // 应用初始配置 myChart.setOption(option); // 模拟实时数据更新 var dataCount = 20; // 显示的数据点数量 var timeData = []; // 时间数据 var cpuData = []; // CPU数据 var memoryData = []; // 内存数据 // 添加初始数据 for (var i = 0; i < dataCount; i++) { var time = new Date(); time.setSeconds(time.getSeconds() - (dataCount - i)); timeData.push(formatTime(time)); cpuData.push(Math.round(Math.random() * 100)); memoryData.push(Math.round(Math.random() * 100)); } // 更新初始数据 myChart.setOption({ xAxis: { data: timeData }, series: [ { name: 'CPU使用率', data: cpuData }, { name: '内存使用率', data: memoryData } ] }); // 定时更新数据 setInterval(function() { // 移除第一个数据点 timeData.shift(); cpuData.shift(); memoryData.shift(); // 添加新的数据点 var time = new Date(); timeData.push(formatTime(time)); cpuData.push(Math.round(Math.random() * 100)); memoryData.push(Math.round(Math.random() * 100)); // 更新图表 myChart.setOption({ xAxis: { data: timeData }, series: [ { name: 'CPU使用率', data: cpuData }, { name: '内存使用率', data: memoryData } ] }); }, 2000); // 每2秒更新一次 // 格式化时间函数 function formatTime(date) { var hours = date.getHours(); var minutes = date.getMinutes(); var seconds = date.getSeconds(); hours = hours < 10 ? '0' + hours : hours; minutes = minutes < 10 ? '0' + minutes : minutes; seconds = seconds < 10 ? '0' + seconds : seconds; return hours + ':' + minutes + ':' + seconds; }
4.3 主题与样式定制
ECharts支持通过主题和样式定制来改变图表的外观。
// 自定义主题 var theme = { "color": [ "#3fb1e3", "#6be6c1", "#626c91", "#a0a7e6", "#c4ebad", "#96dee8" ], "backgroundColor": "#ffffff", "textStyle": {}, "title": { "textStyle": { "color": "#3fb1e3" }, "subtextStyle": { "color": "#18181c" } }, "line": { "itemStyle": { "normal": { "borderWidth": "3" } }, "lineStyle": { "normal": { "width": "4" } }, "symbolSize": "10", "symbol": "emptyCircle", "smooth": true }, "radar": { "itemStyle": { "normal": { "borderWidth": "3" } }, "lineStyle": { "normal": { "width": "4" } }, "symbolSize": "10", "symbol": "emptyCircle", "smooth": true }, "bar": { "itemStyle": { "normal": { "barBorderRadius": 0 } } }, "pie": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" }, "emphasis": { "borderWidth": 0 } } }, "scatter": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" } } }, "boxplot": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" } } }, "parallel": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" } } }, "sankey": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" } } }, "funnel": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" } } }, "gauge": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" } } }, "candlestick": { "itemStyle": { "normal": { "color": "#e6a53d", "color0": "transparent", "borderColor": "#e6a53d", "borderColor0": "#3fb1e3", "borderWidth": "2" } } }, "graph": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" } }, "lineStyle": { "normal": { "width": "1", "color": "#aaaaaa" } }, "symbolSize": "10", "symbol": "emptyCircle", "smooth": true, "color": [ "#3fb1e3", "#6be6c1", "#626c91", "#a0a7e6", "#c4ebad", "#96dee8" ], "label": { "normal": { "textStyle": { "color": "#ffffff" } } } }, "map": { "itemStyle": { "normal": { "areaColor": "#f3f3f3", "borderColor": "#999999", "borderWidth": 0.5 }, "emphasis": { "areaColor": "rgba(255,178,72,1)", "borderColor": "#eb8146", "borderWidth": 1 } }, "label": { "normal": { "textStyle": { "color": "#893448" } }, "emphasis": { "textStyle": { "color": "rgb(137,52,72)" } } } }, "geo": { "itemStyle": { "normal": { "areaColor": "#f3f3f3", "borderColor": "#999999", "borderWidth": 0.5 }, "emphasis": { "areaColor": "rgba(255,178,72,1)", "borderColor": "#eb8146", "borderWidth": 1 } }, "label": { "normal": { "textStyle": { "color": "#893448" } }, "emphasis": { "textStyle": { "color": "rgb(137,52,72)" } } } }, "categoryAxis": { "axisLine": { "show": true, "lineStyle": { "color": "#cccccc" } }, "axisTick": { "show": false, "lineStyle": { "color": "#333" } }, "axisLabel": { "show": true, "textStyle": { "color": "#999999" } }, "splitLine": { "show": false, "lineStyle": { "color": [ "#eeeeee" ] } }, "splitArea": { "show": false, "areaStyle": { "color": [ "rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)" ] } } }, "valueAxis": { "axisLine": { "show": true, "lineStyle": { "color": "#cccccc" } }, "axisTick": { "show": false, "lineStyle": { "color": "#333" } }, "axisLabel": { "show": true, "textStyle": { "color": "#999999" } }, "splitLine": { "show": true, "lineStyle": { "color": [ "#eeeeee" ] } }, "splitArea": { "show": false, "areaStyle": { "color": [ "rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)" ] } } }, "logAxis": { "axisLine": { "show": true, "lineStyle": { "color": "#cccccc" } }, "axisTick": { "show": false, "lineStyle": { "color": "#333" } }, "axisLabel": { "show": true, "textStyle": { "color": "#999999" } }, "splitLine": { "show": true, "lineStyle": { "color": [ "#eeeeee" ] } }, "splitArea": { "show": false, "areaStyle": { "color": [ "rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)" ] } } }, "timeAxis": { "axisLine": { "show": true, "lineStyle": { "color": "#cccccc" } }, "axisTick": { "show": false, "lineStyle": { "color": "#333" } }, "axisLabel": { "show": true, "textStyle": { "color": "#999999" } }, "splitLine": { "show": true, "lineStyle": { "color": [ "#eeeeee" ] } }, "splitArea": { "show": false, "areaStyle": { "color": [ "rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)" ] } } }, "toolbox": { "iconStyle": { "normal": { "borderColor": "#999999" }, "emphasis": { "borderColor": "#666666" } } }, "legend": { "textStyle": { "color": "#999999" } }, "tooltip": { "axisPointer": { "lineStyle": { "color": "#cccccc", "width": 1 }, "crossStyle": { "color": "#cccccc", "width": 1 } } }, "timeline": { "lineStyle": { "color": "#626c91", "width": 1 }, "itemStyle": { "normal": { "color": "#626c91", "borderWidth": 1 }, "emphasis": { "color": "#626c91" } }, "controlStyle": { "normal": { "color": "#626c91", "borderColor": "#626c91", "borderWidth": 0.5 }, "emphasis": { "color": "#626c91", "borderColor": "#626c91", "borderWidth": 0.5 } }, "checkpointStyle": { "color": "#3fb1e3", "borderColor": "rgba(63,177,227,0.15)" }, "label": { "normal": { "textStyle": { "color": "#626c91" } }, "emphasis": { "textStyle": { "color": "#626c91" } } } }, "visualMap": { "color": [ "#2a99c9", "#c8b88b" ] }, "dataZoom": { "backgroundColor": "rgba(255,255,255,0)", "dataBackgroundColor": "rgba(222,222,222,1)", "fillerColor": "rgba(114,230,212,0.25)", "handleColor": "#cccccc", "handleSize": "100%", "textStyle": { "color": "#999999" } }, "markPoint": { "label": { "normal": { "textStyle": { "color": "#ffffff" } }, "emphasis": { "textStyle": { "color": "#ffffff" } } } } }; // 注册主题 echarts.registerTheme('custom-theme', theme); // 使用主题初始化图表 var myChart = echarts.init(document.getElementById('main'), 'custom-theme'); // 配置项 var option = { title: { text: '自定义主题示例' }, tooltip: { trigger: 'axis' }, legend: { data: ['邮件营销', '联盟广告', '视频广告'] }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', boundaryGap: false, data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] }, yAxis: { type: 'value' }, series: [ { name: '邮件营销', type: 'line', stack: '总量', data: [120, 132, 101, 134, 90, 230, 210] }, { name: '联盟广告', type: 'line', stack: '总量', data: [220, 182, 191, 234, 290, 330, 310] }, { name: '视频广告', type: 'line', stack: '总量', data: [150, 232, 201, 154, 190, 330, 410] } ] }; // 使用配置项显示图表 myChart.setOption(option);
4.4 事件交互
ECharts提供了丰富的事件交互机制,可以通过监听图表事件来实现自定义交互。
// 初始化图表 var myChart = echarts.init(document.getElementById('main')); // 配置项 var option = { title: { text: '事件交互示例' }, tooltip: { trigger: 'axis' }, legend: { data: ['销量', '收入'] }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] }, yAxis: [ { type: 'value', name: '销量', min: 0, axisLabel: { formatter: '{value} 件' } }, { type: 'value', name: '收入', min: 0, axisLabel: { formatter: '{value} 元' } } ], series: [ { name: '销量', type: 'bar', data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330] }, { name: '收入', type: 'line', yAxisIndex: 1, data: [1200, 1320, 1010, 1340, 900, 2300, 2100, 1820, 1910, 2340, 2900, 3300] } ] }; // 使用配置项显示图表 myChart.setOption(option); // 监听点击事件 myChart.on('click', function(params) { console.log(params); // 显示点击的数据信息 var info = document.getElementById('info'); info.innerHTML = ` <p>系列名称: ${params.seriesName}</p> <p>数据名称: ${params.name}</p> <p>数据值: ${params.value}</p> `; }); // 监听图例点击事件 myChart.on('legendselectchanged', function(params) { console.log('图例选择变化:', params); var info = document.getElementById('legendInfo'); var selected = params.selected; var html = '<p>当前图例选择状态:</p><ul>'; for (var name in selected) { html += `<li>${name}: ${selected[name] ? '显示' : '隐藏'}</li>`; } html += '</ul>'; info.innerHTML = html; }); // 监听数据区域缩放事件 myChart.on('datazoom', function(params) { console.log('数据区域缩放:', params); var info = document.getElementById('zoomInfo'); info.innerHTML = ` <p>数据区域缩放事件:</p> <p>起始百分比: ${params.start}%</p> <p>结束百分比: ${params.end}%</p> `; }); // 添加按钮交互 document.getElementById('changeData').addEventListener('click', function() { // 生成随机数据 var newData1 = []; var newData2 = []; for (var i = 0; i < 12; i++) { newData1.push(Math.round(Math.random() * 500)); newData2.push(Math.round(Math.random() * 5000)); } // 更新数据 myChart.setOption({ series: [ { data: newData1 }, { data: newData2 } ] }); }); // 添加图表类型切换 document.getElementById('changeType').addEventListener('click', function() { // 获取当前系列类型 var series = myChart.getOption().series; var newType1 = series[0].type === 'bar' ? 'line' : 'bar'; var newType2 = series[1].type === 'line' ? 'bar' : 'line'; // 更新图表类型 myChart.setOption({ series: [ { type: newType1 }, { type: newType2 } ] }); });
5. 实战案例与代码示例
5.1 销售数据仪表盘
下面是一个综合性的销售数据仪表盘示例,展示了如何使用ECharts创建一个复杂的数据可视化界面。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>销售数据仪表盘</title> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> <style> body { margin: 0; padding: 20px; font-family: Arial, sans-serif; background-color: #f5f7fa; } .dashboard { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: auto auto; gap: 20px; max-width: 1400px; margin: 0 auto; } .chart-container { background-color: #fff; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.1); padding: 20px; height: 400px; } .full-width { grid-column: 1 / -1; } h1 { text-align: center; color: #333; margin-bottom: 30px; } .chart-title { font-size: 18px; font-weight: bold; margin-bottom: 15px; color: #333; } </style> </head> <body> <h1>销售数据仪表盘</h1> <div class="dashboard"> <div class="chart-container"> <div class="chart-title">月度销售趋势</div> <div id="salesTrend" style="width: 100%; height: 340px;"></div> </div> <div class="chart-container"> <div class="chart-title">产品销售占比</div> <div id="productRatio" style="width: 100%; height: 340px;"></div> </div> <div class="chart-container full-width"> <div class="chart-title">各区域销售对比</div> <div id="regionComparison" style="width: 100%; height: 340px;"></div> </div> </div> <script> // 初始化所有图表 var salesTrendChart = echarts.init(document.getElementById('salesTrend')); var productRatioChart = echarts.init(document.getElementById('productRatio')); var regionComparisonChart = echarts.init(document.getElementById('regionComparison')); // 月度销售趋势配置 var salesTrendOption = { tooltip: { trigger: 'axis', axisPointer: { type: 'cross', crossStyle: { color: '#999' } } }, toolbox: { feature: { dataView: {show: true, readOnly: false}, magicType: {show: true, type: ['line', 'bar']}, restore: {show: true}, saveAsImage: {show: true} } }, legend: { data: ['销售额', '利润', '增长率'] }, xAxis: [ { type: 'category', data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], axisPointer: { type: 'shadow' } } ], yAxis: [ { type: 'value', name: '金额', min: 0, axisLabel: { formatter: '{value} 万元' } }, { type: 'value', name: '增长率', min: -50, max: 50, axisLabel: { formatter: '{value} %' } } ], series: [ { name: '销售额', type: 'bar', data: [120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330] }, { name: '利润', type: 'bar', data: [20, 32, 11, 34, 10, 130, 110, 82, 91, 134, 190, 230] }, { name: '增长率', type: 'line', yAxisIndex: 1, data: [5.2, 8.3, -3.1, 12.4, -2.8, 25.6, 18.9, 10.2, 15.7, 22.5, 30.1, 35.8], // 标记特殊点 markPoint: { data: [ {type: 'max', name: '最大值'}, {type: 'min', name: '最小值'} ] }, // 平滑曲线 smooth: true } ] }; // 产品销售占比配置 var productRatioOption = { tooltip: { trigger: 'item', formatter: '{a} <br/>{b}: {c}万元 ({d}%)' }, legend: { orient: 'vertical', left: 10, data: ['产品A', '产品B', '产品C', '产品D', '产品E'] }, series: [ { name: '销售占比', type: 'pie', radius: ['50%', '70%'], avoidLabelOverlap: false, itemStyle: { borderRadius: 10, borderColor: '#fff', borderWidth: 2 }, label: { show: false, position: 'center' }, emphasis: { label: { show: true, fontSize: '16', fontWeight: 'bold' } }, labelLine: { show: false }, data: [ {value: 335, name: '产品A'}, {value: 310, name: '产品B'}, {value: 234, name: '产品C'}, {value: 135, name: '产品D'}, {value: 1548, name: '产品E'} ] } ] }; // 各区域销售对比配置 var regionComparisonOption = { tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, legend: { data: ['华东', '华南', '华北', '西部', '中部'] }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', data: ['Q1', 'Q2', 'Q3', 'Q4'] }, yAxis: { type: 'value', name: '销售额(万元)', axisLabel: { formatter: '{value}' } }, series: [ { name: '华东', type: 'bar', stack: 'total', emphasis: {focus: 'series'}, data: [320, 332, 301, 334] }, { name: '华南', type: 'bar', stack: 'total', emphasis: {focus: 'series'}, data: [120, 132, 101, 134] }, { name: '华北', type: 'bar', stack: 'total', emphasis: {focus: 'series'}, data: [220, 182, 191, 234] }, { name: '西部', type: 'bar', stack: 'total', emphasis: {focus: 'series'}, data: [150, 232, 201, 154] }, { name: '中部', type: 'bar', stack: 'total', emphasis: {focus: 'series'}, data: [98, 77, 101, 99] } ] }; // 应用配置项 salesTrendChart.setOption(salesTrendOption); productRatioChart.setOption(productRatioOption); regionComparisonChart.setOption(regionComparisonOption); // 响应式调整 window.addEventListener('resize', function() { salesTrendChart.resize(); productRatioChart.resize(); regionComparisonChart.resize(); }); </script> </body> </html>
5.2 地理数据可视化
ECharts提供了强大的地理数据可视化功能,可以创建地图、散点地图等地理信息图表。
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>地理数据可视化</title> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/map/js/china.js"></script> <style> body { margin: 0; padding: 20px; font-family: Arial, sans-serif; background-color: #f5f7fa; } .container { max-width: 1200px; margin: 0 auto; } h1 { text-align: center; color: #333; margin-bottom: 30px; } .chart-container { background-color: #fff; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.1); padding: 20px; margin-bottom: 20px; } .chart-title { font-size: 18px; font-weight: bold; margin-bottom: 15px; color: #333; } #chinaMap { width: 100%; height: 600px; } .controls { margin-bottom: 20px; text-align: center; } button { padding: 8px 15px; margin: 0 5px; background-color: #5470C6; color: white; border: none; border-radius: 4px; cursor: pointer; } button:hover { background-color: #465A9E; } .info-panel { background-color: #f0f5ff; border-left: 4px solid #5470C6; padding: 15px; margin-top: 20px; border-radius: 4px; } </style> </head> <body> <div class="container"> <h1>中国销售数据地理分布</h1> <div class="controls"> <button id="salesBtn">销售数据</button> <button id="densityBtn">网点密度</button> <button id="growthBtn">增长率</button> </div> <div class="chart-container"> <div class="chart-title">中国各省销售数据热力图</div> <div id="chinaMap"></div> </div> <div class="info-panel" id="infoPanel"> <p>点击地图上的省份查看详细数据</p> </div> </div> <script> // 初始化地图 var chinaMapChart = echarts.init(document.getElementById('chinaMap')); // 销售数据 var salesData = [ {name: '北京', value: 234.56}, {name: '天津', value: 123.45}, {name: '上海', value: 345.67}, {name: '重庆', value: 156.78}, {name: '河北', value: 234.56}, {name: '河南', value: 345.67}, {name: '云南', value: 123.45}, {name: '辽宁', value: 234.56}, {name: '黑龙江', value: 123.45}, {name: '湖南', value: 234.56}, {name: '安徽', value: 123.45}, {name: '山东', value: 345.67}, {name: '新疆', value: 123.45}, {name: '江苏', value: 456.78}, {name: '浙江', value: 345.67}, {name: '江西', value: 123.45}, {name: '湖北', value: 234.56}, {name: '广西', value: 123.45}, {name: '甘肃', value: 123.45}, {name: '山西', value: 123.45}, {name: '内蒙古', value: 123.45}, {name: '陕西', value: 234.56}, {name: '吉林', value: 123.45}, {name: '福建', value: 234.56}, {name: '贵州', value: 123.45}, {name: '广东', value: 567.89}, {name: '青海', value: 123.45}, {name: '西藏', value: 123.45}, {name: '四川', value: 234.56}, {name: '宁夏', value: 123.45}, {name: '海南', value: 123.45}, {name: '台湾', value: 123.45}, {name: '香港', value: 234.56}, {name: '澳门', value: 123.45} ]; // 网点密度数据 var densityData = [ {name: '北京', value: 85}, {name: '天津', value: 72}, {name: '上海', value: 90}, {name: '重庆', value: 65}, {name: '河北', value: 55}, {name: '河南', value: 60}, {name: '云南', value: 45}, {name: '辽宁', value: 70}, {name: '黑龙江', value: 50}, {name: '湖南', value: 58}, {name: '安徽', value: 52}, {name: '山东', value: 68}, {name: '新疆', value: 30}, {name: '江苏', value: 75}, {name: '浙江', value: 80}, {name: '江西', value: 48}, {name: '湖北', value: 62}, {name: '广西', value: 42}, {name: '甘肃', value: 35}, {name: '山西', value: 50}, {name: '内蒙古', value: 38}, {name: '陕西', value: 55}, {name: '吉林', value: 52}, {name: '福建', value: 70}, {name: '贵州', value: 40}, {name: '广东', value: 88}, {name: '青海', value: 32}, {name: '西藏', value: 25}, {name: '四川', value: 58}, {name: '宁夏', value: 40}, {name: '海南', value: 60}, {name: '台湾', value: 75}, {name: '香港', value: 95}, {name: '澳门', value: 92} ]; // 增长率数据 var growthData = [ {name: '北京', value: 12.5}, {name: '天津', value: 8.3}, {name: '上海', value: 15.2}, {name: '重庆', value: 10.7}, {name: '河北', value: 7.8}, {name: '河南', value: 9.5}, {name: '云南', value: 12.3}, {name: '辽宁', value: 5.6}, {name: '黑龙江', value: 4.2}, {name: '湖南', value: 11.4}, {name: '安徽', value: 10.8}, {name: '山东', value: 9.7}, {name: '新疆', value: 14.5}, {name: '江苏', value: 11.2}, {name: '浙江', value: 13.6}, {name: '江西', value: 9.3}, {name: '湖北', value: 10.1}, {name: '广西', value: 8.9}, {name: '甘肃', value: 7.5}, {name: '山西', value: 6.8}, {name: '内蒙古', value: 8.2}, {name: '陕西', value: 9.9}, {name: '吉林', value: 6.3}, {name: '福建', value: 11.7}, {name: '贵州', value: 13.2}, {name: '广东', value: 14.8}, {name: '青海', value: 9.1}, {name: '西藏', value: 10.5}, {name: '四川', value: 11.9}, {name: '宁夏', value: 8.7}, {name: '海南', value: 12.1}, {name: '台湾', value: 7.9}, {name: '香港', value: 5.4}, {name: '澳门', value: 6.1} ]; // 地图配置 function getMapOption(data, title, visualMapMin, visualMapMax, unit) { return { title: { text: title, left: 'center' }, tooltip: { trigger: 'item', formatter: function(params) { return params.name + ': ' + params.value + unit; } }, visualMap: { min: visualMapMin, max: visualMapMax, left: 'left', top: 'bottom', text: ['高', '低'], calculable: true, inRange: { color: ['#e0f3f8', '#abd9e9', '#74add1', '#4575b4', '#313695'] } }, series: [ { name: title, type: 'map', map: 'china', roam: true, // 启用缩放和平移 label: { show: true }, data: data, emphasis: { label: { show: true }, itemStyle: { areaColor: '#ff7f50' } } } ] }; } // 初始显示销售数据 chinaMapChart.setOption(getMapOption(salesData, '中国各省销售数据(单位:万元)', 0, 600, '万元')); // 按钮点击事件 document.getElementById('salesBtn').addEventListener('click', function() { chinaMapChart.setOption(getMapOption(salesData, '中国各省销售数据(单位:万元)', 0, 600, '万元')); }); document.getElementById('densityBtn').addEventListener('click', function() { chinaMapChart.setOption(getMapOption(densityData, '中国各省网点密度(单位:个/万人)', 0, 100, '个/万人')); }); document.getElementById('growthBtn').addEventListener('click', function() { chinaMapChart.setOption(getMapOption(growthData, '中国各省销售增长率(单位:%)', 0, 20, '%')); }); // 地图点击事件 chinaMapChart.on('click', function(params) { var infoPanel = document.getElementById('infoPanel'); var provinceName = params.name; var value = params.value || 0; // 查找其他数据 var salesValue = 0; var densityValue = 0; var growthValue = 0; for (var i = 0; i < salesData.length; i++) { if (salesData[i].name === provinceName) { salesValue = salesData[i].value; break; } } for (var i = 0; i < densityData.length; i++) { if (densityData[i].name === provinceName) { densityValue = densityData[i].value; break; } } for (var i = 0; i < growthData.length; i++) { if (growthData[i].name === provinceName) { growthValue = growthData[i].value; break; } } infoPanel.innerHTML = ` <h3>${provinceName}省详细数据</h3> <p><strong>销售额:</strong>${salesValue} 万元</p> <p><strong>网点密度:</strong>${densityValue} 个/万人</p> <p><strong>增长率:</strong>${growthValue} %</p> `; }); // 响应式调整 window.addEventListener('resize', function() { chinaMapChart.resize(); }); </script> </body> </html>
6. 性能优化和最佳实践
6.1 大数据量渲染优化
当需要渲染大量数据时,可以采用以下优化策略:
// 初始化图表 var myChart = echarts.init(document.getElementById('main')); // 生成大量数据 var dataCount = 10000; // 一万个数据点 var data = []; var now = new Date(); var oneDay = 24 * 3600 * 1000; var value = Math.random() * 1000; for (var i = 0; i < dataCount; i++) { data.push([ new Date(now.getTime() - (dataCount - i) * oneDay), Math.round((Math.random() - 0.5) * 20 + value) ]); value = data[data.length - 1][1]; } // 大数据量优化配置 var option = { title: { text: '大数据量渲染优化示例' }, tooltip: { trigger: 'axis', formatter: function (params) { params = params[0]; var date = new Date(params.value[0]); return date.getFullYear() + '/' + (date.getMonth() + 1) + '/' + date.getDate() + ' : ' + params.value[1]; }, axisPointer: { animation: false } }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'time', splitLine: { show: false } }, yAxis: { type: 'value', boundaryGap: [0, '100%'], splitLine: { show: false } }, series: [{ name: '模拟数据', type: 'line', showSymbol: false, // 不显示数据点标记 hoverAnimation: false, // 关闭悬停动画 data: data, // 大数据量优化配置 large: true, // 开启大数据量优化 largeThreshold: 100, // 大数据量阈值 progressive: 200, // 渐进式渲染阈值 progressiveThreshold: 1000, // 启用渐进式渲染的阈值 // 线条样式 lineStyle: { width: 1, opacity: 0.5 } }] }; // 应用配置 myChart.setOption(option); // 动态更新数据 setInterval(function () { // 移除第一个数据点 data.shift(); // 添加新的数据点 var now = new Date(); value = Math.round((Math.random() - 0.5) * 20 + value); data.push([ now, value ]); // 更新图表 myChart.setOption({ series: [{ data: data }] }); }, 1000);
6.2 响应式设计
确保图表能够在不同设备上正常显示:
// 初始化图表 var myChart = echarts.init(document.getElementById('main')); // 基础配置 var option = { title: { text: '响应式设计示例' }, tooltip: { trigger: 'axis' }, legend: { data: ['邮件营销', '联盟广告', '视频广告'] }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', boundaryGap: false, data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] }, yAxis: { type: 'value' }, series: [ { name: '邮件营销', type: 'line', stack: '总量', data: [120, 132, 101, 134, 90, 230, 210] }, { name: '联盟广告', type: 'line', stack: '总量', data: [220, 182, 191, 234, 290, 330, 310] }, { name: '视频广告', type: 'line', stack: '总量', data: [150, 232, 201, 154, 190, 330, 410] } ] }; // 应用配置 myChart.setOption(option); // 根据窗口大小调整图表 function resizeChart() { // 获取容器宽度 var width = document.getElementById('main').clientWidth; // 根据宽度调整配置 var newOption = JSON.parse(JSON.stringify(option)); // 深拷贝 if (width < 768) { // 移动端配置 newOption.legend.orient = 'horizontal'; newOption.legend.top = 'bottom'; newOption.grid.bottom = '15%'; // 简化标签 newOption.xAxis.axisLabel.interval = 0; newOption.xAxis.axisLabel.rotate = 30; } else { // 桌面端配置 newOption.legend.orient = 'vertical'; newOption.legend.right = 10; newOption.legend.top = 'middle'; newOption.grid.right = '15%'; } // 应用新配置 myChart.setOption(newOption); // 调整图表大小 myChart.resize(); } // 初始调整 resizeChart(); // 监听窗口大小变化 window.addEventListener('resize', resizeChart);
6.3 数据处理与格式化
合理处理和格式化数据可以提高图表的可读性和用户体验:
// 初始化图表 var myChart = echarts.init(document.getElementById('main')); // 原始数据 var rawData = [ {date: '2023-01-01', category: 'A', value: 120}, {date: '2023-01-01', category: 'B', value: 200}, {date: '2023-01-01', category: 'C', value: 150}, {date: '2023-01-02', category: 'A', value: 132}, {date: '2023-01-02', category: 'B', value: 182}, {date: '2023-01-02', category: 'C', value: 232}, {date: '2023-01-03', category: 'A', value: 101}, {date: '2023-01-03', category: 'B', value: 191}, {date: '2023-01-03', category: 'C', value: 201}, {date: '2023-01-04', category: 'A', value: 134}, {date: '2023-01-04', category: 'B', value: 234}, {date: '2023-01-04', category: 'C', value: 154}, {date: '2023-01-05', category: 'A', value: 90}, {date: '2023-01-05', category: 'B', value: 290}, {date: '2023-01-05', category: 'C', value: 190} ]; // 数据处理函数 function processData(rawData) { // 获取所有日期 var dates = [...new Set(rawData.map(item => item.date))]; // 获取所有类别 var categories = [...new Set(rawData.map(item => item.category))]; // 按类别组织数据 var seriesData = {}; categories.forEach(category => { seriesData[category] = { name: category, type: 'bar', data: [] }; }); // 填充数据 dates.forEach(date => { rawData.forEach(item => { if (item.date === date) { seriesData[item.category].data.push(item.value); } }); }); return { dates: dates, series: Object.values(seriesData) }; } // 处理数据 var processedData = processData(rawData); // 配置项 var option = { title: { text: '数据处理与格式化示例' }, tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }, // 自定义提示框格式 formatter: function(params) { var date = params[0].axisValue; var result = date + '<br/>'; params.forEach(param => { result += param.marker + param.seriesName + ': ' + // 数值格式化 param.value.toLocaleString() + // 添加单位 ' 元<br/>'; }); // 计算总计 var total = params.reduce((sum, param) => sum + param.value, 0); result += '<hr style="margin: 3px 0"/>总计: ' + total.toLocaleString() + ' 元'; return result; } }, legend: { data: processedData.series.map(item => item.name) }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', data: processedData.dates, // 轴标签格式化 axisLabel: { formatter: function(value) { // 格式化日期显示 var date = new Date(value); return (date.getMonth() + 1) + '/' + date.getDate(); } } }, yAxis: { type: 'value', // 轴标签格式化 axisLabel: { formatter: function(value) { if (value >= 10000) { return (value / 10000).toFixed(1) + '万'; } return value; } } }, series: processedData.series, // 数据标签格式化 label: { show: true, position: 'top', formatter: function(params) { return params.value.toLocaleString(); } } }; // 应用配置 myChart.setOption(option);
6.4 最佳实践总结
- 模块化配置:将复杂的配置拆分为多个模块,便于维护和复用。
// 基础配置模块 function getBaseConfig() { return { title: { left: 'center' }, tooltip: { trigger: 'axis' }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true } }; } // 折线图配置模块 function getLineConfig(data) { return { xAxis: { type: 'category', data: data.categories }, yAxis: { type: 'value' }, series: data.series.map(item => ({ name: item.name, type: 'line', data: item.values })) }; } // 合并配置 function createChartConfig(data) { return { ...getBaseConfig(), ...getLineConfig(data), title: { ...getBaseConfig().title, text: data.title } }; } // 使用 var data = { title: '模块化配置示例', categories: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], series: [ {name: '系列1', values: [120, 132, 101, 134, 90, 230, 210]}, {name: '系列2', values: [220, 182, 191, 234, 290, 330, 310]} ] }; var option = createChartConfig(data);
- 数据与视图分离:将数据处理逻辑与图表配置分离,提高代码可维护性。
// 数据处理类 class DataProcessor { constructor(rawData) { this.rawData = rawData; } // 处理数据 process() { // 实现数据处理逻辑 return this.processedData; } // 格式化数据 format(data) { // 实现数据格式化逻辑 return formattedData; } } // 图表配置类 class ChartConfig { constructor(data) { this.data = data; } // 生成配置 generate() { // 实现配置生成逻辑 return option; } } // 使用 var rawData = [...]; // 原始数据 var processor = new DataProcessor(rawData); var processedData = processor.process(); var formattedData = processor.format(processedData); var config = new ChartConfig(formattedData); var option = config.generate();
- 响应式设计:确保图表在不同设备上都能正常显示和交互。
// 响应式图表类 class ResponsiveChart { constructor(domId) { this.domId = domId; this.chart = echarts.init(document.getElementById(domId)); this.currentOption = null; // 监听窗口大小变化 window.addEventListener('resize', this.handleResize.bind(this)); } // 处理窗口大小变化 handleResize() { this.chart.resize(); this.updateOptionForScreenSize(); } // 根据屏幕大小更新配置 updateOptionForScreenSize() { if (!this.currentOption) return; var width = document.getElementById(this.domId).clientWidth; var newOption = this.getOptionForScreenSize(width); this.chart.setOption(newOption); } // 获取适合屏幕大小的配置 getOptionForScreenSize(width) { // 深拷贝当前配置 var option = JSON.parse(JSON.stringify(this.currentOption)); if (width < 768) { // 移动端适配 // 调整图例位置 option.legend.orient = 'horizontal'; option.legend.top = 'bottom'; // 调整网格边距 option.grid.bottom = '15%'; // 调整轴标签 option.xAxis.axisLabel.rotate = 30; option.xAxis.axisLabel.interval = 0; } else { // 桌面端适配 option.legend.orient = 'vertical'; option.legend.right = 10; option.legend.top = 'middle'; option.grid.right = '15%'; } return option; } // 设置配置 setOption(option) { this.currentOption = option; var responsiveOption = this.getOptionForScreenSize( document.getElementById(this.domId).clientWidth ); this.chart.setOption(responsiveOption); } // 销毁图表 dispose() { window.removeEventListener('resize', this.handleResize); this.chart.dispose(); } } // 使用 var chart = new ResponsiveChart('chart-container'); chart.setOption(option);
- 性能优化:对于大数据量或复杂图表,采用适当的优化策略。
// 性能优化配置 function getOptimizedOption(data) { return { // 启用大数据量优化 series: [{ large: true, largeThreshold: 1000, progressive: 200, progressiveThreshold: 3000, // 关闭不必要的动画和效果 animation: false, hoverAnimation: false, // 简化渲染 showSymbol: false, // 其他配置... }], // 其他配置... }; } // 分块渲染大数据 function renderLargeDataInChunks(chart, data, chunkSize = 1000) { var index = 0; function renderChunk() { var chunk = data.slice(index, index + chunkSize); chart.setOption({ series: [{ data: chunk }] }); index += chunkSize; if (index < data.length) { // 使用 requestAnimationFrame 优化渲染 requestAnimationFrame(renderChunk); } } renderChunk(); }
7. 总结
ECharts是一个功能强大、灵活易用的数据可视化库,通过JSON配置可以创建各种类型的图表。本文详细介绍了ECharts的基础概念、JSON配置结构、常见图表的配置方法、高级配置技巧以及实战案例,并提供了性能优化和最佳实践建议。
通过掌握这些知识,你可以充分利用ECharts的功能,创建出美观、交互丰富、性能优秀的数据可视化作品。无论是简单的统计图表,还是复杂的仪表盘和地理信息系统,ECharts都能满足你的需求。
在实际应用中,建议根据具体场景选择合适的图表类型,合理组织数据结构,优化配置参数,并注意性能和响应式设计,以提供最佳的用户体验。