連續(xù)時間折線圖的前后端實現(xiàn)

技術棧

  • vue3
  • VChart
  • egg.js
  • MySQL

需求

根據(jù)已有任務數(shù)據(jù)到腥,獲取連續(xù)天的任務完成的數(shù)量个唧,并且通過接口返回后做成圖表庇忌。預期數(shù)據(jù)如下:

[
  {
    "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
  },
  // ...
]

非連續(xù)數(shù)據(jù)

最簡單的查詢方式當然就是 GROUP BY 了睛挚,但是呢有些日期里面是沒有任何數(shù)據(jù)的。

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

所以需要手動補全這些日期溃蔫,將其數(shù)據(jù)設置為 0健提。網(wǎng)上查了一些方案,有不少騷操作伟叛,不過我還是更喜歡和日期表聯(lián)表查詢的方式矩桂。感覺這種更合理些。

插入日期表

在建表后痪伦,我是通過 node 遍歷的方式插入的數(shù)據(jù),感覺很 low 的方式……不過暫時能用就行雹锣。后面再學習更優(yōu)雅的寫法网沾。

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 條日期數(shù)據(jù)蕊爵。

聯(lián)表查詢?nèi)蝿樟?SQL 寫法

在有了兩個表后辉哥,就可以使用 LEFT JOIN 來進行聯(lián)表查詢了。由于要補全連續(xù)日期攒射,所以先查日期表醋旦,然后再聯(lián)表查詢了任務表。

    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 優(yōu)化下:

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;

如此就感覺優(yōu)雅多了会放。

圖形渲染

有始有終饲齐,最后把任務量以折線圖的方式呈現(xiàn)出來~

node 端進行接口實現(xiàn)

 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' })

  // 創(chuàng)建 vchart 實例
  // 繪制
  vchart.renderSync()
}
</script>

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

效果圖如下捂人,非常完美~

整理一些學到的后端知識

  • 查詢的目標不一定非得是固定的表,也可以用 () 括號來查詢目標數(shù)據(jù)集矢沿。無論是 FROM 還是 LEFT JOIN 都是如此滥搭。
  • IFNULL() 函數(shù)可以在數(shù)據(jù)為空的時候返回一個默認值。
  • COALESCE() 函數(shù)也是用來處理缺失值的捣鲸,感覺和 IFNULL 差不多瑟匆。
  • 查詢?nèi)掌诜秶臅r候,不需要連續(xù)用大于等于小于加上 AND栽惶,可以直接用 BETWEEN...AND... 來實現(xiàn)愁溜。
  • DATE() 函數(shù)可以從日期疾嗅、時間數(shù)據(jù)中提取日期部分,如 YYYY-MM-DD祝谚。
  • MySQL 服務器也分好多版本宪迟,網(wǎng)上資料來看 8.0+ 是最新的,而穩(wěn)定版本一般是 5.7交惯。另外如果想切換云服務器的 MySQL 版本需要備份刪庫……
  • 在 egg.js 中需要在 mysql 配置中打開 dateStrings: true 來確保時區(qū)為當前時區(qū)次泽。否則存取日期的時候會差八個小時(東八區(qū))。

最后

以上就是我在學習圖表后端接口實現(xiàn)的過程中的一些心得席爽,感覺數(shù)據(jù)方向上 SQL 的各種應用還是很廣的意荤,可以通過各種不同維度、指標只锻、篩選條件玖像,產(chǎn)出各類不同數(shù)據(jù)。

可以預見后面可以折騰的東西還有很多~

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末齐饮,一起剝皮案震驚了整個濱河市捐寥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祖驱,老刑警劉巖握恳,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捺僻,居然都是意外死亡乡洼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門匕坯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來束昵,“玉大人,你說我怎么就攤上這事葛峻∏鲁” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵泞歉,是天一觀的道長逼侦。 經(jīng)常有香客問我,道長腰耙,這世上最難降的妖魔是什么榛丢? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮挺庞,結(jié)果婚禮上晰赞,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好掖鱼,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布然走。 她就那樣靜靜地躺著,像睡著了一般戏挡。 火紅的嫁衣襯著肌膚如雪芍瑞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天褐墅,我揣著相機與錄音拆檬,去河邊找鬼。 笑死妥凳,一個胖子當著我的面吹牛竟贯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逝钥,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼屑那,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了艘款?” 一聲冷哼從身側(cè)響起持际,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哗咆,沒想到半個月后选酗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡岳枷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了呜叫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片空繁。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖朱庆,靈堂內(nèi)的尸體忽然破棺而出盛泡,到底是詐尸還是另有隱情,我是刑警寧澤娱颊,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布傲诵,位于F島的核電站,受9級特大地震影響箱硕,放射性物質(zhì)發(fā)生泄漏拴竹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一剧罩、第九天 我趴在偏房一處隱蔽的房頂上張望栓拜。 院中可真熱鬧,春花似錦、人聲如沸幕与。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啦鸣。三九已至潮饱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诫给,已是汗流浹背香拉。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蝙搔,地道東北人缕溉。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像吃型,于是被迫代替她去往敵國和親证鸥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容