Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

实现热力图功能 #146

Open
greendolphindance opened this issue Feb 6, 2024 · 4 comments
Open

实现热力图功能 #146

greendolphindance opened this issue Feb 6, 2024 · 4 comments
Labels
good first issue Good for newcomers

Comments

@greendolphindance
Copy link

感觉加了很多花里胡哨的东西已经偏离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 Siricee added the good first issue Good for newcomers label Feb 7, 2024
@Siricee
Copy link
Owner

Siricee commented Feb 7, 2024

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

@greendolphindance
Copy link
Author

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

有道理!我回头改改

@greendolphindance
Copy link
Author

挺好的 这图我建议改为一列 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
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

2 participants