连续时间折线图的前后端实现

技术栈

  • vue3
  • VChart
  • egg.js
  • MySQL

需求

根据已有任务数据,获取连续天的任务完成的数量,并且通过接口返回后做成图表。预期数据如下:

[
  {
    "x": "2024-01-01",
    "y": 0
  },
  {
    "x": "2024-01-02",
    "y": 26
  },
  {
    "x": "2024-01-03",
    "y": 43
  },
  {
    "x": "2024-01-04",
    "y": 39
  },
  {
    "x": "2024-01-05",
    "y": 22
  },
  {
    "x": "2024-01-06",
    "y": 0
  },
  // ...
]

非连续数据

最简单的查询方式当然就是 GROUP BY 了,但是呢有些日期里面是没有任何数据的。

SELECT
    DATE (FinishTime),
    COUNT(*) AS count
FROM
    tasks
WHERE
    FinishTime >= '2024-01-01'
    AND FinishTime <= '2024-02-01'
GROUP BY
    DATE (FinishTime)

所以需要手动补全这些日期,将其数据设置为 0。网上查了一些方案,有不少骚操作,不过我还是更喜欢和日期表联表查询的方式。感觉这种更合理些。

插入日期表

在建表后,我是通过 node 遍历的方式插入的数据,感觉很 low 的方式……不过暂时能用就行。后面再学习更优雅的写法。

const Service = require('egg').Service
const dayjs = require('dayjs')

class DateService extends Service {
  async createDates() {
    const startDay = dayjs().subtract(600, 'day')
    for (let i = 0; i < 1000; i++) {
      const currentDateStr = startDay.add(i, 'day').format('YYYY-MM-DD')
      await this.app.mysql.insert('dates', {
        date: currentDateStr,
      })
    }
    return '插入成功'
  }
}

module.exports = DateService

运行起来很慢,慢慢悠悠的用 dayjs 插入最近的 1000 条日期数据。

联表查询任务量 SQL 写法

在有了两个表后,就可以使用 LEFT JOIN 来进行联表查询了。由于要补全连续日期,所以先查日期表,然后再联表查询了任务表。

    SELECT
    DATE(dt.dt_date) as x,
    IFNULL (tk.tk_count, 0) AS y
FROM
    (
        SELECT
            DATE (date) AS dt_date
        FROM
            dates
        WHERE
            date >= '2024-01-01'
            AND date <= '2024-02-01
    ) dt
    LEFT JOIN (
        SELECT
            DATE (FinishTime) as tk_date,
            COUNT(*) AS tk_count
        FROM
            tasks
        WHERE
            FinishTime >= '2024-01-01
            AND FinishTime <= '2024-02-01'
        GROUP BY
            DATE (FinishTime)
    ) tk ON dt.dt_date = tk.tk_date

用 AI GPT 优化下:

SELECT
    DATE(dt.dt_date) AS x,
    COALESCE(tk.tk_count, 0) AS y
FROM (
    SELECT
        DATE(date) AS dt_date
    FROM
        dates
    WHERE
        date BETWEEN '2024-01-01' AND '2024-02-01'
) dt
LEFT JOIN (
    SELECT
        DATE(FinishTime) AS tk_date,
        COUNT(*) AS tk_count
    FROM
        tasks
    WHERE
        FinishTime BETWEEN '2024-01-01' AND '2024-02-01'
    GROUP BY
        DATE(FinishTime)
) tk ON dt.dt_date = tk.tk_date;

如此就感觉优雅多了。

图形渲染

有始有终,最后把任务量以折线图的方式呈现出来~

node 端进行接口实现

 async getTaskCount() {
    const { start_date, end_date } = this.ctx.query

    // 这里做了简单的正则匹配
    const reg = /^\d{4}-\d{2}-\d{2}$/
    if (!reg.test(start_date) || !reg.test(end_date)) {
      return []
    }

    const sql = `
    SELECT
    DATE(dt.dt_date) as x,
    IFNULL (tk.tk_count, 0) AS y
FROM
    (
        SELECT
            DATE (date) AS dt_date
        FROM
            dates
        WHERE
            date >= ?
            AND date <= ?
    ) dt
    LEFT JOIN (
        SELECT
            DATE (FinishTime) as tk_date,
            COUNT(*) AS tk_count
        FROM
            tasks
        WHERE
            FinishTime >= ?
            AND FinishTime <= ?
        GROUP BY
            DATE (FinishTime)
    ) tk ON dt.dt_date = tk.tk_date
    `

    const result = await this.app.mysql.query(sql, [
      start_date,
      end_date,
      start_date,
      end_date,
    ])

    return result
  }

前端部分通过 VChart 快速渲染

<template>
  <!-- 为 vchart 准备一个具备大小(宽高)的 DOM,当然你也可以在 spec 配置中指定 -->
  <div id="chart" style="width: 600px; height: 400px"></div>
</template>

<script setup>
import axios from 'axios'
import VChart from '@visactor/vchart'

import { onMounted, ref } from 'vue'

onMounted(() => {
  getChartData()
})

const chartData = ref([])

function getChartData() {
  return axios
    .get('/api/yang/chart', {
      params: {
        start_date: '2024-01-01',
        end_date: '2024-02-01',
      },
    })
    .then(({ data }) => {
      chartData.value = data
      renderChart()
    })
}

function renderChart() {
  const spec = {
    type: 'line',
    data: {
      values: chartData.value,
    },
    xField: 'x',
    yField: 'y',
  }

  const vchart = new VChart(spec, { dom: 'chart' })

  // 创建 vchart 实例
  // 绘制
  vchart.renderSync()
}
</script>

<style lang="scss" scoped></style>

效果图如下,非常完美~

整理一些学到的后端知识

  • 查询的目标不一定非得是固定的表,也可以用 () 括号来查询目标数据集。无论是 FROM 还是 LEFT JOIN 都是如此。
  • IFNULL() 函数可以在数据为空的时候返回一个默认值。
  • COALESCE() 函数也是用来处理缺失值的,感觉和 IFNULL 差不多。
  • 查询日期范围的时候,不需要连续用大于等于小于加上 AND,可以直接用 BETWEEN...AND... 来实现。
  • DATE() 函数可以从日期、时间数据中提取日期部分,如 YYYY-MM-DD。
  • MySQL 服务器也分好多版本,网上资料来看 8.0+ 是最新的,而稳定版本一般是 5.7。另外如果想切换云服务器的 MySQL 版本需要备份删库……
  • 在 egg.js 中需要在 mysql 配置中打开 dateStrings: true 来确保时区为当前时区。否则存取日期的时候会差八个小时(东八区)。

最后

以上就是我在学习图表后端接口实现的过程中的一些心得,感觉数据方向上 SQL 的各种应用还是很广的,可以通过各种不同维度、指标、筛选条件,产出各类不同数据。

可以预见后面可以折腾的东西还有很多~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容