如何使用echarts做类似github日历贡献图?

在某个大神的博客中看到这个动态日历的组件,以类似github日历贡献图的方式展现了近段时间的文章数量。感觉非常棒,于是就产生了在jpress上做一个这种动态日历的想法,使用echarts热力图,最终实现效果如下图:

 

该组件前端基于echarts,后端基于jpress。 

图表显示最近365天每天的文章发布数量,并以绿色展示当天的发布量,颜色越深则数量越大。 我们先将图表效果做出来再与后端的数据做结合。 首先我们需要先在页面中引入echarts.js,并定义相关的颜色:

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5.2.1/dist/echarts.min.js"></script>
<script type="text/javascript">
    var face ="light";
    var color1, color2, color3, color4;
    if("light" === face){
        color1 = ["#ebedf0", "#c6e48b", "#7bc96f", "#239a3b", "#196127"];
        color2 = "#ffffff";
        color3 = "#3C4858";
        color4 = "#f9f9f9";
    }else{
        color1 = ["#ebedf0", "#c6e48b", "#7bc96f", "#239a3b", "#196127"];
        color2 = "#000";
        color3 = "#fff";
        color4 = "#212121";
    }
</script>

然后在页面中用一个div做图表的容器:  

<div id="japressCalendarHeatmap" style="width: 100% ; height: 150px"></div>

接下来编写相关的js渲染图表:  

<script type="text/javascript">
function showJapressCalendarHeatmap(){
    var mydata = [];
    mydata.push( ['2020-10-25',0],  ['2020-10-26',0],  ['2020-10-27',0],  ['2020-10-28',0],  ['2020-10-29',0],  ['2020-10-30',0],  ['2020-10-31',0],  ['2020-11-01',0],  ['2020-11-02',0],  ['2020-11-03',0],  ['2020-11-04',0],  ['2020-11-05',0],  ['2020-11-06',0],  ['2020-11-07',0],  ['2020-11-08',0],  ['2020-11-09',0],  ['2020-11-10',0],  ['2020-11-11',0],  ['2020-11-12',0],  ['2020-11-13',0],  ['2020-11-14',0],  ['2020-11-15',0],  ['2020-11-16',0],  ['2020-11-17',0],  ['2020-11-18',0],  ['2020-11-19',0],  ['2020-11-20',0],  ['2020-11-21',0],  ['2020-11-22',0],  ['2020-11-23',0],  ['2020-11-24',0],  ['2020-11-25',0],  ['2020-11-26',0],  ['2020-11-27',0],  ['2020-11-28',0],  ['2020-11-29',0],  ['2020-11-30',0],  ['2020-12-01',0],  ['2020-12-02',0],  ['2020-12-03',0],  ['2020-12-04',0],  ['2020-12-05',0],  ['2020-12-06',0],  ['2020-12-07',0],  ['2020-12-08',0],  ['2020-12-09',0],  ['2020-12-10',0],  ['2020-12-11',0],  ['2020-12-12',0],  ['2020-12-13',0],  ['2020-12-14',0],  ['2020-12-15',0],  ['2020-12-16',0],  ['2020-12-17',0],  ['2020-12-18',0],  ['2020-12-19',0],  ['2020-12-20',1],  ['2020-12-21',0],  ['2020-12-22',0],  ['2020-12-23',0],  ['2020-12-24',0],  ['2020-12-25',2],  ['2020-12-26',0],  ['2020-12-27',0],  ['2020-12-28',0],  ['2020-12-29',0],  ['2020-12-30',0],  ['2020-12-31',0],  ['2021-01-01',0],  ['2021-01-02',0],  ['2021-01-03',0],  ['2021-01-04',0],  ['2021-01-05',0],  ['2021-01-06',0],  ['2021-01-07',0],  ['2021-01-08',0],  ['2021-01-09',0],  ['2021-01-10',0],  ['2021-01-11',0],  ['2021-01-12',0],  ['2021-01-13',0],  ['2021-01-14',0],  ['2021-01-15',0],  ['2021-01-16',0],  ['2021-01-17',0],  ['2021-01-18',0],  ['2021-01-19',0],  ['2021-01-20',0],  ['2021-01-21',0],  ['2021-01-22',0],  ['2021-01-23',0],  ['2021-01-24',0],  ['2021-01-25',0],  ['2021-01-26',0],  ['2021-01-27',0],  ['2021-01-28',0],  ['2021-01-29',0],  ['2021-01-30',0],  ['2021-01-31',0],  ['2021-02-01',0],  ['2021-02-02',0],  ['2021-02-03',0],  ['2021-02-04',0],  ['2021-02-05',0],  ['2021-02-06',0],  ['2021-02-07',0],  ['2021-02-08',0],  ['2021-02-09',0],  ['2021-02-10',0],  ['2021-02-11',0],  ['2021-02-12',0],  ['2021-02-13',0],  ['2021-02-14',0],  ['2021-02-15',0],  ['2021-02-16',0],  ['2021-02-17',0],  ['2021-02-18',0],  ['2021-02-19',0],  ['2021-02-20',0],  ['2021-02-21',0],  ['2021-02-22',0],  ['2021-02-23',0],  ['2021-02-24',0],  ['2021-02-25',0],  ['2021-02-26',0],  ['2021-02-27',0],  ['2021-02-28',0],  ['2021-03-01',0],  ['2021-03-02',0],  ['2021-03-03',0],  ['2021-03-04',0],  ['2021-03-05',0],  ['2021-03-06',0],  ['2021-03-07',0],  ['2021-03-08',0],  ['2021-03-09',0],  ['2021-03-10',0],  ['2021-03-11',0],  ['2021-03-12',0],  ['2021-03-13',0],  ['2021-03-14',0],  ['2021-03-15',0],  ['2021-03-16',0],  ['2021-03-17',0],  ['2021-03-18',0],  ['2021-03-19',0],  ['2021-03-20',0],  ['2021-03-21',0],  ['2021-03-22',0],  ['2021-03-23',0],  ['2021-03-24',0],  ['2021-03-25',0],  ['2021-03-26',0],  ['2021-03-27',0],  ['2021-03-28',0],  ['2021-03-29',0],  ['2021-03-30',0],  ['2021-03-31',0],  ['2021-04-01',0],  ['2021-04-02',0],  ['2021-04-03',0],  ['2021-04-04',0],  ['2021-04-05',0],  ['2021-04-06',0],  ['2021-04-07',0],  ['2021-04-08',0],  ['2021-04-09',0],  ['2021-04-10',0],  ['2021-04-11',0],  ['2021-04-12',0],  ['2021-04-13',0],  ['2021-04-14',0],  ['2021-04-15',0],  ['2021-04-16',0],  ['2021-04-17',0],  ['2021-04-18',0],  ['2021-04-19',0],  ['2021-04-20',0],  ['2021-04-21',0],  ['2021-04-22',0],  ['2021-04-23',0],  ['2021-04-24',0],  ['2021-04-25',1],  ['2021-04-26',0],  ['2021-04-27',0],  ['2021-04-28',0],  ['2021-04-29',0],  ['2021-04-30',0],  ['2021-05-01',0],  ['2021-05-02',0],  ['2021-05-03',0],  ['2021-05-04',0],  ['2021-05-05',0],  ['2021-05-06',0],  ['2021-05-07',0],  ['2021-05-08',0],  ['2021-05-09',0],  ['2021-05-10',0],  ['2021-05-11',0],  ['2021-05-12',0],  ['2021-05-13',0],  ['2021-05-14',0],  ['2021-05-15',0],  ['2021-05-16',0],  ['2021-05-17',0],  ['2021-05-18',0],  ['2021-05-19',0],  ['2021-05-20',0],  ['2021-05-21',0],  ['2021-05-22',0],  ['2021-05-23',0],  ['2021-05-24',0],  ['2021-05-25',0],  ['2021-05-26',0],  ['2021-05-27',0],  ['2021-05-28',0],  ['2021-05-29',0],  ['2021-05-30',0],  ['2021-05-31',0],  ['2021-06-01',1],  ['2021-06-02',0],  ['2021-06-03',0],  ['2021-06-04',0],  ['2021-06-05',0],  ['2021-06-06',0],  ['2021-06-07',0],  ['2021-06-08',0],  ['2021-06-09',0],  ['2021-06-10',0],  ['2021-06-11',0],  ['2021-06-12',0],  ['2021-06-13',0],  ['2021-06-14',0],  ['2021-06-15',0],  ['2021-06-16',0],  ['2021-06-17',0],  ['2021-06-18',0],  ['2021-06-19',0],  ['2021-06-20',0],  ['2021-06-21',0],  ['2021-06-22',0],  ['2021-06-23',0],  ['2021-06-24',0],  ['2021-06-25',0],  ['2021-06-26',0],  ['2021-06-27',0],  ['2021-06-28',0],  ['2021-06-29',0],  ['2021-06-30',0],  ['2021-07-01',0],  ['2021-07-02',0],  ['2021-07-03',0],  ['2021-07-04',0],  ['2021-07-05',0],  ['2021-07-06',0],  ['2021-07-07',0],  ['2021-07-08',0],  ['2021-07-09',0],  ['2021-07-10',0],  ['2021-07-11',0],  ['2021-07-12',0],  ['2021-07-13',0],  ['2021-07-14',0],  ['2021-07-15',0],  ['2021-07-16',0],  ['2021-07-17',0],  ['2021-07-18',0],  ['2021-07-19',0],  ['2021-07-20',0],  ['2021-07-21',0],  ['2021-07-22',0],  ['2021-07-23',0],  ['2021-07-24',0],  ['2021-07-25',0],  ['2021-07-26',0],  ['2021-07-27',0],  ['2021-07-28',0],  ['2021-07-29',0],  ['2021-07-30',0],  ['2021-07-31',0],  ['2021-08-01',0],  ['2021-08-02',0],  ['2021-08-03',0],  ['2021-08-04',0],  ['2021-08-05',0],  ['2021-08-06',0],  ['2021-08-07',0],  ['2021-08-08',0],  ['2021-08-09',0],  ['2021-08-10',0],  ['2021-08-11',0],  ['2021-08-12',0],  ['2021-08-13',0],  ['2021-08-14',0],  ['2021-08-15',0],  ['2021-08-16',0],  ['2021-08-17',0],  ['2021-08-18',0],  ['2021-08-19',0],  ['2021-08-20',0],  ['2021-08-21',0],  ['2021-08-22',0],  ['2021-08-23',0],  ['2021-08-24',0],  ['2021-08-25',0],  ['2021-08-26',0],  ['2021-08-27',0],  ['2021-08-28',0],  ['2021-08-29',0],  ['2021-08-30',0],  ['2021-08-31',0],  ['2021-09-01',1],  ['2021-09-02',0],  ['2021-09-03',0],  ['2021-09-04',0],  ['2021-09-05',0],  ['2021-09-06',0],  ['2021-09-07',0],  ['2021-09-08',0],  ['2021-09-09',0],  ['2021-09-10',0],  ['2021-09-11',0],  ['2021-09-12',0],  ['2021-09-13',0],  ['2021-09-14',0],  ['2021-09-15',0],  ['2021-09-16',0],  ['2021-09-17',0],  ['2021-09-18',0],  ['2021-09-19',0],  ['2021-09-20',0],  ['2021-09-21',0],  ['2021-09-22',0],  ['2021-09-23',0],  ['2021-09-24',0],  ['2021-09-25',0],  ['2021-09-26',0],  ['2021-09-27',0],  ['2021-09-28',0],  ['2021-09-29',0],  ['2021-09-30',0],  ['2021-10-01',0],  ['2021-10-02',0],  ['2021-10-03',0],  ['2021-10-04',0],  ['2021-10-05',0],  ['2021-10-06',0],  ['2021-10-07',0],  ['2021-10-08',0],  ['2021-10-09',0],  ['2021-10-10',0],  ['2021-10-11',0],  ['2021-10-12',0],  ['2021-10-13',0],  ['2021-10-14',0],  ['2021-10-15',0],  ['2021-10-16',5],  ['2021-10-17',0],  ['2021-10-18',1],  ['2021-10-19',0],  ['2021-10-20',0],  ['2021-10-21',0],  ['2021-10-22',0],  ['2021-10-23',0],  ['2021-10-24',0],  ['2021-10-25',1], );
    var chartDom = document.getElementById('japressCalendarHeatmap');
    var myChart = echarts.init(chartDom);
    var option = {
        title: {
            show:false,
            top: 30,
        },
        tooltip: {
            trigger: 'item',
            formatter: function (params) {
                return params.data[0] + '<br>数量:' + params.data[1];
            }
        },
        visualMap: {
            min: 0,
            max: 5,
            show: false,
            showLabel: true,
            calculable: false,
            inRange: {
                symbol: 'rect',
                color: color1
            },
            itemWidth: 12,
            itemHeight: 50,
            orient: 'horizontal',
            left: 'center',
            top: 0
        },
        calendar: {
            left: 'center',
                top:'center',
            name: {
                textStyle: {
                    color: color3
                }
            },
            cellSize: [13, 13],
            splitLine: {
                show: false
            },
            range: ['2020-10-25','2021-10-25'],
            itemStyle: {
                borderColor: color2,
                borderWidth: 2
            },
            yearLabel: { show: false },
            dayLabel: {
                nameMap: 'cn', // 设置中文显示
                color: color3,
                fontSize:11,
            },
            monthLabel: {
                nameMap: 'cn', // 设置中文显示
                fontSize:11,
                color: color3,
            }
        },
        series: {
            type: 'heatmap',
            coordinateSystem: 'calendar',
            data: mydata
        }
    };
    option && myChart.setOption(option);
}
showJapressCalendarHeatmap();
</script>

到这里,静态的图表就完成了。   接下来我们需要调用实际的数据,这里我们编写一个jpress插件,来获取数据,下面是关键代码: ChartsArticleProvider.java:

package com.miigua.japress.charts.provider;
import com.miigua.japress.charts.service.ChartsArticleService;
import io.jboot.aop.annotation.Bean;
import io.jboot.components.cache.CacheTime;
import io.jboot.components.cache.annotation.Cacheable;
import io.jboot.service.JbootServiceBase;
import io.jpress.module.article.model.Article;
import java.util.List;
@Bean
public class ChartsArticleProvider extends JbootServiceBase<Article> implements ChartsArticleService {
    @Override
    @Cacheable(name = "japress-charts", key = "daylist-#(begin)-#(end)", liveSeconds = 30 * CacheTime.MINUTE, nullCacheEnable = true)
    public List<Article> getDayListCount(String begin , String end){
        begin += " 00:00:00";
        end += " 23:59:59";
        List<Article> articles = DAO.find("select *,DATE_FORMAT(created,'%Y-%m-%d') as days ,count(id) as counts , created from article GROUP BY days HAVING created >= ? and created <= ? order by created asc" , begin ,end);
        return articles;
    }
}

Util.java:

package com.miigua.japress.charts.utils;
import io.jboot.utils.DateUtil;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
public class Util {
    /**
     * 获取某个时间段内所有日期
     * @param begin
     * @param end
     * @return
     */
    public static List<String> getDayBetweenDates(String begin, String end) {
        Date dBegin = DateUtil.parseDate(begin,DateUtil.datePattern);
        Date dEnd = DateUtil.parseDate(end,DateUtil.datePattern);
        List<String> lDate = new ArrayList<String>();
        lDate.add(begin);
        Calendar calBegin = Calendar.getInstance();
        // 使用给定的 Date 设置此 Calendar 的时间
        calBegin.setTime(dBegin);
        Calendar calEnd = Calendar.getInstance();
        // 使用给定的 Date 设置此 Calendar 的时间
        calEnd.setTime(dEnd);
        // 测试此日期是否在指定日期之后
        while (dEnd.after(calBegin.getTime())) {
            // 根据日历的规则,为给定的日历字段添加或减去指定的时间量
            calBegin.add(Calendar.DAY_OF_MONTH, 1);
            lDate.add(DateUtil.toString(calBegin.getTime(),DateUtil.datePattern));
        }
        return lDate;
    }
}

CalendarHeatmapDirective.java:    

package com.miigua.japress.charts.directive;
import com.jfinal.aop.Inject;
import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
import com.miigua.japress.charts.service.ChartsArticleService;
import com.miigua.japress.charts.utils.Util;
import io.jboot.utils.DateUtil;
import io.jboot.web.directive.annotation.JFinalDirective;
import io.jboot.web.directive.base.JbootDirectiveBase;
import io.jpress.module.article.model.Article;
import java.util.*;
@JFinalDirective("japressCalendarHeatmap")
public class CalendarHeatmapDirective extends JbootDirectiveBase {
    @Inject
    private ChartsArticleService articleProvider;
    @Override
    public void onRender(Env env, Scope scope, Writer writer) {
        //需要展示的天数
        int days = getParaToInt("days", scope, 365);
        Date now = new Date();
        //计算起始时间
        String endTime = DateUtil.toString(now,DateUtil.datePattern);
        Date temp = DateUtil.addDays(now , -days);
        String startTime = DateUtil.toString(temp,DateUtil.datePattern);
        //获取起始时间内每天的数量
        List<Article> dayArticleCount = articleProvider.getDayListCount(startTime, endTime);
        //获取起始时间内的每一天
        List<String> dateList = Util.getDayBetweenDates(startTime, endTime);
        Map<String, Long> dateMap = new HashMap<>();
        long max = 0;
        for(Article a : dayArticleCount) {
            String day = a.get("days");
            long count = a.get("counts");
            if(count > max){
                max = count;
            }
            dateMap.put(day, count);
        }
        List<Map<String,Object>> resultList = new ArrayList<>();
        for(String day : dateList) {
            long count = 0;
            if(dateMap.get(day) != null) {
                count = dateMap.get(day);
            }
            Map<String,Object> map = new HashMap<>(2);
            map.put("day",day);
            map.put("count",count);
            resultList.add(map);
        }
        Map<String,Object> resultMap = new HashMap<>(2);
        resultMap.put("dayList",resultList);
        resultMap.put("max",max);
        resultMap.put("min",0);
        scope.setLocal("japressCalendarHeatmap", resultMap);
        renderBody(env, scope, writer);
    }
    @Override
    public boolean hasEnd() {
        return true;
    }
}

以上便是插件核心代码。 接下来我们开始在html页面中读取后端的数据,为了调用方便以及页面模板的整洁,我们将图表html和js相关的代码单独定义到一个文件,使用的时候直接引入调用。 这里我的文件名是 japress-charts.html,文件内容如下:  

<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5.2.1/dist/echarts.min.js"></script>
<script type="text/javascript">
    var face ="light";
    var color1, color2, color3, color4;
    if("light" === face){
        color1 = ["#ebedf0", "#c6e48b", "#7bc96f", "#239a3b", "#196127"];
        color2 = "#ffffff";
        color3 = "#3C4858";
        color4 = "#f9f9f9";
    }else{
        color1 = ["#ebedf0", "#c6e48b", "#7bc96f", "#239a3b", "#196127"];
        color2 = "#000";
        color3 = "#fff";
        color4 = "#212121";
    }
</script>
#define japressCalendarHeatmap(width,height,dayCount)
#japressCalendarHeatmap(days=dayCount)
#if(japressCalendarHeatmap.dayList.size() > 0)
<div id="japressCalendarHeatmap" style="width: #(width ?? '100%') ; height: #(height ?? '185px')"></div>
<script type="text/javascript">
    function showJapressCalendarHeatmap(){
        var mydata = [];
        mydata.push(#for(j:japressCalendarHeatmap.dayList) ['#(j.day)',#(j.count)], #end);
        var chartDom = document.getElementById('japressCalendarHeatmap');
        var myChart = echarts.init(chartDom);
        var option = {
            title: {
                show:false,
                top: 30,
            },
            tooltip: {
                trigger: 'item',
                formatter: function (params) {
                    return params.data[0] + '<br>数量:' + params.data[1];
                }
            },
            visualMap: {
                min: #(japressCalendarHeatmap.min),
                max: #(japressCalendarHeatmap.max),
                show: false,
                showLabel: true,
                calculable: false,
                inRange: {
                    symbol: 'rect',
                    color: color1
                },
                itemWidth: 12,
                itemHeight: 50,
                orient: 'horizontal',
                left: 'center',
                top: 0
            },
            calendar: {
                left: 'center',
                    top:'center',
                name: {
                    textStyle: {
                        color: color3
                    }
                },
                cellSize: [13, 13],
                splitLine: {
                    show: false
                },
                range: ['#(japressCalendarHeatmap.dayList.get(0).day)','#(japressCalendarHeatmap.dayList.get(japressCalendarHeatmap.dayList.size()-1).day)'],
                itemStyle: {
                    borderColor: color2,
                    borderWidth: 2
                },
                yearLabel: { show: false },
                dayLabel: {
                    nameMap: 'cn', // 设置中文显示
                    color: color3,
                    fontSize:11,
                },
                monthLabel: {
                    nameMap: 'cn', // 设置中文显示
                    fontSize:11,
                    color: color3,
                }
            },
            series: {
                type: 'heatmap',
                coordinateSystem: 'calendar',
                data: mydata
            }
        };
        option && myChart.setOption(option);
    }
    showJapressCalendarHeatmap();
</script>
#end
#end
#end

在需要使用该图表的时候我们只需要如下方式引入调用就可以了:    

#include("./japress-charts.html")
#@japressCalendarHeatmap('100%','150px',365)

调用时支持3个自定义的参数:组件宽,组件高,要展示的天数。 ok,大功告成!注意再使用的时候要先安装刚才编写的插件。

版权声明:
作者:Miigua
链接:https://www.miigua.com/article/282.html
来源:米瓜的博客
文章版权归作者所有,未经允许请勿转载。

THE END
二维码
打赏
请在后台主题设置处设置打赏图片
< <上一篇
下一篇>>