[Python从零到壹] 番外篇之可视化利用D3库实现CSDN博客每日统计效果(类似github)

news2024/11/9 2:14:00

欢迎大家来到“Python从零到壹”,在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界。所有文章都将结合案例、代码和作者的经验讲解,真心想把自己近十年的编程经验分享给大家,希望对您有所帮助,文章中不足之处也请海涵。Python系列整体框架包括基础语法10篇、网络爬虫30篇、可视化分析10篇、机器学习20篇、大数据分析20篇、图像识别30篇、人工智能40篇、Python安全20篇、其他技巧10篇。您的关注、点赞和转发就是对秀璋最大的支持,知识无价人有情,希望我们都能在人生路上开心快乐、共同成长。

本文属于番外篇,主要介绍Python可视化分析,利用Python采集每年发表博客的数据,再利用D3实现类似于Github的每日贡献统计,显示效果如下图所示,也是回答读者之前的疑惑。这十多年在CSDN坚持分享,也是一笔宝贵的财务啊!希望文章对您有所帮助,如果有不足之处,还请海涵。

文章目录

  • 一.博客数据采集
    • 1.数据采集
    • 2.翻页采集数据
    • 3.数据按年份存储
    • 4.完整代码
  • 二.D3可视化分析
  • 三.CSDN近十年内容分析
  • 四.总结

在这里插入图片描述

在这里插入图片描述

该部分代码在作者Github的番外篇中可以下载,下载地址如下:

  • https://github.com/eastmountyxz/Python-zero2one
  • 开源600多页电子书:https://github.com/eastmountyxz/HWCloudImageRecognition

本文参考zjw666作者的D3代码,在此感谢,其地址如下:

  • https://github.com/d3
  • https://github.com/zjw666/D3_demo

前文赏析:(尽管该部分占大量篇幅,但我舍不得删除,哈哈!)

第一部分 基础语法

  • [Python从零到壹] 一.为什么我们要学Python及基础语法详解
  • [Python从零到壹] 二.语法基础之条件语句、循环语句和函数
  • [Python从零到壹] 三.语法基础之文件操作、CSV文件读写及面向对象

第二部分 网络爬虫

  • [Python从零到壹] 四.网络爬虫之入门基础及正则表达式抓取博客案例
  • [Python从零到壹] 五.网络爬虫之BeautifulSoup基础语法万字详解
  • [Python从零到壹] 六.网络爬虫之BeautifulSoup爬取豆瓣TOP250电影详解
  • [Python从零到壹] 七.网络爬虫之Requests爬取豆瓣电影TOP250及CSV存储
  • [Python从零到壹] 八.数据库之MySQL基础知识及操作万字详解
  • [Python从零到壹] 九.网络爬虫之Selenium基础技术万字详解(定位元素、常用方法、键盘鼠标操作)
  • [Python从零到壹] 十.网络爬虫之Selenium爬取在线百科知识万字详解(NLP语料构造必备技能)

第三部分 数据分析和机器学习

  • [Python从零到壹] 十一.数据分析之Numpy、Pandas、Matplotlib和Sklearn入门知识万字详解(1)
  • [Python从零到壹] 十二.机器学习之回归分析万字总结全网首发(线性回归、多项式回归、逻辑回归)
  • [Python从零到壹] 十三.机器学习之聚类分析万字总结全网首发(K-Means、BIRCH、层次聚类、树状聚类)
  • [Python从零到壹] 十四.机器学习之分类算法三万字总结全网首发(决策树、KNN、SVM、分类算法对比)
  • [Python从零到壹] 十五.文本挖掘之数据预处理、Jieba工具和文本聚类万字详解
  • [Python从零到壹] 十六.文本挖掘之词云热点与LDA主题分布分析万字详解
  • [Python从零到壹] 十七.可视化分析之Matplotlib、Pandas、Echarts入门万字详解
  • [Python从零到壹] 十八.可视化分析之Basemap地图包入门详解
  • [Python从零到壹] 十九.可视化分析之热力图和箱图绘制及应用详解
  • [Python从零到壹] 二十.可视化分析之Seaborn绘图万字详解
  • [Python从零到壹] 二十一.可视化分析之Pyechart绘图万字详解
  • [Python从零到壹] 二十二.可视化分析之OpenGL绘图万字详解
  • [Python从零到壹] 二十三.十大机器学习算法之决策树分类分析详解(1)
  • [Python从零到壹] 二十四.十大机器学习算法之KMeans聚类分析详解(2)
  • [Python从零到壹] 二十五.十大机器学习算法之KNN算法及图像分类详解(3)
  • [Python从零到壹] 二十六.十大机器学习算法之朴素贝叶斯算法及文本分类详解(4)
  • [Python从零到壹] 二十七.十大机器学习算法之线性回归算法分析详解(5)
  • [Python从零到壹] 二十八.十大机器学习算法之SVM算法分析详解(6)
  • [Python从零到壹] 二十九.十大机器学习算法之随机森林算法分析详解(7)
  • [Python从零到壹] 三十.十大机器学习算法之逻辑回归算法及恶意请求检测应用详解(8)
  • [Python从零到壹] 三十一.十大机器学习算法之Boosting和AdaBoost应用详解(9)
  • [Python从零到壹] 三十二.十大机器学习算法之层次聚类和树状图聚类应用详解(10)

第四部分 Python图像处理基础

  • [Python从零到壹] 三十三.图像处理基础篇之什么是图像处理和OpenCV配置
  • [Python从零到壹] 三十四.OpenCV入门详解——显示读取修改及保存图像
  • [Python从零到壹] 三十五.图像处理基础篇之OpenCV绘制各类几何图形
  • [Python从零到壹] 三十六.图像处理基础篇之图像算术与逻辑运算详解
  • [Python从零到壹] 三十七.图像处理基础篇之图像融合处理和ROI区域绘制
  • [Python从零到壹] 三十八.图像处理基础篇之图像几何变换(平移缩放旋转)
  • [Python从零到壹] 三十九.图像处理基础篇之图像几何变换(镜像仿射透视)
  • [Python从零到壹] 四十.图像处理基础篇之图像量化处理
  • [Python从零到壹] 四十一.图像处理基础篇之图像采样处理
  • [Python从零到壹] 四十二.图像处理基础篇之图像金字塔向上取样和向下取样

第五部分 Python图像运算和图像增强

  • [Python从零到壹] 四十三.图像增强及运算篇之图像点运算和图像灰度化处理
  • [Python从零到壹] 四十四.图像增强及运算篇之图像灰度线性变换详解
  • [Python从零到壹] 四十五.图像增强及运算篇之图像灰度非线性变换详解
  • [Python从零到壹] 四十六.图像增强及运算篇之图像阈值化处理
  • [Python从零到壹] 四十七.图像增强及运算篇之腐蚀和膨胀详解
  • [Python从零到壹] 四十八.图像增强及运算篇之形态学开运算、闭运算和梯度运算
  • [Python从零到壹] 四十九.图像增强及运算篇之顶帽运算和底帽运算
  • [Python从零到壹] 五十.图像增强及运算篇之图像直方图理论知识和绘制实现
  • [Python从零到壹] 五十一.图像增强及运算篇之图像灰度直方图对比分析万字详解
  • [Python从零到壹] 五十二.图像增强及运算篇之图像掩膜直方图和HS直方图
  • [Python从零到壹] 五十三.图像增强及运算篇之直方图均衡化处理
  • [Python从零到壹] 五十四.图像增强及运算篇之局部直方图均衡化和自动色彩均衡化处理
  • [Python从零到壹] 五十五.图像增强及运算篇之图像平滑(均值滤波、方框滤波、高斯滤波)
  • [Python从零到壹] 五十六.图像增强及运算篇之图像平滑(中值滤波、双边滤波)
  • [Python从零到壹] 五十七.图像增强及运算篇之图像锐化Roberts、Prewitt算子实现边缘检测
  • [Python从零到壹] 五十八.图像增强及运算篇之图像锐化Sobel、Laplacian算子实现边缘检测
  • [Python从零到壹] 五十九.图像增强及运算篇之图像锐化Scharr、Canny、LOG实现边缘检测

第六部分 Python图像识别和图像高阶案例

  • [Python从零到壹] 六十.图像识别及经典案例篇之基于阈值及边缘检测的图像分割
  • [Python从零到壹] 六十一.图像识别及经典案例篇之基于纹理背景和聚类算法的图像分割
  • [Python从零到壹] 六十二.图像识别及经典案例篇之基于均值漂移算法和分水岭算法的图像分割
  • [Python从零到壹] 六十三.图像识别及经典案例篇之图像漫水填充分割应用

第七部分 NLP与文本挖掘

第八部分 人工智能入门知识

第九部分 网络攻防与AI安全

第十部分 知识图谱构建实战

扩展部分 人工智能高级案例

  • [Python从零到壹] 番外篇之可视化利用D3库实现CSDN博客每日统计效果(类似github)

作者新开的“娜璋AI安全之家”将专注于Python和安全技术,主要分享Web渗透、系统安全、人工智能、大数据分析、图像识别、恶意代码检测、CVE复现、威胁情报分析等文章。虽然作者是一名技术小白,但会保证每一篇文章都会很用心地撰写,希望这些基础性文章对你有所帮助,在Python和安全路上与大家一起进步。


一.博客数据采集

首先介绍博客数据采集方法,为保护CSDN原创,这里仅以方法为主。建议读者结合自己的方向去抓取文本知识。

1.数据采集

核心扩展包:

  • import requests
  • from lxml import etree

核心流程:

  • 解决headers问题
  • 解决翻页问题
  • 审查元素分析DOM树结构
  • 定位节点采用Xpath分析
  • 分别采集标题、URL、时间、阅读和评论数量

审查元素如下图所示:

在这里插入图片描述

对应HTML代码如下:

<div class="article-list">
    <div class="article-item-box csdn-tracking-statistics">
      <h4 class="">
        <a href="https://blog.csdn.net/Eastmount/article/details/124587047">
            <span class="article-type type-1 float-none">原创</span>
          [Python人工智能] 三十六.基于Transformer的商品评论情感分析 (2)keras构建多头自注意力(Transformer)模型
        </a> 
      </h4>
      <p class="content">
        本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。
        前一篇文章利用Keras构建深度学习模型并实现了情感分析。这篇文章将介绍
        Transformer基础知识,并通过Keras构建多头自注意力(Transformer)模型,
        实现IMDB电影影评数据情感分析。基础性文章,希望对您有所帮助!...
      </p>
      <div class="info-box d-flex align-content-center">
        <p>
          <span class="date">2022-08-28 18:35:44</span>
          <span class="read-num"><img src="" alt="">2852</span>
          <span class="read-num"><img src="" alt="">4</span>
        </p>

博客均是按照如下div进行布局。

在这里插入图片描述

该部分的关键代码如下:

# coding:utf-8
# By:Eastmount
import requests
from lxml import etree

#设置浏览器代理,它是一个字典
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
        AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
}
num = 2
url = 'https://xxxx/list/' + str(num)

#向服务器发出请求
r = requests.get(url = url, headers = headers).text

#解析DOM树结构
count = 0
html_etree = etree.HTML(r)
div = html_etree.xpath('//*[@class="article-list"]/div')
for item in div:
    #标题
    value = item.xpath('./h4/a/text()')
    title = value[1].strip()
    count += 1
    print(title)
    #时间
    blog_time = item.xpath('./div/p/span[1]/text()')
    print(blog_time)

print("博客总数:", count)

输出结果如下图所示:

在这里插入图片描述

如果需要进一步提取时间,则修改的代码如下所示:

#向服务器发出请求
r = requests.get(url = url, headers = headers).text

#解析DOM树结构
count = 0
title_list = []
time_list = []
year_list = []
html_etree = etree.HTML(r)
div = html_etree.xpath('//*[@class="article-list"]/div')
for item in div:
    #标题
    value = item.xpath('./h4/a/text()')
    title = value[1].strip()
    count += 1
    print(title)
    title_list.append(title)
    
    #日期和时间
    data = item.xpath('./div/p/span[1]/text()')
    data = str(data[0])
    #print(data)

    #提取日期
    year = data.split("-")[0]
    month = data.split("-")[1]
    day = data.split("-")[2].split(" ")[0]
    blog_time = str(year) + "-" + month + "-" + day
    print(blog_time, year)
    time_list.append(blog_time)
    year_list.append(year)
print("博客总数:", count)

输出结果如下图所示,每页共计40篇博客。

在这里插入图片描述


2.翻页采集数据

博客翻页主要通过循环实现,下图展示了翻页的网址。

在这里插入图片描述

此时增加翻页后的代码如下所示:

# coding:utf-8
# By:Eastmount
import requests
from lxml import etree

#设置浏览器代理,它是一个字典
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
        AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
}

num = 1
count = 0
title_list = []
time_list = []
year_list = []
while num<=18:
    url = 'https://xxx/list/' + str(num)

    #向服务器发出请求
    r = requests.get(url = url, headers = headers).text

    #解析DOM树结构
    html_etree = etree.HTML(r)
    div = html_etree.xpath('//*[@class="article-list"]/div')
    for item in div:
        #标题
        value = item.xpath('./h4/a/text()')
        title = value[1].strip()
        count += 1
        print(title)
        title_list.append(title)
        
        #日期和时间
        data = item.xpath('./div/p/span[1]/text()')
        data = str(data[0])

        #提取日期
        year = data.split("-")[0]
        month = data.split("-")[1]
        day = data.split("-")[2].split(" ")[0]
        blog_time = str(year) + "-" + month + "-" + day
        print(blog_time, year)
        time_list.append(blog_time)
        year_list.append(year)
    num += 1
print("博客总数:", count)
print(len(title_list), len(time_list), len(year_list))

输出结果如下图所示,作者711篇博客均采集完成。

在这里插入图片描述


3.数据按年份存储

将所有博客时间存储至列表中,再按年份统计每天的发表次数,最终存储至Json文件,按照如下格式。

在这里插入图片描述

第一步,生成一年的所有日期,以2021年为例。

import arrow
 
#判断闰年并获取一年的总天数
def isLeapYear(years):
    #断言:年份不为整数时抛出异常
    assert isinstance(years, int), "请输入整数年,如 2018"
    #判断是否是闰年
    if ((years % 4 == 0 and years % 100 != 0) or (years % 400 == 0)):  
        days_sum = 366
        return days_sum
    else:
        days_sum = 365
        return days_sum
 
#获取一年的所有日期
def getAllDayPerYear(years):
    start_date = '%s-1-1' % years
    a = 0
    all_date_list = []
    days_sum = isLeapYear(int(years))
    print()
    while a < days_sum:
        b = arrow.get(start_date).shift(days=a).format("YYYY-MM-DD")
        a += 1
        all_date_list.append(b)
    return all_date_list

all_date_list = getAllDayPerYear("2021")
print(all_date_list)

输出结果如下所示:

['2021-01-01', '2021-01-02', '2021-01-03', ...,  '2021-12-29', '2021-12-30', '2021-12-31']

在这里插入图片描述

第二步,遍历一年所有日期发表博客的数量。

#--------------------------------------------------------------------------
#遍历一年所有日期发表博客的数量
k = 0
res = {}
while k<len(all_date_list):
    tt = all_date_list[k]
    res[tt] = 0
    m = 0
    while m<len(time_list):
        tt_lin = time_list[m]
        if tt==tt_lin:
            res[tt] += 1
        m += 1
    k += 1
print("\n------------------------统计结束-------------------------\n")
print(res)
#统计发表博客数
count_blog = 0
for i in res.keys():
    if res[i] > 0:
        count_blog += res[i]
print(count_blog)

输出结果可以看到2021年作者发表博客106篇,并且形成该年的每日发表博客数。

在这里插入图片描述

第三步,存储至Json文件。

#--------------------------------------------------------------------------
#存储至Json文件
import json
print(type(res), len(res))
with open("data-2021.json", "w", encoding='utf-8') as f:
    json.dump(res, f)
    print("文件写入....")

最终生成结果如下图所示:

在这里插入图片描述

在这里插入图片描述


4.完整代码

完整代码如下所示,修改近十年的年份,将生成不同的博客数量。

  • 2022年:61篇
  • 2021年:106篇
  • 2020年:132篇
  • 2019年:87篇
  • 2018年:53篇
  • 2017年:35篇
  • 2016年:48篇
  • 2015年:92篇
  • 2014年:57篇
  • 2013年:34篇
# coding:utf-8
# By:Eastmount
import requests
from lxml import etree

#--------------------------------------------------------------------------
#采集CSDN博客数据
headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
        AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
}

num = 1
count = 0
title_list = []
time_list = []
year_list = []
while num<=18:
    url = 'https://xxx/list/' + str(num)

    #向服务器发出请求
    r = requests.get(url = url, headers = headers).text

    #解析DOM树结构
    html_etree = etree.HTML(r)
    div = html_etree.xpath('//*[@class="article-list"]/div')
    for item in div:
        #标题
        value = item.xpath('./h4/a/text()')
        title = value[1].strip()
        count += 1
        #print(title)
        title_list.append(title)
        
        #日期和时间
        data = item.xpath('./div/p/span[1]/text()')
        data = str(data[0])

        #提取日期
        year = data.split("-")[0]
        month = data.split("-")[1]
        day = data.split("-")[2].split(" ")[0]
        blog_time = str(year) + "-" + month + "-" + day
        #print(blog_time, year)
        time_list.append(blog_time)
        year_list.append(year)
    num += 1
print("博客总数:", count)
print(len(title_list), len(time_list), len(year_list))

#--------------------------------------------------------------------------
#生成一年所有日期
import arrow
 
#判断闰年并获取一年的总天数
def isLeapYear(years):
    #断言:年份不为整数时抛出异常
    assert isinstance(years, int), "请输入整数年,如 2018"
    #判断是否是闰年
    if ((years % 4 == 0 and years % 100 != 0) or (years % 400 == 0)):  
        days_sum = 366
        return days_sum
    else:
        days_sum = 365
        return days_sum
 
#获取一年的所有日期
def getAllDayPerYear(years):
    start_date = '%s-1-1' % years
    a = 0
    all_date_list = []
    days_sum = isLeapYear(int(years))
    print()
    while a < days_sum:
        b = arrow.get(start_date).shift(days=a).format("YYYY-MM-DD")
        a += 1
        all_date_list.append(b)
    return all_date_list

all_date_list = getAllDayPerYear("2021")
print(all_date_list)

#--------------------------------------------------------------------------
#遍历一年所有日期发表博客的数量
k = 0
res = {}
while k<len(all_date_list):
    tt = all_date_list[k]
    res[tt] = 0
    m = 0
    while m<len(time_list):
        tt_lin = time_list[m]
        if tt==tt_lin:
            res[tt] += 1
        m += 1
    k += 1
print("\n------------------------统计结束-------------------------\n")
print(res)
#统计发表博客数
count_blog = 0
for i in res.keys():
    if res[i] > 0:
        count_blog += res[i]
print(count_blog)

#--------------------------------------------------------------------------
#存储至Json文件
import json
print(type(res), len(res))
with open("data-2021.json", "w", encoding='utf-8') as f:
    json.dump(res, f)
    print("文件写入....")

二.D3可视化分析

可视化主要通过D3实现,推荐读者学习D3可视化知识。本文参考了zjw666作者的代码。

  • https://github.com/d3
  • https://github.com/zjw666/D3_demo

读者可以在我的github下载本文的所有代码及数据。

  • https://github.com/eastmountyxz/Python-zero2one

该项目代码框架如下:

在这里插入图片描述

部分的关键代码如下:

index.html

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>calendar</title>
    <link rel="stylesheet" type="text/css" href="./index.css" />
</head>
<body>
<script src="./lib/d3.js"></script>
<script src="./index.js" type='module'></script>
</body>
</html>

index.css

.box{
    margin: 10% auto;
    width: 1500px;
    height: 400px;
    background-color: #fff;
}

.title{
    font-size: 20px;
}

.text{
    font-size: 10px;
}

.label{
    font-size: 12px;
}

chart-headmap.js

export default class Chart {
    constructor(){
        this._width = 1300;
        this._height = 400;
        this._margins = {top:30, left:30, right:30, bottom:30};
        this._data = [];
        this._scaleX = null;
        this._scaleY = null;
        this._colors = d3.scaleOrdinal(d3.schemeCategory10);
        this._box = null;
        this._svg = null;
        this._body = null;
        this._padding = {top:10, left:10, right:10, bottom:10};
    }

    width(w){
        if (arguments.length === 0) return this._width;
        this._width = w;
        return this;
    }

    height(h){
        if (arguments.length === 0) return this._height;
        this._height = h;
        return this;
    }

    margins(m){
        if (arguments.length === 0) return this._margins;
        this._margins = m;
        return this;
    }

    data(d){
        if (arguments.length === 0) return this._data;
        this._data = d;
        return this;
    }

    scaleX(x){
        if (arguments.length === 0) return this._scaleX;
        this._scaleX = x;
        return this;
    }

    scaleY(y){
        if (arguments.length === 0) return this._scaleY;
        this._scaleY = y;
        return this;
    }

    svg(s){
        if (arguments.length === 0) return this._svg;
        this._svg = s;
        return this;
    }

    body(b){
        if (arguments.length === 0) return this._body;
        this._body = b;
        return this;
    }

    box(b){
        if (arguments.length === 0) return this._box;
        this._box = b;
        return this;
    }

    getBodyWidth(){
        let width = this._width - this._margins.left - this._margins.right;
        return width > 0 ? width : 0;
    }

    getBodyHeight(){
        let height = this._height - this._margins.top - this._margins.bottom;
        return height > 0 ? height : 0;
    }

    padding(p){
        if (arguments.length === 0) return this._padding;
        this._padding = p;
        return this;
    }

    defineBodyClip(){

        this._svg.append('defs')
                 .append('clipPath')
                 .attr('id', 'clip')
                 .append('rect')
                 .attr('width', this.getBodyWidth() + this._padding.left + this._padding.right)
                 .attr('height', this.getBodyHeight() + this._padding.top  + this._padding.bottom)
                 .attr('x', -this._padding.left)
                 .attr('y', -this._padding.top);
    }

    render(){
        return this;
    }

    bodyX(){
        return this._margins.left;

    }

    bodyY(){
        return this._margins.top;
    }

    renderBody(){
        if (!this._body){
            this._body = this._svg.append('g')
                            .attr('class', 'body')
                            .attr('transform', 'translate(' + this.bodyX() + ',' + this.bodyY() + ')')
                            .attr('clip-path', "url(#clip)");
        }

        this.render();
    }

    renderChart(){
        if (!this._box){
            this._box = d3.select('body')
                            .append('div')
                            .attr('class','box');
        }

        if (!this._svg){
            this._svg = this._box.append('svg')
                            .attr('width', this._width)
                            .attr('height', this._height);
        }

        this.defineBodyClip();

        this.renderBody();
    }
}

最重要的代码是index.js,如下所示:

index.js

import Chart from "./chart-headmap.js";

d3.json('./data/data-2021.json').then(function(data){

    /* ----------------------------配置参数------------------------  */
    const chart = new Chart();
    const config = {
        margins: {top: 80, left: 50, bottom: 50, right: 50},
        textColor: 'black',
        title: '2021年博客创作热力图(106篇)',
        hoverColor: 'red',
        startTime: '2021-01-01',
        endTime: '2021-12-31',
        cellWidth: 20,
        cellHeight: 20,
        cellPadding: 1,
        cellColor1: '#F5F5F5',
        cellColor2: 'green',
        lineColor: 'yellow',
        lineWidth: 2
    }

    chart.margins(config.margins);

    /* ----------------------------初始化常量------------------------  */
    const startTime = new Date(config.startTime);
    const endTime = new Date(config.endTime);
    const widthOffset = config.cellWidth + config.cellPadding;
    const heightOffset = config.cellHeight + config.cellPadding;

    /* ----------------------------颜色转换------------------------  */
    chart.scaleColor = d3.scaleLinear()
                            .domain([0, d3.max(Object.values(data))])
                            .range([config.cellColor1, config.cellColor2]);

    /* ----------------------------渲染矩形------------------------  */
    chart.renderRect = function(){
        let currentYear, currentMonth;
        let yearGroup, monthGroup;
        const initDay = startTime.getDay();
        let currentDay = initDay;
        const totalDays = getTotalDays(startTime, endTime) + initDay;	
	console.info(totalDays);

        const mainBody = chart.body()
                                .append('g')
                                .attr('class', 'date')
                                .attr('transform', 'translate(' + 35 + ',' + 50 + ')')

        while(currentDay <= totalDays){
            let currentDate = getDate(startTime, currentDay).split('-');

            if(!currentYear || currentDate[0] !== currentYear){
                currentYear = currentDate[0];

                yearGroup = mainBody
                                .append('g')
                                .attr('class', 'year ' + currentYear);
            }

            if (!currentMonth || currentDate[1] !== currentMonth){
                currentMonth = currentDate[1];

                monthGroup = yearGroup.append('g').attr('class', 'month ' + currentMonth);
            }

            monthGroup
                 .append('g')
                 .attr('class', 'g ' + currentDate.join('-'))
                 .datum(currentDate.join('-'))
                 .append('rect')
                 .attr('width', config.cellWidth)
                 .attr('height', config.cellHeight)
                 .attr('x', Math.floor(currentDay / 7) * widthOffset)
                 .attr('y', currentDay % 7 * heightOffset);

            currentDay++;
        }

        d3.selectAll('.g')
            .each(function(d){
                d3.select(this)
                    .attr('fill', chart.scaleColor(data[d] || 0))
                    .datum({time: d, value: data[d] || 0});
            });

        function getTotalDays(startTime, endTime){
            return Math.floor((endTime.getTime() - startTime.getTime()) / 86400000);
        }

        function getDate(startTime, day){
            const date =  new Date(startTime.getTime() + 86400000 * (day - initDay));
            return d3.timeFormat("%Y-%m-%d")(date);
        }
    }

    /* ----------------------------渲染分隔线------------------------  */
    chart.renderLine = function(){
        const initDay = startTime.getDay();
        const days = [initDay-1];
        const linePaths = getLinePath();

        d3.select('.date')
                .append('g')
                .attr('class', 'lines')
                .selectAll('path')
                .data(linePaths)
                .enter()
                .append('path')
                .attr('stroke', config.lineColor)
                .attr('stroke-width', config.lineWidth)
                .attr('fill', 'none')
                .attr('d', (d) => d);

        function getLinePath(){
            const paths = [];

            d3.selectAll('.month')
                .each(function(d,i){
                    days[i+1] = days[i] + this.childNodes.length;
                });

            days.forEach((day,i) => {
                let path = 'M';
                let weekDay = day < 0 ? 6 : day % 7;

                if (weekDay !== 6) {
                    path += Math.floor(day / 7) * widthOffset + ' ' + 7 * heightOffset;
                    path +=  ' l' + '0' + ' ' + (weekDay - 6) * heightOffset;
                    path += ' l' + widthOffset + ' ' + '0';
                    path += ' l' + '0' + ' ' + (-weekDay - 1) * heightOffset;
                } else {
                    path += (Math.floor(day / 7) + 1) * widthOffset + ' ' + 7 * heightOffset;
                    path +=  ' l' + '0' + ' ' + (-7) * heightOffset;
                }

                paths.push(path);
            });

            return paths;
        }

    }

    /* ----------------------------渲染文本标签------------------------ */
    chart.renderText = function(){
        let week = ['Sun', 'Mon', 'Tue', 'Wed', 'Tur', 'Fri', 'Sat'];

        d3.select('.year')
            .append('g')
            .attr('class', 'week')
            .selectAll('.label')
            .data(week)
            .enter()
            .append('text')
            .attr('class', 'label')
            .attr('x', -40)
            .attr('y', heightOffset/2)
            .attr('dy', (d,i) => i * heightOffset + 4)
            .text((d)=>d);

        let months = d3.timeMonth.range(new Date(startTime.getFullYear(), startTime.getMonth(), startTime.getDate()), new Date(endTime.getFullYear(), endTime.getMonth(), endTime.getDate()));

        months = months.map((d) => d3.timeFormat("%b")(d));

        d3.select('.year')
            .append('g')
            .attr('class', 'month-label')
            .selectAll('text')
            .data(months)
            .enter()
            .append('text')
            .attr('x', (d,i) => i*widthOffset*4.25 + widthOffset*2)
            .attr('y', -10)
            .text((d) => d)

    }

    /* ----------------------------渲染图标题------------------------  */
    chart.renderTitle = function(){

        chart.svg().append('text')
                .classed('title', true)
                .attr('x', chart.width()/2)
                .attr('y', 0)
                .attr('dy', '2em')
                .text(config.title)
                .attr('fill', config.textColor)
                .attr('text-anchor', 'middle')
                .attr('stroke', config.textColor);

    }

    /* ----------------------------绑定鼠标交互事件------------------------  */
    chart.addMouseOn = function(){
        //防抖函数
        function debounce(fn, time){
            let timeId = null;
            return function(){
                const context = this;
                const event = d3.event;
                timeId && clearTimeout(timeId)
                timeId = setTimeout(function(){
                    d3.event = event;
                    fn.apply(context, arguments);
                }, time);
            }
        }

        d3.selectAll('.g')
            .on('mouseenter', function(d){
                const e = d3.event;
                const position = d3.mouse(chart.svg().node());

                d3.select(e.target)
                    .attr('fill', config.hoverColor);

                chart.svg()
                    .append('text')
                    .classed('tip', true)
                    .attr('x', position[0]+5)
                    .attr('y', position[1])
                    .attr('fill', config.textColor)
                    .text(d.time);
            })
            .on('mouseleave', function(d){
                const e = d3.event;

                d3.select(e.target)
                    .attr('fill', chart.scaleColor(d.value));

                d3.select('.tip').remove();
            })
            .on('mousemove', debounce(function(){
                    const position = d3.mouse(chart.svg().node());
                    d3.select('.tip')
                    .attr('x', position[0]+5)
                    .attr('y', position[1]-5);
                }, 6)
            );
    }

    chart.render = function(){

        chart.renderTitle();

        chart.renderRect();

        chart.renderLine();

        chart.renderText();

        chart.addMouseOn();

    }

    chart.renderChart();
});

由于该网页需要通过HTTP访问,因此需要搭建服务器访问。否则可能报错:

Access to script at 'file:///C:/Users/xiuzhang/Desktop/headmap_date/index.js

VS Code在扩展Extensions中搜索Live Server,选择并安装它。

在这里插入图片描述

打开网站文件夹,选择具体的页面,右击文件选择 Open with Live Server。

在这里插入图片描述

服务器就启动了,所选择浏览的页面也会在浏览器打开

  • http://127.0.0.1:5500/index.html

在这里插入图片描述

在这里插入图片描述


三.CSDN近十年内容分析

最后按年爬取数据并分别显示即可,修改关键代码为年份。

在这里插入图片描述

在这里插入图片描述

最终显示结果如下图所示:

在这里插入图片描述


四.总结

写到这里,这篇文章就介绍结束。最后,希望这篇基础性文章对您有所帮助,如果文章中存在错误或不足之处,还请海涵~作为人工智能的菜鸟,我希望自己能不断进步并深入,后续将它应用于图像识别、网络安全、对抗样本等领域,指导大家撰写简单的学术论文,一起加油!

转眼就过年了,2022年简单总结:很多遗憾,很多不足,勉强算是分秒必争,只争朝夕,但愧对家人,陪伴太少,论文、科研、分享和家庭都做得不好,这一年勉强给个65分吧。最最感恩的永远是女神,回家的感觉真好,平平淡淡,温温馨馨,虽然这辈子科研、事业和职称上没有太大的追求,和大佬们的差距如鸿沟,但能做自己喜欢的事,爱自己喜欢的人,每天前进一小步(人生勿比),一家人健康幸福,足矣。提前祝大家新春快乐,阖家幸福。小珞珞是真的逗,陪伴的感觉真好,女神是真的好,爱你们喔,晚安娜继续加油!

在这里插入图片描述

(By:Eastmount 2023-01-29 夜于贵阳 http://blog.csdn.net/eastmount/ )


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/185388.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于对公司做项目的一些想法

项目管理法则里面最重要的是如下的三角形&#xff1a;基于一定的范围、合理的时间和足够的成本下实现项目完成&#xff0c;并保证质量。项目中最重要的是质量&#xff0c;质量不行就意味着项目失败&#xff0c;请参考大跃进时期的大炼钢铁&#xff08;多快好省大炼钢&#xff0…

是什么影响了 MySQL 索引 B + 树的高度?

提到 MySQL&#xff0c;想必大多后端同学都不会陌生&#xff0c;提到 B 树&#xff0c;想必还是有很大部分都知道 InnoDB 引擎的索引实现&#xff0c;利用了 B 树的数据结构。 那 InnoDB 的一棵 B 树可以存放多少行数据&#xff1f;它又有多高呢&#xff1f; 到底是哪些因…

WebRTC → 信令服务器

相关简介 信令&#xff1a;驱动系统运转。控制各个模块的前后调用关系;业务不同&#xff0c;逻辑不同&#xff0c;信令也会千差万别 要实现一对一通信&#xff0c;驱动系统的核心就是信令。信令控制着系统各个模块之间的前后调用关系&#xff0c;比如当收到用户成功加入房间后…

3D模型在线查看利器【多种格式】

BimAnt 3DViewer网站可以 打开多种 3D 文件格式并在你的浏览器中可视化展示3D模型&#xff0c;支持 obj、3ds、stl、ply、gltf、glb、off、 3dm、fbx 等等。 1、支持的3D模型格式 BimAnt 3DViewer网站支持多种文件格式的导入和导出。 如果文件格式有文本和二进制版本&#x…

Minecraft 1.19.2 Fabric模组开发 09.Mixin

我们今天用mixin在1.19.2 fabric中实现一个望远镜 1.由于fabric已经自动配置好了mixin&#xff0c;所以我们无需配置mixin&#xff0c;先在ItemInit中新建一个我们的望远镜物品&#xff1a; ItemInit.java public static final Item BIRDWATCHER registerItem("birdwat…

Smart-doc的脚本生成在线文档(精简官方文档描述)

Smart-doc优点&#xff1a; 无侵入的接口文档、在线文档生成器。三种生成文档方式。对于程序代码开发中只需要加注释&#xff08;符合一定的语法&#xff0c;五分钟可掌握&#xff09;就能生成在线文档。可以支持c、java、php、node等等常见的主流语言。 如何使用&#xff1a; …

47.Isaac教程--ORB

ORB ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录ORBGem 提供的类型关键点描述符如何使用 Gem&#xff08;界面&#xff09;构建包Isaac Codelets示例应用程序主机设备嵌入式 Jetson 设备这个 gem 提供了一个特征检测器和描述符提取器…

2011年专业408算法题

文章目录0 结果1 题目2 思路2.1 思路1&#xff08;暴力解&#xff1a;排序&#xff09;2.2 思路2&#xff08;较优解&#xff1a;归并合并数组&#xff09;2.3 思路3&#xff08;较优解&#xff1a;数组指针后移&#xff09;2.4 思路4&#xff08;最优解&#xff1a;两个数组的…

webpack是如何进行依赖图谱收集的?

我自己学习webpack已有很长时间了&#xff0c;但是经常会遇到这样的问题: 可以熟练配置webpack的一些常用配置&#xff0c;但是对一些不常见的api或者概念总是云里雾里。因此&#xff0c;对着网上资料手写了一个简易版的webpack&#xff0c;现在对其中的依赖图谱收集部分进行梳…

Numpy(7)—字节交换、NumPy 副本和视图、深浅拷贝、矩阵库、NumPy 线性代数、NumPy IO(读写)、NumPy Matplotlib

1.字节交换 import numpy as npA np.array([1, 256, 8755], dtypenp.int16) print(A) print(list(map(hex, A))) print(A.byteswap(inplaceTrue)) print(list(map(hex, A)))2.NumPy 副本和视图 副本是一个数据的完整的拷贝&#xff0c;如果我们对副本进行修改&#xff0c;它不…

【MyBatis 持久层框架】Mapper代理开发详细解读

文章目录1. 前言2. Mapper 代理开发3. 过程剖析4. 总结1. 前言 前面在 MyBatis 快速入门篇中&#xff0c;我们使用了 MyBatis 原生的开发方式操作数据库&#xff0c;解决了 JDBC 操作数据库时的硬编码和操作繁琐的问题。实际上&#xff0c;在 Java 项目中&#xff0c;我们更常…

python3——函数

目录 一、函数定义 二、函数调用 1.打印Hello World 2.判断最大值 3.计算矩形面积 4.help说明文档 三、参数传递 (一)位置参数 (二)关键字参数 (三)默认参数(缺省参数) (四)可变参数(收集参数) 1.位置可变参数(接收所有的位置参数&#xff0c;返回一个元组) 2.关键…

高通开发系列 - MSM8909 lk aboot阶段点灯操作

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 第一种LK提供的接口实现第二种直接操作寄存器这篇文章之前请参考下:高通开发系列 - MSM8909指示灯操作 在LK中点灯有两种方式,一种…

JAVA-定位排查bug

在开发过程中难免会遇到bug&#xff0c;理解bug的含义&#xff0c;定位bug的位置&#xff0c;对于解决bug至关重要&#xff01;掌握高效的排错技巧&#xff0c;对于程序员来说必不可少。 目录 一、错误异常的分类 二、常见报错信息及原因&#xff08;持续更新中&#xff09;…

域内权限维持:AdminSDHolder

01、简介 AdminSDHolder是一个特殊的AD容器&#xff0c;通常作为某些特权组成员的对象的安全模板。Active Directory将采用AdminSDHolder对象的ACL并定期将其应用于所有受保护的AD账户和组&#xff0c;以防止意外和无意的修改并确保对这些对象的访问是安全的。如果攻击者能完全…

Flex布局和主要属性用法详解

目录 前言 一个小例子 基本概念&#xff1a; 设置在主轴上的排列方式 设置在侧轴上的排列方式 更换主轴和侧轴方向 换行 align-content属性 元素&#xff08;子容器&#xff09;的相关属性 flex-basis flex-grow flex-shrink属性 flex属性 前言 flex布局是继标准…

JDBC-Statement

1.Statement执行静态sql语句&#xff08;“字符串”&#xff09; 返回结果 2.&#xff01;实际工作一般用PreparedStatement来进行sql语句的执行&#xff0c;因为sql注入的风险 3and4.SQl注入就是Statement没有检查我们输入sql语句&#xff0c;一些别有用心的可能写一些危害数据…

智能手表主控芯片盘点,智能手表GUI,智能手表市场

聚焦&#xff1a;无线连接芯片&#xff0c;市场&#xff0c;技术 祝大家新年快乐&#xff0c;开工大吉&#xff01;趁寒假简单梳理了下智能手表应用&#xff0c;做个分享&#xff0c;不对的地方欢迎交流指正&#xff1b; 01 市场容量&#xff0c;分类及拓扑 2个数据供参考 一个…

C++ dll、lib 的定义以及引用,

最近在研究socket&#xff0c;发现socket程序要依赖ws2_32.dll,涉及到动态链接库&#xff0c;有点懵&#xff0c;上网恶补了一下链接库的知识&#xff0c;最后总结出这么一篇文章 链接库分为两种&#xff1a;动态链接库(dll) 和静态链接库(lib) 动态链接库 &#xff1a; 动态链…

【C++】C++11语法解析

&#x1f308;欢迎来到C专栏~~C11 (꒪ꇴ꒪(꒪ꇴ꒪ )&#x1f423;,我是Scort目前状态&#xff1a;大三非科班啃C中&#x1f30d;博客主页&#xff1a;张小姐的猫~江湖背景快上车&#x1f698;&#xff0c;握好方向盘跟我有一起打天下嘞&#xff01;送给自己的一句鸡汤&#x1f…