文章目录
整体 目标:
无代码或低代码,可以实现数据的图形化显示。
1. 技术选型
- 图表:Apache ECharts。样例官网:https://echarts.apache.org/examples/zh/index.html
- 后端接口数据:Hasor Dataway。官网:https://www.dataql.net/docs/dataway/overview
2. 实现方案
2.1. 方案介绍
**流程整合方案:**Dataway数据接口 + ECharts绘制Html = 动态图表
-
工作流图:
-
数据流图:
2.2. 方案实现(demo)
我们以一个日历图为例,来看一个动态图表实现的全过程。
-
目标:在日历图上,显示有数据推送的日期,并根据推送的数据量多少,对日期色块以深浅进行区分。
-
效果:
2.2.1. 使用echarts绘制html静态页
根据需求,从Apache ECharts样例官网:https://echarts.apache.org/examples/zh/index.html,筛选符合需求的图表。
2.2.1.1. 选择合适的图表
日历坐标系可以满足。
2.2.1.2. 下载html demo
下载样例,下载到的是一个完整可运行的html demo
<!--
此示例下载自 https://echarts.apache.org/examples/zh/editor.html?c=calendar-simple
-->
<!DOCTYPE html>
<html lang="zh-CN" style="height: 100%">
<head>
<meta charset="utf-8">
</head>
<body style="height: 100%; margin: 0">
<div id="container" style="height: 100%"></div>
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@5.4.1/dist/echarts.min.js"></script>
<script type="text/javascript">
var dom = document.getElementById('container');
var myChart = echarts.init(dom, null, {
renderer: 'canvas',
useDirtyRect: false
});
var app = {};
var option;
function getVirtualData(year) {
const date = +echarts.time.parse(year + '-01-01');
const end = +echarts.time.parse(year + '-12-31');
const dayTime = 3600 * 24 * 1000;
const data = [];
for (let time = date; time <= end; time += dayTime) {
data.push([
echarts.time.format(time, '{yyyy}-{MM}-{dd}', false),
Math.floor(Math.random() * 10000)
]);
}
return data;
}
option = {
visualMap: {
show: false,
min: 0,
max: 10000
},
calendar: {
range: '2017'
},
series: {
type: 'heatmap',
coordinateSystem: 'calendar',
data: getVirtualData('2017')
}
};
if (option && typeof option === 'object') {
myChart.setOption(option);
}
window.addEventListener('resize', myChart.resize);
</script>
</body>
</html>
至此,静态页面有了,我们需要的是一个动态数据接口,选择使用dataway开源项目来实现动态接口的创建。
dataway手册:https://www.dataql.net/docs/dataway/overview
需要的数据格式应该是:
[
['2022-11-16',9],
['2022-11-17',8],
['2022-12-03',66],
['2022-12-05',1014],
['2022-12-06',2253],
['2022-12-07',135]
]
2.2.2. 使用Dataway准备数据接口
2.2.2.1. 部署dataway
只需要在基础的springboot开箱代码基础上加上maven依赖及少量配置,即可run一个dataway服务。
-
dataway依赖表创建
CREATE TABLE `interface_release` ( `pub_id` varchar(64) NOT NULL COMMENT 'Publish ID', `pub_api_id` varchar(64) NOT NULL COMMENT '所属API ID', `pub_method` varchar(12) NOT NULL COMMENT 'HttpMethod:GET、PUT、POST', `pub_path` varchar(512) NOT NULL COMMENT '拦截路径', `pub_status` varchar(4) NOT NULL COMMENT '状态:-1-删除, 0-草稿,1-发布,2-有变更,3-禁用', `pub_comment` varchar(255) NOT NULL COMMENT '注释', `pub_type` varchar(24) NOT NULL COMMENT '脚本类型:SQL、DataQL', `pub_script` mediumtext NOT NULL COMMENT '查询脚本:xxxxxxx', `pub_script_ori` mediumtext NOT NULL COMMENT '原始查询脚本,仅当类型为SQL时不同', `pub_schema` mediumtext NOT NULL COMMENT '接口的请求/响应数据结构', `pub_sample` mediumtext NOT NULL COMMENT '请求/响应/请求头样本数据', `pub_option` mediumtext NOT NULL COMMENT '扩展配置信息', `pub_release_time` varchar(32) NOT NULL COMMENT '发布时间(下线不更新)', PRIMARY KEY (`pub_id`), KEY `idx_interface_release_api` (`pub_api_id`), KEY `idx_interface_release_path` (`pub_path`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='Dataway API 发布历史。'; CREATE TABLE `interface_release` ( `pub_id` varchar(64) NOT NULL COMMENT 'Publish ID', `pub_api_id` varchar(64) NOT NULL COMMENT '所属API ID', `pub_method` varchar(12) NOT NULL COMMENT 'HttpMethod:GET、PUT、POST', `pub_path` varchar(512) NOT NULL COMMENT '拦截路径', `pub_status` varchar(4) NOT NULL COMMENT '状态:-1-删除, 0-草稿,1-发布,2-有变更,3-禁用', `pub_comment` varchar(255) NOT NULL COMMENT '注释', `pub_type` varchar(24) NOT NULL COMMENT '脚本类型:SQL、DataQL', `pub_script` mediumtext NOT NULL COMMENT '查询脚本:xxxxxxx', `pub_script_ori` mediumtext NOT NULL COMMENT '原始查询脚本,仅当类型为SQL时不同', `pub_schema` mediumtext NOT NULL COMMENT '接口的请求/响应数据结构', `pub_sample` mediumtext NOT NULL COMMENT '请求/响应/请求头样本数据', `pub_option` mediumtext NOT NULL COMMENT '扩展配置信息', `pub_release_time` varchar(32) NOT NULL COMMENT '发布时间(下线不更新)', PRIMARY KEY (`pub_id`), KEY `idx_interface_release_api` (`pub_api_id`), KEY `idx_interface_release_path` (`pub_path`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='Dataway API 发布历史。';
-
dataway springboot项目引入相关依赖
<!--hasor dataway--> <dependency> <groupId>net.hasor</groupId> <artifactId>hasor-spring</artifactId> <version>4.2.2</version> </dependency> <dependency> <groupId>net.hasor</groupId> <artifactId>hasor-dataway</artifactId> <version>4.2.2</version> </dependency> <!--作为 Spring Boot 项目有着自己完善的数据库方面工具支持。我们这次采用 druid + mysql + spring-boot-starter-jdbc 的方式。--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.30</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.21</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency>
-
dataway springboot项目配置
# 解决接口返回中文都是乱码 server.servlet.encoding.charset=utf-8 server.servlet.encoding.force=true server.servlet.encoding.enabled=true # 是否启用 Dataway 功能(必选:默认false) HASOR_DATAQL_DATAWAY=true # 是否开启 Dataway 后台管理界面(必选:默认false) HASOR_DATAQL_DATAWAY_ADMIN=true # dataway API工作路径(可选,默认:/api/) HASOR_DATAQL_DATAWAY_API_URL=/api/ # dataway-ui 的工作路径(可选,默认:/interface-ui/) HASOR_DATAQL_DATAWAY_UI_URL=/interface-ui/ # SQL执行器方言设置(可选,建议设置) HASOR_DATAQL_FX_PAGE_DIALECT=mysql # db spring.datasource.url=jdbc:mysql://localhost:3306/cdc?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.type=com.alibaba.druid.pool.DruidDataSource # druid spring.datasource.druid.initial-size=3 spring.datasource.druid.min-idle=3 spring.datasource.druid.max-active=10 spring.datasource.druid.max-wait=60000 spring.datasource.druid.stat-view-servlet.login-username=admin spring.datasource.druid.stat-view-servlet.login-password=admin spring.datasource.druid.filter.stat.log-slow-sql=true spring.datasource.druid.filter.stat.slow-sql-millis=1
-
dataway springboot把数据源设置到 Hasor 容器中
package com.example.simple; import net.hasor.core.ApiBinder; import net.hasor.core.DimModule; import net.hasor.db.JdbcModule; import net.hasor.db.Level; import net.hasor.spring.SpringModule; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sql.DataSource; @DimModule @Component public class ExampleModule implements SpringModule { @Autowired private DataSource dataSource = null; @Override public void loadModule(ApiBinder apiBinder) throws Throwable { // .DataSource form Spring boot into Hasor apiBinder.installModule(new JdbcModule(Level.Full, this.dataSource)); } }
-
在 dataway springboot 中启用 Hasor
package com.example.simple; import net.hasor.spring.boot.EnableHasor; import net.hasor.spring.boot.EnableHasorWeb; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @EnableHasor() @EnableHasorWeb() @SpringBootApplication public class SimpleApplication { public static void main(String[] args) { System.setProperty("file.encoding","UTF-8"); SpringApplication.run(SimpleApplication.class, args); } }
-
至此,即可启动应用
2.2.2.2. 创建数据接口
书接上回,我们需要的数据格式应该是:
[
['2022-11-16',9],
['2022-11-17',8],
['2022-12-03',66],
['2022-12-05',1014],
['2022-12-06',2253],
['2022-12-07',135]
]
-
sql编写
对此,我们如果通过简单的sql,按天汇总数据量,那sql应该这么写:
select DATE_FORMAT(add_time,'%Y-%m-%d') as time,sum(received_data_total) as sum from cdc.`cdc_receive_round_log` where add_time > '2022-10-01' and data_platform='zgl' group by DATE_FORMAT(add_time,'%Y-%m-%d') ;
在mysql中执行结果如下:
-
基于sql,在dataway中创建接口
-
验证接口是否可访问
在浏览器访问: http://localhost:8080/api/received_count_per_day2
可以拿到结果:
{"success":true,"message":"OK","code":0,"lifeCycleTime":128,"executionTime":32,"value":{"zgl":[{"time":"2022-11-16","sum":9},{"time":"2022-11-17","sum":8},{"time":"2022-12-03","sum":66},{"time":"2022-12-05","sum":1014},{"time":"2022-12-06","sum":2253},{"time":"2022-12-07","sum":135}],"hsh":[{"time":"2022-10-08","sum":582},{"time":"2022-10-11","sum":165},{"time":"2022-10-14","sum":400},{"time":"2022-10-26","sum":200},{"time":"2022-11-03","sum":427},{"time":"2022-11-11","sum":200},{"time":"2022-11-18","sum":1500},{"time":"2022-11-30","sum":700},{"time":"2022-12-01","sum":882746},{"time":"2022-12-02","sum":260753}]}}
上述如图:
but:
dataway接口返回的数据value.zgl内的报文,是一个list中嵌套dict。但是我们echarts需要的数据报文,是list里嵌套list。
我们要怎么将dataway的返参结果调整为echarts所需结构?
方案有二:
其一:通过dataway的dataql语法进行调整
其二:在echarts html中,拿到dataway接口数据报文后,通过js进行调整。
经过思考,毋庸置疑应选方案二,以减少学习成本。(下一节描述此案例如何调整。)
2.2.3. 调试html demo + dataway接口
直接看代码注释:
<!DOCTYPE html>
<html lang="zh-CN" style="height: 100%">
<head>
<meta charset="utf-8">
</head>
<body style="height: 100%; margin: 0">
<!-- 图表容器 -->
<div id="container" style="height: 100%"></div>
<!-- echarts js -->
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@5.4.1/dist/echarts.min.js"></script>
<!-- 由于需要异步加载数据,此处需要引入jquery -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script>
<script type="text/javascript">
// 获取dom,以便使用echarts进行渲染
var dom = document.getElementById('container');
var myChart = echarts.init(dom, null, {
renderer: 'canvas',
useDirtyRect: false
});
// 异步加载数据,http://localhost:8080/api/received_count_per_day2 就是我们使用dataway动态创建的接口
$.get('http://localhost:8080/api/received_count_per_day2').done(function (data) {
// zgl日历数据处理,将list嵌套dict,改为list嵌套list结构。
var zgl_origion = data.value.zgl
var zgl_echarts_data = []
for (var i = 0; i < zgl_origion.length; i++) {
var item = []
item.push(zgl_origion[i]['time'])
item.push(zgl_origion[i]['sum'])
zgl_echarts_data.push(item)
}
//hsh日历数据处理,将list嵌套dict,改为list嵌套list结构。
var hsh_origion = data.value.hsh
var hsh_echarts_data = []
for (var i = 0; i < hsh_origion.length; i++) {
var item = []
item.push(hsh_origion[i]['time'])
item.push(hsh_origion[i]['sum'])
hsh_echarts_data.push(item)
}
// 渲染图表
myChart.setOption({
title: {
text: '' // 图表的头部文本
},
tooltip: {
// 在划过日期块的时候,显示日期和数据。不写的话,默认只会显示数值,没有日期。
formatter: function (p) {
var format = echarts.format.formatTime('yyyy-MM-dd', p.data[0]);
return format + '数量: ' + p.data[1];
}
},
visualMap: {
show: false,
min: 0,
max: 5000,
textStyle: { color: '#000' },
precision: 0,
inRange: {
// 在线调色板 https://www.sojson.com/web/panel.html
// 色块深浅,将从下面的列表中根据数值大小自动选择
color: ['#D4EFDF', '#82E0AA', '#58D68D', '#27AE60', '#229954', '#196F3D'],
colorAlpha: 0.9, // 图元的颜色的透明度
}
},
calendar: [{
range: '2022',
dayLabel: {
show: true
}
},
{
top: 260,
range: '2022',
// cellSize: ['auto', 20]
}
],
series: [{
type: 'heatmap',
coordinateSystem: 'calendar',
data: zgl_echarts_data // zgl数据渲染
},
{
type: 'heatmap',
coordinateSystem: 'calendar',
calendarIndex: 1,
data: hsh_echarts_data // hsh数据渲染
}]
})
});
window.addEventListener('resize', myChart.resize);
</script>
</body>
</html>
然后,直接用浏览器访问此页面,或在vscode中的live server进行访问:
可以看到,dataway是已经允许了跨域访问的,无需再做调整。
2.2.4. 发布html到nginx
2.2.4.1. 将完整url,放入到系统菜单上(配置菜单),使其可访问
这里要做的,是echarts html页面路径跟我们将要嵌入的系统域名保持一致,echarts html页面路径可以放在一个特别标记的位置上,比如 https://hello.com/my_echarts/calender_summary.html, 用诸如/my_echarts
来让nginx区分是访问原系统页面或我们编写的echarts html页面即可。
同时,dataway接口已经支持跨域访问,不用调整不提。
3. 后期工作
-
是否支持类似时间区间筛选之类的表头?
理论上是支持的,因为echarts的图表,默认是放在一个html元素如div中,只需要进行简单的html编写
-
图表的空间布局摆放要求?
同上,记上css定位等html技术,即可实现静态页的调整。