【网站建设】”热力图“功能实现
实现下面这种类似 GitHub 贡献热力图的「创作指数日历」

修改子主题functions.php代码
<?php
// 1. 数据获取:当前自然年文章
function contribution_heatmap_get_data() {
// 统一使用WordPress时区获取年份
$current_year = wp_date('Y');
// 优化WP_Query参数:解决查询遗漏+边界模糊问题
$args = array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => -1,
'fields' => 'post_date',
'no_found_rows' => true,
'ignore_sticky_posts' => true,
'date_query' => array(
array(
'column' => 'post_date',
'after' => $current_year . '-01-01 00:00:00',
'before' => $current_year . '-12-31 23:59:59',
'inclusive' => true,
),
),
);
// 执行查询
$query = new WP_Query($args);
$contribution_data = array();
// 日志:打印查询总数
error_log('=== 热力图统计开始 ===');
error_log('当前统计年份:' . $current_year);
error_log('本次查询到文章总数:' . count($query->posts));
foreach ($query->posts as $post) {
// 1. 原始日期(数据库里的UTC时区datetime字符串)
$raw_post_date = $post->post_date;
// 2. 关键:直接提取前10位(YYYY-MM-DD),跳过时区转换,避免跨天
if (strlen($raw_post_date) < 10) {
error_log('警告:原始日期 ' . $raw_post_date . ' 格式异常,已跳过');
continue;
}
$formatted_date = substr($raw_post_date, 0, 10);
// 3. 日志:打印原始日期、提取后的统计日期(方便验证)
error_log('原始UTC日期:' . $raw_post_date . ' | 统计日期:' . $formatted_date);
// 4. 统计每日文章数(无数量上限)
if (isset($contribution_data[$formatted_date])) {
$contribution_data[$formatted_date]++;
} else {
$contribution_data[$formatted_date] = 1;
}
}
// 日志:打印最终统计结果
error_log('最终日期统计结果:' . print_r($contribution_data, true));
error_log('=== 热力图统计结束 ===' . "\n");
wp_reset_postdata();
return $contribution_data;
}
// 2. 短代码:新增鼠标悬浮tooltip,区分过去/未来日期
function contribution_heatmap_shortcode() {
ob_start();
$contribution_data = contribution_heatmap_get_data();
$json_data = json_encode($contribution_data);
$current_year = date('Y');
?>
<div id="contribution-wrapper" style="width: 800px; margin: 20px 0;">
<!-- 右上角4级色块图例 -->
<div id="heatmap-legend" style="display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-bottom: 10px;">
<span style="font-size: 0.9rem; color: #666;">不活跃</span>
<div style="width: 20px; height: 20px; background: #ffffff; border: 1px solid #e0e0e0;"></div>
<div style="width: 20px; height: 20px; background: #c8e6c9; border: 1px solid #e0e0e0;"></div>
<div style="width: 20px; height: 20px; background: #66bb6a; border: 1px solid #e0e0e0;"></div>
<div style="width: 20px; height: 20px; background: #2e7d32; border: 1px solid #e0e0e0;"></div>
<span style="font-size: 0.9rem; color: #666;">活跃</span>
</div>
<!-- 热力图容器 -->
<div id="contribution-heatmap" style="width: 100%; height: 250px; padding: 20px; border: 1px solid #ddd;">
<div id="full-calendar" style="width: 100%; height: 100%;"></div>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/d3/7.8.5/d3.min.js"></script>
<script>
window.onload = function() {
if (!d3) {
document.getElementById("contribution-heatmap").innerHTML = "D3加载失败";
return;
}
const contributionData = <?php echo $json_data; ?>;
const startDate = new Date("<?php echo $current_year; ?>", 0, 1);
const endDate = new Date("<?php echo $current_year; ?>", 11, 31);
const today = new Date(); // 获取当前日期(用于区分过去/未来)
today.setHours(0, 0, 0, 0); // 重置时间为0点,避免时分秒干扰日期判断
const dateRange = d3.timeDays(startDate, endDate);
const cellSize = 14;
const margin = { top: 20, right: 10, bottom: 30, left: 10 };
// ******** 新增1:创建tooltip元素(默认隐藏) ********
const tooltip = d3.select("body").append("div")
.attr("class", "heatmap-tooltip")
.style("position", "absolute")
.style("background-color", "#ffffff")
.style("border", "1px solid #e0e0e0")
.style("border-radius", "4px")
.style("padding", "6px 10px")
.style("font-size", "0.9rem")
.style("box-shadow", "0 2px 5px rgba(0, 0, 0, 0.1)")
.style("opacity", 0) // 默认透明隐藏
.style("pointer-events", "none") // 不阻挡鼠标事件
.style("z-index", "9999"); // 确保在最上层
// 创建SVG容器
const svg = d3.select("#full-calendar")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
// ******** 新增2:日期格式化函数(2026-01-26 Mon) ********
const dateFormatter = d3.timeFormat("%Y-%m-%d %a"); // %Y=年, %m=月, %d=日, %a=星期缩写(Mon/Tue等)
// 热力图颜色:与4级图例对应
const cells = svg.selectAll(".cell")
.data(dateRange)
.enter().append("rect")
.attr("class", "cell")
.attr("width", cellSize - 1)
.attr("height", cellSize - 1)
.attr("x", d => d3.timeWeek.count(d3.timeYear(d), d) * cellSize)
.attr("y", d => d3.timeFormat("%w")(d) * cellSize)
.attr("fill", d => {
const dateStr = d3.timeFormat("%Y-%m-%d")(d);
if (contributionData[dateStr] >= 3) return "#2e7d32"; // 等级4(活跃)
if (contributionData[dateStr] >= 2) return "#66bb6a"; // 等级3
if (contributionData[dateStr] >= 1) return "#c8e6c9"; // 等级2
return "#ffffff"; // 等级1(不活跃)
})
.attr("stroke", "#f0f0f0");
// ******** 新增3:鼠标悬浮事件(区分过去/未来日期) ********
cells
// 鼠标移入:显示tooltip
.on("mouseover", function(event, d) {
// 1. 格式化日期为「2026-01-26 Mon」格式
const formattedDate = dateFormatter(d);
// 2. 重置当前日期的时分秒(避免时分秒干扰判断)
const cellDate = new Date(d);
cellDate.setHours(0, 0, 0, 0);
// 3. 区分「过去/当前日期」和「未来日期」
let tooltipContent = "";
if (cellDate <= today) {
// 过去/当前日期:显示「日期 + 文章数」
const dateStr = d3.timeFormat("%Y-%m-%d")(d);
const articleCount = contributionData[dateStr] || 0;
tooltipContent = `${formattedDate}<br>创作文章:${articleCount} 篇`;
} else {
// 未来日期:只显示「日期」
tooltipContent = formattedDate;
}
// 4. 显示tooltip并调整位置(跟随鼠标)
tooltip.html(tooltipContent)
.style("left", (event.pageX + 10) + "px") // 鼠标右侧10px
.style("top", (event.pageY - 20) + "px") // 鼠标上方20px
.transition() // 淡入效果
.duration(200)
.style("opacity", 0.95);
})
// 鼠标移出:隐藏tooltip
.on("mouseout", function() {
tooltip.transition() // 淡出效果
.duration(500)
.style("opacity", 0);
});
// 月份标签
const months = d3.timeMonths(startDate, endDate);
svg.selectAll(".month-label")
.data(months)
.enter().append("text")
.attr("class", "month-label")
.attr("x", d => d3.timeWeek.count(d3.timeYear(d), d) * cellSize)
.attr("y", 7 * cellSize + 20)
.attr("font-size", "11px")
.attr("fill", "#666")
.text(d => d3.timeFormat("%b")(d));
};
</script>
<style>
.cell:hover {
stroke: #333 !important;
stroke-width: 1px !important;
}
</style>
<?php
return ob_get_clean();
}
add_shortcode('contribution_heatmap', 'contribution_heatmap_shortcode');
?>
验证效果是否生效
- 新建 / 编辑页面,在编辑器中输入短代码
[contribution_heatmap]
- 点击「发布 / 更新」,前台访问页面(按
Ctrl+F5强制清除缓存)。 - 此时能看到:
- 一个完整的 GitHub 风格日历热力图,横向排列月份,纵向排列星期。
- 有文章的日期显示对应绿色(文章数越多颜色越深),无文章的日期显示白色。
- 鼠标悬浮在方块上,会显示具体日期和创作文章数。
- 右侧有活跃度颜色梯度说明,布局整齐美观。
不活跃
活跃



