Echarts入门(SpringBoot + Vue)

一、Echarts简介

代码已上传至码云:echarts_boot: echarts使用demo

ECharts是一个使用 JavaScript 实现的"数据可视化"库, 它可以流畅的运行在 PC 和移动设备上

什么是数据可视化?

也就是可以将数据通过图表的形式展示出来,

Echarts官网:Apache ECharts

本篇文章以此柱状图为例:

二、业务背景

数据库中分别有学生表、课程表、成绩表三张表

使用echarts对 某科目全班平均分近三年变化、某个人总分数变化、某人某年各科成绩作统计和可视化操作

表结构如下:

三、数据准备

/*
 Navicat Premium Data Transfer

 Source Server         : CloudShell
 Source Server Type    : MySQL
 Source Server Version : 50736 (5.7.36)
 Source Host           : 124.70.69.186:13306
 Source Schema         : echarts_test

 Target Server Type    : MySQL
 Target Server Version : 50736 (5.7.36)
 File Encoding         : 65001

 Date: 01/07/2023 15:44:13
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for course
-- ----------------------------
DROP TABLE IF EXISTS `course`;
CREATE TABLE `course` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `cou_num` int(11) DEFAULT NULL COMMENT '课程号',
  `cou_name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '课程名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='课程表';

-- ----------------------------
-- Records of course
-- ----------------------------
BEGIN;
INSERT INTO `course` (`id`, `cou_num`, `cou_name`) VALUES (1, 1, '语文');
INSERT INTO `course` (`id`, `cou_num`, `cou_name`) VALUES (2, 2, '数学');
INSERT INTO `course` (`id`, `cou_num`, `cou_name`) VALUES (3, 3, '英语');
COMMIT;

-- ----------------------------
-- Table structure for score
-- ----------------------------
DROP TABLE IF EXISTS `score`;
CREATE TABLE `score` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `cou_num` int(11) NOT NULL COMMENT '课程号',
  `stu_num` int(11) NOT NULL COMMENT '学号',
  `score` int(11) DEFAULT NULL COMMENT '成绩',
  `time` timestamp NULL DEFAULT NULL COMMENT '时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='成绩表';

-- ----------------------------
-- Records of score
-- ----------------------------
BEGIN;
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (1, 1, 1, 90, '2023-06-30 09:57:19');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (2, 2, 1, 88, '2023-06-30 09:57:31');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (3, 3, 1, 95, '2023-06-30 09:57:42');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (4, 1, 2, 70, '2023-06-30 09:57:54');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (5, 2, 2, 99, '2023-06-30 09:58:04');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (6, 3, 2, 60, '2023-06-30 09:58:12');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (7, 1, 3, 56, '2023-06-30 09:58:20');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (8, 2, 3, 32, '2023-06-30 09:58:31');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (9, 3, 3, 43, '2022-06-30 09:58:48');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (10, 1, 1, 88, '2022-06-30 09:57:19');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (11, 2, 1, 86, '2022-06-30 09:57:31');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (12, 3, 1, 93, '2022-06-30 09:57:42');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (13, 1, 2, 70, '2022-06-30 09:57:54');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (14, 2, 2, 99, '2022-06-30 09:58:04');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (15, 3, 2, 60, '2022-06-30 09:58:12');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (16, 1, 3, 56, '2022-06-30 09:58:20');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (17, 2, 3, 32, '2022-06-30 09:58:31');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (18, 3, 3, 43, '2022-06-30 09:58:48');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (19, 1, 1, 86, '2021-06-30 09:57:19');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (20, 2, 1, 84, '2021-06-30 09:57:31');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (21, 3, 1, 91, '2021-06-30 09:57:42');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (22, 1, 2, 70, '2021-06-30 09:57:54');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (23, 2, 2, 99, '2021-06-30 09:58:04');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (24, 3, 2, 60, '2021-06-30 09:58:12');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (25, 1, 3, 56, '2021-06-30 09:58:20');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (26, 2, 3, 32, '2021-06-30 09:58:31');
INSERT INTO `score` (`id`, `cou_num`, `stu_num`, `score`, `time`) VALUES (27, 3, 3, 43, '2021-06-30 09:58:48');
COMMIT;

-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `stu_num` int(11) NOT NULL COMMENT '学号',
  `stu_name` varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT '姓名',
  `stu_sex` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '性别',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='学生表';

-- ----------------------------
-- Records of student
-- ----------------------------
BEGIN;
INSERT INTO `student` (`id`, `stu_num`, `stu_name`, `stu_sex`) VALUES (1, 1, '张三', '男');
INSERT INTO `student` (`id`, `stu_num`, `stu_name`, `stu_sex`) VALUES (2, 2, '李四', '女');
INSERT INTO `student` (`id`, `stu_num`, `stu_name`, `stu_sex`) VALUES (3, 3, '王五', '男');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

四、构建后端工程 

1.添加依赖

mysql、mybaits-plus和lombok

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- mybaits-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>

 2.编辑配置文件

server:
  port: 18889
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/echarts_test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&AllowPublicKeyRetrieval=True&useSSL=false
    username: root
    password: root


mybatis-plus:
  mapper-locations: mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true
  type-aliases-package: com.crazyk.echartsboot.bo

 3.根据数据库表结构构件项目包和类

以学生实体为例 

实体类

package com.crazyk.echartsboot.bo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 * @program: EchartsBoot
 * @description: 学生实体
 * @author: CrazyK
 * @create: 2023-06-30 10:02
 **/
@Data
@TableName("student")
public class Student {

    @TableId(type = IdType.AUTO)
    private Integer id;

    /**
     * 学号
     */
    private Integer stuNum;

    /**
     * 姓名
     */
    private String stuName;

    /**
     * 性别
     */
    private String StuSex;
}

 mapper接口

package com.crazyk.echartsboot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.crazyk.echartsboot.bo.Student;
import org.apache.ibatis.annotations.Mapper;

/**
 * @program: EchartsBoot
 * @description: 学生接口
 * @author: CrazyK
 * @create: 2023-06-30 10:08
 **/
@Mapper
public interface StudentMapper extends BaseMapper<Student> {
}

 service实现类

package com.crazyk.echartsboot.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.crazyk.echartsboot.bo.Student;
import com.crazyk.echartsboot.mapper.StudentMapper;
import org.springframework.stereotype.Service;

/**
 * @program: EchartsBoot
 * @description: 学生服务
 * @author: CrazyK
 * @create: 2023-06-30 10:10
 **/
@Service
public class StudentService extends ServiceImpl<StudentMapper, Student> {
}

 controller控制类

package com.crazyk.echartsboot.controller;

import com.crazyk.echartsboot.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @program: EchartsBoot
 * @description: 学生控制层
 * @author: CrazyK
 * @create: 2023-06-30 10:12
 **/
@RestController
@RequestMapping("/student")
public class StudentController {

    @Autowired
    private StudentService studentService;
    
}

 mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.crazyk.echartsboot.mapper.StudentMapper">

</mapper>

 4.编写业务代码

博主太懒了,不想写sql,所以用的stream

package com.crazyk.echartsboot.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.crazyk.echartsboot.bo.Course;
import com.crazyk.echartsboot.bo.Score;
import com.crazyk.echartsboot.bo.Student;
import com.crazyk.echartsboot.service.CourseService;
import com.crazyk.echartsboot.service.ScoreService;
import com.crazyk.echartsboot.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @program: EchartsBoot
 * @description: 成绩控制层
 * @author: CrazyK
 * @create: 2023-06-30 10:14
 **/
@RestController
@RequestMapping("/score")
public class ScoreController {

    @Autowired
    private ScoreService scoreService;
    @Autowired
    private CourseService courseService;
    @Autowired
    private StudentService studentService;

    /**
     * 某科目全班平均分 近三年变化
     */
    @RequestMapping("avg")
    public Map<String, Integer> avg(String couName){
        Integer couNum = courseService.getOne(new QueryWrapper<Course>().eq("cou_name", couName))
                .getId();
        Map<String, List<Score>> map = scoreService.list(new QueryWrapper<Score>().eq("cou_num", couNum))
                .stream()
                .collect(Collectors.groupingBy(s -> s.getTime().toString().substring(24)));
        Map<String,Integer> res = new HashMap<>();
        for (String key : map.keySet()){
            Double average = map.get(key).stream()
                    .map(s -> s.getScore())
                    .mapToInt(Integer::valueOf)
                    .average().orElse(0D);
            Integer score = Integer.valueOf(String.valueOf(average).substring(0,2));
            res.put(key,score);
        }
        System.out.println(res);
        return res;
    }

    /**
     *  某个人总分数变化
     */
    @RequestMapping("sum")
    public Map<String,Integer> sum(String stuName){
        Integer stuNum = studentService.getOne(new QueryWrapper<Student>().eq("stu_name",stuName)).getStuNum();
        Map<String, List<Score>> map = scoreService.list(new QueryWrapper<Score>().eq("stu_num", stuNum))
                .stream()
                .collect(Collectors.groupingBy(s -> s.getTime().toString().substring(24)));
        Map<String,Integer> res = new HashMap<>();
        for (String key : map.keySet()){
            Integer sum = map.get(key).stream()
                    .map(s -> s.getScore())
                    .mapToInt(Integer::valueOf)
                    .sum();
            res.put(key,sum);
        }
        System.out.println(res);
        return res;
    }

    /**
     *  某人某年各科成绩
     */
    @RequestMapping("own")
    public Map<String,Integer> own(String stuName,String year){
        Integer stuNum = studentService.getOne(new QueryWrapper<Student>().eq("stu_name",stuName)).getStuNum();
        Map<Integer, List<Score>> map = scoreService.list(new QueryWrapper<Score>().eq("stu_num", stuNum))
                .stream()
                .filter(s -> s.getTime().toString().substring(24).equals(year))
                .collect(Collectors.groupingBy(s -> s.getCouNum()));
        Map<String,Integer> res = new HashMap<>();
        for (Integer key : map.keySet()){
            String couName = courseService.getOne(new QueryWrapper<Course>().eq("cou_num",map.get(key).get(0).getCouNum())).getCouName();
            Integer score = map.get(key).get(0).getScore();
            res.put(couName,score);
        }
        System.out.println(res);
        return res;
    }
}

5.接口测试

测试一下刚才写的三个接口

测试没问题就可以开始写前端了

五、构建前端工程

1.创建Vue工程

我创建的是vue2

vue create 项目名

部署成功后安装需要的依赖

npm install vue-router --save #vueRouter
npm i element-ui -S #element-ui
npm install echarts -S #echarts

npm install axios #axios

 2.全局引入

在main.js全局引入,这是vue项目的入口

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import * as echarts from "echarts";
import {postRequest} from "@/utils/api";
import {getRequest} from "@/utils/api";
import {putRequest} from "@/utils/api";
import {deleteRequest} from "@/utils/api";



Vue.config.productionTip = false
Vue.use(ElementUI,{size:'small'});
Vue.prototype.$echarts = echarts;



new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

3.配置代理 

在vue.config.js文件中添加代理,使请求转发到后端端口

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true
})

let proxyObj = {}

proxyObj['/'] = {
  //websocket
  ws: false,
  //目标地址
  target: 'http://localhost:18889',
  changeOrigin: true,
  pathRewrite: {
    '^/': '/'
  }
};

module.exports = defineConfig({
  transpileDependencies: true,
  devServer:{
    host: 'localhost',
    port: 8080,
    proxy: proxyObj
  }
})

4.编写请求工具类 

api.js用于封装请求

import axios from "axios";

let base = '';

//json格式post请求
export const postRequest = (url,params) => {
    return axios({
        method: 'post',
        url: base + url,
        data:params
    })
}

//put请求
export const putRequest = (url,params) => {
    return axios({
        method: 'put',
        url:base + url,
        data:params
    })
}

//get请求
export const getRequest = (url,params) => {
    return axios({
        method: 'get',
        url:base + url,
        data:params
    })
}

//delete请求
export const deleteRequest = (url,params) => {
    return axios({
        method: 'delete',
        url:base + url,
        data:params
    })
}

 然后在main.js中引入请求封装

//插件形式使用请求
Vue.prototype.postRequest = postRequest;
Vue.prototype.getRequest = getRequest;
Vue.prototype.putRequest = putRequest;
Vue.prototype.deleteRequest = deleteRequest;

 5.编写业务组件

<template>
  <div>
    <div>
      <table>
        <tr>
          <td><el-tag>请输入年份</el-tag></td>
          <td><el-input v-model="year" size="small" style="margin-left: 8px"></el-input></td>
          <td><div>&emsp;</div></td>
          <td><el-tag>请输入学生姓名</el-tag></td>
          <td><el-input v-model="stuName" size="small" style="margin-left: 8px"></el-input></td>
          <td><div>&emsp;</div></td>
          <span slot="footer" class="dialog-footer">
            <el-button type="primary" @click="own" size="mini">查询某人某年各科成绩</el-button>
          </span>
        </tr>
        <tr>
          <td><el-tag>请输入学生姓名</el-tag></td>
          <td><el-input v-model="stuName" size="small" style="margin-left: 8px"></el-input></td>
          <td><div>&emsp;</div></td>
          <span slot="footer" class="dialog-footer">
            <el-button type="primary" @click="sum" size="mini">查询某个人总分数变化</el-button>
          </span>
        </tr>
        <tr>
          <td><el-tag>请输入科目名称</el-tag></td>
          <td><el-input v-model="couName" size="small" style="margin-left: 8px"></el-input></td>
          <td><div>&emsp;</div></td>
          <span slot="footer" class="dialog-footer">
            <el-button type="primary" @click="avg" size="mini">查询某科目班级平均分</el-button>
          </span>
        </tr>
      </table>

    </div>
    <div style="height: 500px">
      <h2>this.titleEcharts</h2>
      <div style="height: 100%;width: 100%" ref="histChart"></div>
    </div>
  </div>

</template>

<script >
import * as echarts from 'echarts'
export default {
  data() {
    return {
      chart:null,
      option:{
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          }
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        xAxis: [
          {
            type: 'category',
            data: [],
            axisTick: {
              alignWithLabel: true
            }
          }
        ],
        yAxis: [
          {
            type: 'value'
          }
        ],
        series: [
          {
            name: 'Direct',
            type: 'bar',
            barWidth: '60%',
            data: []
          }
        ]
      },
      year:"",
      stuName:"",
      couName:"",
      titleEcharts:"输入表单信息点击按钮查询"
    }
  },
  watch: {
    seriveData: {
      deep: true,
      handler() {
        this.updateChartView()
      }
    }
  },
  mounted() {
    this.chart = echarts.init(this.$refs.histChart);
    this.updateChartView();
    window.addEventListener('resize',this.handleWindowResize);
  },
  updated() {
    this.updateChartView();
  },
  beforeDestroy() {
    window.removeEventListener('resize',this.handleWindowResize);
  },
  methods: {
    /**
     * 某人某年各科成绩
     */
    own(){
      this.option.xAxis[0].data = [];
      this.option.series[0].data = [];
      let url = '/score/own?stuName=' + this.stuName + '&year=' + this.year;
      this.getRequest(url).then(resp => {
        console.log(resp.data)
        const map = new Map(Object.entries(resp.data));
        for (let [key,value] of map){
          this.option.xAxis[0].data.push(key);
          this.option.series[0].data.push(value);
        }
        this.titleEcharts = this.stuName + "同学" + this.year + "年各科成绩"
        this.stuName = "";
        this.year = "";
        this.updateChartView();
      });
    },
    /**
     * 某个人总分数变化
     */
    sum(){
      this.option.xAxis[0].data = [];
      this.option.series[0].data = [];
      let url = '/score/sum?stuName=' + this.stuName;
      this.getRequest(url).then(resp => {
        console.log(resp.data)
        const map = new Map(Object.entries(resp.data));
        for (let [key,value] of map){
          this.option.xAxis[0].data.push(key);
          this.option.series[0].data.push(value);
        }
        this.titleEcharts = this.stuName + "同学各年总分数变化"
        this.stuName = "";
        this.year = "";
        this.updateChartView();
      });
    },
    /**
     * 某科目班级平均分
     */
    avg(){
      this.option.xAxis[0].data = [];
      this.option.series[0].data = [];
      let url = '/score/avg?couName=' + this.couName;
      this.getRequest(url).then(resp => {
        console.log(resp.data)
        const map = new Map(Object.entries(resp.data));
        for (let [key,value] of map){
          this.option.xAxis[0].data.push(key);
          this.option.series[0].data.push(value);
        }
        this.titleEcharts = this.couName + "科目班级平均分"
        this.couName = "";
        this.updateChartView();
      });
    },
    /**
     * 更新echarts视图
     */
    updateChartView() {
      if (!this.chart) return

      this.chart.setOption(this.option, true)
    },
    /**
     * 窗口缩放时动态调整echarts大小
     */
    handleWindowResize() {
      if (!this.chart) return;
      this.chart.resize();
    }

  }
}
</script>

<style scoped>
h2{
  text-align: center;
  padding: 30px;
  font-size: 18px;
}

</style>

 六、页面展示

1.查询某人某年各科成绩

2.查询某个人总分数变化

3.查询某科目班级平均分

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值