繁瑣實用系列(一):vue實現github活躍圖
阿新 • • 發佈:2021-02-11
根據一些實用、不難但是繁瑣的功能,推出一個這個系列,方便大家以後直接拿來就用。
緣由:
最新有老師看見其他網站有仿github活躍圖的效果,讓我安排一下。
去github仔細看了看,以一個想法是看看echarts有沒有類似的,找了一會發現有個類似的,但是修改成本較大,果斷造輪子。
效果圖:
思路:
1.先排除座標說明,先繪製最裡面的每個小格子。
第一個格子的時間:上一年今天如果不是週一,那麼推到下一個週一。
最後一個格子的時間:今天。
格子的數量:最後一個格子時間 - 第一個格子時間 。
最後一個列的個數:今天的星期數 或 格子總數量除以7取餘。
格子的顏色:格子是用for迴圈變數的陣列資料,設定每個格子data時候,帶上提交次數屬性,然後寫個根據數量獲取不同顏色的方法。
滑鼠移入的內容:元件採用的iview的tooltip。根據專案整合的ui庫可更改,或者手寫一個也不難。
2.繪製x,y軸座標值。
y座標代表星期數,比較簡單,直接新增格子容器節點的上面就行。
x座標代表月份, 並非每列都有月份,只會在當前月份的第一列顯示月份。
月份第一個列判斷依據:每個格子我都賦予了具體的日期,判斷前一列是否有不相同月份的日期,如果有不同的日期設定一個變數為true,如果沒有重新設定成false,然後根據這個變數來決定本列是不是月份第一列。
3.繪製legend。
位置使用flex佈局就行,不做過多解釋。
左側的slider用的iview。
程式碼:
<template> <div class="submission-chart"> <div class="calendar"> <div class="weeks"> <div class="week">週二</div> <div class="week">週四</div> <div class="week">週六</div> </div> <div class="column" v-for="(columnData, columnIndex) in dateData" :key="columnIndex"> <div class="title">{{columnData.title}}</div> <div class="date-wrapper" v-for="(dateData, dateIndex) in columnData.data" :key="dateIndex" :style="`background:${getColor(dateData.number)};`" > <Tooltip placement="top" :delay="300" :content="`${dateData.date}:${dateData.number}次通過`"> <div class="date"></div> </Tooltip> </div> </div> </div> <div class="operation"> <div class="slider"> <div class="slider-desc">0</div> <div style="width:120px;"> <Slider :value="sliderValue" :max="12" range :tip-format="sliderFormat" @on-change="sliderChange"></Slider> </div> <div class="slider-desc">12+</div> </div> <div class="legend"> <div class="level-desc">少</div> <div class="level level-1"></div> <div class="level level-2"></div> <div class="level level-3"></div> <div class="level level-4"></div> <div class="level level-5"></div> <div class="level-desc">多</div> </div> </div> </div> </template> <script> import moment from 'moment' export default { name: 'submission-chart', data () { return { dateData: [], submissionRecord: {}, sliderValue: [0, 12] } }, props: { profile: { default: {}, type: Object } }, mounted () { this.formatProblemData() this.init() }, methods: { init () { // 上一年資訊 let prevYear = moment().format('YYYY') - 1 let prevTodayFormatStr = prevYear + '-' + moment().format('MM-DD') let prevToday = moment(prevTodayFormatStr).format('YYYY-MM-DD') // 上年今日的是星期幾 let prevTodayWeekNum = moment(prevToday).weekday() || 7 // 初始日期(上年臨近的星期一) let firstMondayDate = prevTodayWeekNum > 1 ? moment(prevToday).add(8 - prevTodayWeekNum, 'days').format('YYYY-MM-DD') : prevToday // 初始日期至今日的天數,包括今日 let days = moment().diff(moment(firstMondayDate), 'days') + 1 // 每週天數 let columns = 7 // 最大列數(週數) let lineNums = Math.ceil(days / columns) // 繪製圖表的源資料 let dateData = [] let monthCN = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'] let startSliderNum = this.sliderValue[0] let endSliderNum = this.sliderValue[1] for (let i = 0; i < lineNums; i++) { // 最近一星期不一定滿的 let weekColumn = (i === lineNums - 1 ? days % columns ? days % columns : columns : columns) // 開始計算title(月份的圖例) // 思路:第一列直接根據第一天的月份 // 之後的嘛列數根據上一週的最後一天減去第一天的月份,如果大於1代表月份發生了改變,下一列的title顯示最新的月份 let theWeekStartMonth = moment(firstMondayDate).add(i * 7, 'days').format('M') let theWeekEndMonth = moment(firstMondayDate).add(i * 7 + weekColumn, 'days').format('M') let title = (i === 0) ? monthCN[theWeekStartMonth - 1] : '' let ifSwitchMonth = false if (theWeekEndMonth - theWeekStartMonth) { ifSwitchMonth = true } if (i && dateData[i - 1].ifSwitchMonth) { title = monthCN[theWeekEndMonth - 1] } // 圖表源資料格式:columns:列數,title:列標題,ifSwitchMonth:月份是否發生改變,data:每格資料 dateData.push({ columns: weekColumn, title: title, ifSwitchMonth: ifSwitchMonth, data: [] }) for (let j = 0; j < dateData[i].columns; j++) { let date = moment(firstMondayDate).add(i * 7 + j, 'days').format('YYYY-MM-DD') let number = 0 // 提交次數在slider範圍內再進行次數賦值 if ((this.submissionRecord[date] >= startSliderNum && this.submissionRecord[date] < endSliderNum) || (this.submissionRecord[date] > 12 && endSliderNum === 12)) { number = this.submissionRecord[date] } // number:提交次數,date:提交日期 dateData[i].data.push({ number: number, date: date }) } } this.dateData = dateData }, formatProblemData () { let submissionRecord = {} // let OIProblems = this.profile.oi_problems_status.problems || {} // // 格式化profile中oi的提交記錄資料,建立submissionRecord物件,將create_time作為key進行儲存 // Object.keys(OIProblems).forEach(problemID => { // if (OIProblems[problemID]['status'] === 0) { // let date = moment(OIProblems[problemID]['create_time']).format('YYYY-MM-DD') // // 第一次出現提交次數設定1,之後每次出現提交次數+1 // submissionRecord[date.toString()] = submissionRecord[date] ? ((submissionRecord[date])) + 1 : 1 // } // }) // 處理你的業務邏輯 // submissionRecord 最後的格式應為 {'2020-01-01':10, '2020-01-02': 11} this.submissionRecord = submissionRecord }, getColor (number) { // level color // 左閉右開 let color = '#EBEDF0' if (number >= 12) { color = '#196127' } else if (number >= 8) { color = '#239A3B' } else if (number >= 4) { color = '#7BC96F' } else if (number >= 1) { color = '#C6E48B' } else { color = '#EBEDF0' } return color }, sliderFormat (val) { return '提交次數: ' + val }, sliderChange (val) { // 沒有使用v-model繫結sliderValue而是採用回撥的原因 // 1.拖拽1px sliderValue都會引起元件重繪,此元件計算嵌套了2個for迴圈,導致頁面出現卡頓slider不流暢的情況 this.sliderValue = val this.init() } } } </script> <style lang="less" scoped> .submission-chart { width: 820px; height: 180px; background-color: #fff; margin: auto; margin-top: 20px; padding: 0px 0; font-size: 12px; .calendar { margin-left: 16px; margin-right: 16px; display: flex; .weeks { width: 30px; margin-right: 3px; margin-top: 22px; .week { margin-top: 13px; width: 30px; height: 14px; } } .column { width: 11px; margin-right: 3px; .title { width: 14px; height: 14px; margin-bottom: 8px; text-align: left; overflow: visible; white-space: nowrap; } .date-wrapper { width: 11px; height: 11px; background: #EBEDF0; margin-bottom: 3px; .date { width: 11px; height: 11px; :hover { width: 13px; height: 13px; } } } } } .operation { display: flex; justify-content: space-between; align-items: center; margin-top: 10px; .slider { display: flex; justify-content: center; align-items: center; width: 200px; .slider-desc { width: 11px; margin: 0 8px; } } .legend { display: flex; justify-content:center; align-items: center; .level-desc { margin-right: 6px; margin-left: 3px; } .level { margin-right: 3px; width: 11px; height: 11px; } .level-1 { background: #EBEDF0; } .level-2 { background: #C6E48B; } .level-3 { background: #7BC96F; } .level-4 { background: #239A3B; } .level-5 { background: #196127; } } } } </style>
使用:
<submission-chart :profile="profile"/>