Siricee / hexo-theme-Chic

An elegant, powerful, easy-to-read Hexo theme.
https://siricee.github.io/hexo-theme-Chic/
Other
882 stars 176 forks source link

实现热力图功能 #146

Open greendolphindance opened 7 months ago

greendolphindance commented 7 months ago

感觉加了很多花里胡哨的东西已经偏离Siricee老师的本意了……不过还是想分享一下,万一有人想做呢。

这里是原文

部分代码解释见原文。修改后的profile.ejs完整代码:

<div class="container profile-container">
    <div class="intro">
        <div class="avatar">
            <a href="<%- url_for(theme.nav.Posts) %>"><img src="<%- url_for(theme.avatar) %>"></a>
        </div>
        <div id="heatmap-container"></div>

        <script src="https://d3js.org/d3.v5.min.js"></script>

        <script>
            document.addEventListener("DOMContentLoaded", function () {
                function getDateBefore(days) {
                    var currentDate = new Date();
                    currentDate.setDate(currentDate.getDate() - days);
                    var year = currentDate.getFullYear();
                    var month = String(currentDate.getMonth() + 1).padStart(2, '0');
                    var day = String(currentDate.getDate()).padStart(2, '0');
                    return `${year}-${month}-${day}`;
                }
            <%
                    function convertWordCount(wordCountString) {
                        if (!wordCountString) {
                            return 0;
                        }

                        // Convert to string and check if the word count string contains 'k'
                        var lowerCaseString = String(wordCountString).toLowerCase();

                        if (lowerCaseString.includes('k')) {
                            return parseFloat(lowerCaseString) * 1000;
                        } else {
                            return parseFloat(lowerCaseString);
                        }
                    }
                    %>

var data = [
    <% site.posts.each(function (post) { %>
                        {
                            date: "<%= post.date.format('YYYY-MM-DD') %>",
                            word_count: <%= convertWordCount(getWordCount(post.content)) %>,
                    link: "<%= url_for(post.path) %>"
        },
    <% }); %>
];

            var margin = { top: 20, right: 20, bottom: 20, left: 20 };
            var containerWidth = 600;
            var cellSize = Math.min((containerWidth - margin.left - margin.right) / 45, (containerWidth - margin.top - margin.bottom) / 8);
            var width = cellSize * 45;
            var height = cellSize * 8;

            var svg = d3
                .select("#heatmap-container")
                .append("svg")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top + margin.bottom)
                .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

            var xScale = d3.scaleBand().range([width, 0]).padding(0.1);
            var yScale = d3.scaleBand().range([height, 0]).padding(0.1);

            var exponent = 0.3; // Adjust the exponent as needed
            var colorScale = d3.scaleSequential(d3.interpolate("lightblue", "#2d96bd"))
                .domain([1, Math.pow(d3.max(data, function (d) { return d.word_count; }), exponent)]);

            xScale.domain(d3.range(45));
            yScale.domain(d3.range(8));

            // 在渲染每个格子时设置渐变色的值
            var cells = svg.selectAll(".cell")
                .data(d3.cross(d3.range(8), d3.range(45)))
                .enter().append("a")
                .attr("href", function (d) {
                    var currentDate = getDateBefore(d[0] * 45 + d[1]);
                    var correspondingData = data.find(entry => entry.date === currentDate);
                    return correspondingData ? correspondingData.link : "#"; // 设置链接,如果没有链接就是 "#",即当前页面
                })
                .append("rect")
                .attr("class", function (d) {
                    var currentDate = getDateBefore(d[0] * 45 + d[1]);
                    var correspondingData = data.find(entry => entry.date === currentDate);
                    return "cell" + (correspondingData && correspondingData.word_count > 0 ? " blue" : "");
                })

                .attr("x", function (d) { return xScale(d[1]); })
                .attr("y", function (d) { return yScale(d[0]); })
                .attr("width", cellSize)
                .attr("height", cellSize)
                .style("fill", function (d) {
                    var currentDate = getDateBefore(d[0] * 45 + d[1]);
                    var correspondingData = data.find(entry => entry.date === currentDate);
                    return correspondingData ? colorScale(Math.pow(correspondingData.word_count, exponent)) : "#ccc";
                })
                .attr("rx", 4)
                .attr("ry", 4)
                .attr("data-word_count", function (d) {
                    var currentDate = getDateBefore(d[0] * 45 + d[1]);
                    var correspondingData = data.find(entry => entry.date === currentDate);
                    return correspondingData ? correspondingData.word_count : null;
                })
                .on("click", function (event, d) {
                    var currentDate = getDateBefore(d[0] * 45 + d[1]);
                    var correspondingData = data.find(entry => entry.date === currentDate);
                    if (correspondingData && correspondingData.link) {
                        window.location.href = correspondingData.link;
                    }
                });

            function updateCellStyles() {
                var isDarkTheme = document.body.classList.contains("dark-theme");
                cells.style("stroke", isDarkTheme ? "#292a2d" : "#fff")
                    .style("stroke-width", "1px");
            }

            updateCellStyles();

            document.body.addEventListener("themechange", function () {
                updateCellStyles();
            });
        });
        </script>

        <style>
            @media (max-width: 767px) {
                #heatmap-container {
                    display: none;
                }
            }

            /* 在桌面端时隐藏 .avatar */
            @media (min-width: 768px) {
                .avatar {
                    display: none;
                }
            }

            .cell {
                stroke: #fff !important;
                stroke-width: 1px;
                fill: #ccc;
                cursor: default;
                /* 添加这一行 */
            }

            .dark-theme .cell {
                stroke: #292a2d !important;
                stroke-width: 1px;
                fill: #a9a9b3;
                cursor: default;
                /* 添加这一行 */
            }

            .blue {
                cursor: pointer;
            }

            .dark-theme .blue {
                cursor: pointer;
            }
        </style>

        <div class="nickname"><%- theme.nickname %></div>
        <div class="description"><%- markdown(theme.description) %></div>
        <div class="links">
            <% if (theme.links !==undefined) { %>
                <% for (var key in theme.links){ %>
                    <a class="link-item" title="<%- key %>" href="<%= theme.links[key] %>">
                        <% if(theme.links_text_enable) { %>
                            <%= key %>
                                <% } %>
                                    <% if(theme.links_icon_enable){ %>
                                        <i class="iconfont icon-<%- key.toLowerCase() %>"></i>
                                        <% } %>
                    </a>
                    <% } %>
                        <% } %>
        </div>
    </div>
</div>

然后我在想,是不是应该把CSS和javascript代码另外找个文档放着?全写进ejs里仿佛不太好?

Siricee commented 7 months ago

挺好的 这图我建议改为一列 7 个单元格,这样还可以形成周的属性

greendolphindance commented 7 months ago

挺好的 这图我建议改为一列 7 个单元格,这样还可以形成周的属性

有道理!我回头改改

greendolphindance commented 7 months ago

挺好的 这图我建议改为一列 7 个单元格,这样还可以形成周的属性

老师求助 > < 我把行数改为7了,但是文章的排列顺序是先从右往左,再从下往上排序的,但我需要先从下往上,再从右往左排列,以使得一周的文章在同一列的7个格子上。由于我不会写代码,遂尝试调教GPT,但是它太蠢了,调了半天也没弄好。

我现在的代码:

<div class="container profile-container">
    <div class="intro">
        <div class="avatar">
            <a href="<%- url_for(theme.nav.Posts) %>"><img src="<%- url_for(theme.avatar) %>"></a>
        </div>
        <div id="heatmap-container">
            <div id="tooltip"></div>
        </div>

        <script src="https://d3js.org/d3.v5.min.js"></script>

        <script>
            document.addEventListener("DOMContentLoaded", function () {
                function getDateBefore(days) {
                    var currentDate = new Date();
                    currentDate.setDate(currentDate.getDate() - days);
                    var year = currentDate.getFullYear();
                    var month = String(currentDate.getMonth() + 1).padStart(2, '0');
                    var day = String(currentDate.getDate()).padStart(2, '0');
                    return `${year}-${month}-${day}`;
                }

                <%
                    function convertWordCount(wordCountString) {
                        if (!wordCountString) {
                            return 0;
                        }

                        // Convert to string and check if the word count string contains 'k'
                        var lowerCaseString = String(wordCountString).toLowerCase();

                        if (lowerCaseString.includes('k')) {
                            return parseFloat(lowerCaseString) * 1000;
                        } else {
                            return parseFloat(lowerCaseString);
                        }
                    }
                    %>

                var data = [
        <% site.posts.each(function (post) { %>
                        {
                            date: "<%= post.date.format('YYYY-MM-DD') %>",
                            word_count: <%= convertWordCount(getWordCount(post.content)) %>,
                    link: "<%= url_for(post.path) %>",
                    title: "<%= post.title %>"
        },
        <% }); %>
    ];

            var margin = { top: 20, right: 20, bottom: 20, left: 20 };
            var containerWidth = 600;
            var cellSize = Math.min((containerWidth - margin.left - margin.right) / 45, (containerWidth - margin.top - margin.bottom) / 7); // Change 8 to 7 here
            var width = cellSize * 45;
            var height = cellSize * 7; // Change 8 to 7 here

            var svg = d3
                .select("#heatmap-container")
                .append("svg")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top + margin.bottom)
                .append("g")
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

            var xScale = d3.scaleBand().range([0, width]).padding(0.1); // Change range to start from 0
            var yScale = d3.scaleBand().range([0, height]).padding(0.1); // Change range to start from 0

            var exponent = 0.3; // Adjust the exponent as needed
            var colorScale = d3.scaleSequential(d3.interpolate("lightblue", "#2d96bd"))
                .domain([1, Math.pow(d3.max(data, function (d) { return d.word_count; }), exponent)]);

            xScale.domain(d3.range(44, -1, -1)); // Reverse the domain
            yScale.domain(d3.range(6, -1, -1)); // Reverse the domain

            // 在渲染每个格子时设置渐变色的值
            var cells = svg.selectAll(".cell")
                .data(d3.cross(d3.range(7), d3.range(45)).reverse()) // Reverse the data
                .enter().append("a")
                .attr("href", function (d) {
                    var currentDate = getDateBefore(d[0] * 45 + d[1]);
                    var correspondingData = data.find(entry => entry.date === currentDate);
                    return correspondingData ? correspondingData.link : "#";
                })
                .append("rect")
                .attr("class", function (d) {
                    var currentDate = getDateBefore(d[0] * 45 + d[1]);
                    var correspondingData = data.find(entry => entry.date === currentDate);
                    return "cell" + (correspondingData && correspondingData.word_count > 0 ? " blue" : "");
                })
                .attr("x", function (d) { return xScale(d[1]); })
                .attr("y", function (d) { return yScale(d[0]); })
                .attr("width", cellSize)
                .attr("height", cellSize)
                .style("fill", function (d) {
                    var currentDate = getDateBefore(d[0] * 45 + d[1]);
                    var correspondingData = data.find(entry => entry.date === currentDate);
                    return correspondingData ? colorScale(Math.pow(correspondingData.word_count, exponent)) : "#ccc";
                })
                .attr("rx", 4)
                .attr("ry", 4)
                .attr("data-word_count", function (d) {
                    var currentDate = getDateBefore(d[0] * 45 + d[1]);
                    var correspondingData = data.find(entry => entry.date === currentDate);
                    return correspondingData ? correspondingData.word_count : null;
                })
                .attr("title", function (d) {
                    var currentDate = getDateBefore(d[0] * 45 + d[1]);
                    var correspondingData = data.find(entry => entry.date === currentDate);
                    return correspondingData ? currentDate + "\n" + correspondingData.title : "";
                })

                .on("click", function (event, d) {
                    var currentDate = getDateBefore(d[0] * 45 + d[1]);
                    var correspondingData = data.find(entry => entry.date === currentDate);

                    if (correspondingData && correspondingData.link) {
                        // 点击直接跳转
                        window.location.href = correspondingData.link;
                    }
                })
                .on("mouseover", function (event, d) {
                    var title = d3.select(this).attr("title");
                    if (title) {
                        var tooltip = d3.select("#tooltip");
                        // 显示日期和标题
                        tooltip.transition()
                            .duration(200)
                            .style("opacity", 1);
                        var tooltipContent = title;
                        tooltip.html(tooltipContent);

                        // 获取蓝色格子的位置
                        var cellBoundingBox = this.getBoundingClientRect();

                        // 计算 tooltip 的位置,使其中心线与蓝色格子的中心线对齐
                        var tooltipWidth = tooltip.node().offsetWidth;
                        var xPosition = cellBoundingBox.left + cellBoundingBox.width / 2;
                        var yPosition = cellBoundingBox.top;
                        tooltip.style("left", xPosition + "px")
                            .style("top", yPosition + "px");
                    }
                })

                .on("mouseout", function (event, d) {
                    // 隐藏 tooltip
                    var tooltip = d3.select("#tooltip");
                    tooltip.transition()
                        .duration(200)
                        .style("opacity", 0)
                        .on("end", function () {
                            // 清除 tooltip 内容
                            tooltip.html("");
                        });
                });

            function updateCellStyles() {
                var isDarkTheme = document.body.classList.contains("dark-theme");
                cells.style("stroke", isDarkTheme ? "#292a2d" : "#fff")
                    .style("stroke-width", "1px");
            }

            updateCellStyles();

            document.body.addEventListener("themechange", function () {
                updateCellStyles();
            });
});

        </script>

        <style>
            @media (max-width: 767px) {
                #heatmap-container {
                    display: none;
                }
            }

            /* 在桌面端时隐藏 .avatar */
            @media (min-width: 768px) {
                .avatar {
                    display: none;
                }
            }

            .cell {
                stroke: #fff !important;
                stroke-width: 1px;
                fill: #ccc;
                cursor: default;
                /* 添加这一行 */
            }

            .dark-theme .cell {
                stroke: #292a2d !important;
                stroke-width: 1px;
                fill: #a9a9b3;
                cursor: default;
                /* 添加这一行 */
            }

            .blue {
                cursor: pointer;
            }

            .dark-theme .blue {
                cursor: pointer;
            }

            #tooltip {
                position: absolute;
                background-color: white;
                border: 1px solid #a9a9b3;
                padding: 3px;
                /* 调整文字与边框的间距 */
                opacity: 0;
                font-size: 10px;
                /* 调整字体大小 */
                transform: translate(-50%, -100%);
                /* 将框定位到正上方居中 */
                line-height: 1;
            }

            .dark-theme #tooltip {
                background-color: #292a2d;
            }
        </style>

        <div id="tooltip"></div>

        <div class="nickname"><%- theme.nickname %></div>
        <div class="description"><%- markdown(theme.description) %></div>
        <div class="links">
            <% if (theme.links !==undefined) { %>
                <% for (var key in theme.links){ %>
                    <a class="link-item" title="<%- key %>" href="<%= theme.links[key] %>">
                        <% if(theme.links_text_enable) { %>
                            <%= key %>
                                <% } %>
                                    <% if(theme.links_icon_enable){ %>
                                        <i class="iconfont icon-<%- key.toLowerCase() %>"></i>
                                        <% } %>
                    </a>
                    <% } %>
                        <% } %>
        </div>
    </div>
</div>
greendolphindance commented 7 months ago

解决了,把所有的“d[0] 45 + d[1]”改成“d[1] 7 + d[0]”就行了