Chart.js(chartjs-node-canvas)创建基于日期的浮动条形图

h9a6wy2h  于 2022-11-07  发布在  Chart.js
关注(0)|答案(2)|浏览(116)

我将尽可能清楚地解释我的问题,同时也避免使这个主题太长。我最近发现了Chart.js库,它非常适合我的需要。现在,由于我正在使用Node.js,并且需要一个图形的png,我将使用chartjs-node-canvas库。记住这些信息,我将尝试将我的主题分成多个部分,以便更清楚地理解。

最终目标

在进入问题本身之前,我想先讨论一下我的最终目标。这是对我要做的事情给予一个大致的概念,以便相应地拟合响应。为了简短起见,我有{awardedDate: "2022-06-22T12:21:17.22Z", badgeId: 1234567}形式的数据,其中awardedDate是徽章授予的时间戳。而badgeId是所授予徽章的ID(这与图表无关,但它存在是因为它是数据的一部分)。现在,我有一个包含大约2,787个对象的样本,它们都具有不同的奖励日期日期范围从2016年到2022年。我的目标是将这些徽章按月-年分组,该月-年将显示该年该月获得的徽章数量。然后,我想根据该年该月获得的徽章数量绘制一个瀑布图。目前,没有一个具体的结构来描述它的外观,但是它可以是{"02-2022": 10, "03-2022": 5}这样的对象,也可以是其他任何对象。

实际问题

现在你大概知道我的最终目标是什么了,我真正的问题是,我如何才能使一个浮动的(我们可以将瀑布结构的内容留给另一个主题)条形图。由于数据可以有空白周期(数据集可能会有长达数月的间隔),我无法真正利用标签(除非我说错了什么),所以x-y关系是最好的。我试着使用{x: "2022-06-22T12:21:17.226Z", y: [10, 15]}结构,但没有真正产生任何结果。现在,我正在使用一个示例代码来测试图形如何与数据React。当然,一旦我有了一个成品,我会用实际值替换测试值。

const config = {
        type: "bar",
        data: {
            datasets: [{
                label: "Badges",
                data: [
                    {
                        x: "2022-06-22T12:41:17.226Z",
                        y: [10, 15]
                    }
                ],
                borderColor: "rgb(75, 192, 192)",
                borderSkipped: false
            }]
        },
        options: {
            plugins: {
                legend: {
                    display: false
                },
                title: {
                    display: true,
                    text: "Test",
                    color: "#FFFFFF"
                }
            },
            scales: {
                x: {
                    type: 'time',
                    title: {
                        display: true,
                        text: 'Time',
                        color: "#FFFFFF"
                    },
                    min: "2022-06-22T12:21:17.226Z",
                    max: "2022-06-22T14:21:17.226Z",
                    grid: {
                        borderColor: "#FFFFFF",
                        color: "#FFFFFF"
                    },
                    ticks: {
                        color: "#FFFFFF"
                    }
                },
                y: {
                    title: {
                        display: true,
                        text: 'Number of Badges',
                        borderColor: "#FFFFFF",
                        color: "#FFFFFF"
                    },
                    min: 0,
                    max: 50,
                    grid: {
                        borderColor: "#FFFFFF",
                        color: "#FFFFFF"
                    },
                    ticks: {
                        color: "#FFFFFF"
                    }
                }
            }
        },
        plugins: [
            {
                id: 'custom_canvas_background_color',
                beforeDraw: (chart) => {
                    const ctx = chart.ctx;
                    ctx.save();
                    ctx.fillStyle = '#303030';
                    ctx.fillRect(0, 0, chart.width, chart.height);
                    ctx.restore();
                  }
            }
        ]
    };

    const imageBuffer = await canvasRenderService.renderToBuffer(config)

    fs.writeFileSync("./chart2.png", imageBuffer)

这是代码生成的图形:

当然,应该发生的事情是,在开始处会生成一个浮动条,范围从5到10,但是如上所示,什么都没有发生。如果有人能帮助我解决这个问题,那将是非常令人惊讶的。非常感谢你的时间和帮助,我非常感激。

n6lpvg4x

n6lpvg4x1#

受此answer的启发,我提出了以下解决方案。
第一个
如果你也想看到差距,你首先必须用最早和最晚日期之间的后续月份初始化badgesPerMonth,每个月份的值都为零。请看一下这个answer,了解如何完成这个操作。

jfewjypa

jfewjypa2#

在阅读@uminder的回复后,我创建了下面的代码,它解决了我的问题:

dateGroups = Object.fromEntries(
        Object.entries(dateGroups).sort(([d1,],[d2,]) => {return (d1 < d2) ? -1 : ((d1 > d2) ? 1 : 0)})
    )
    const dateTimesConst = Object.keys(dateGroups)
    const dateValuesConst = Object.values(dateGroups)
    let dateTimes = []
    let dateValues = []
    let prevLength = 0
    let mostBadgesPerMonth = 0

    for (let i = 0; i < dateValuesConst.length; i++) {
        const currentMonth = new Date(Date.parse(dateTimesConst[i]))
        const previousMonth = new Date(Date.UTC(currentMonth.getUTCFullYear(), currentMonth.getUTCMonth() - 1, 1, 0, 0, 0, 0)).toISOString()
        const nextMonth = new Date(Date.UTC(currentMonth.getUTCFullYear(), currentMonth.getUTCMonth() + 1, 1, 0, 0, 0, 0)).toISOString()

        // if (!dateTimesConst.includes(previousMonth)) prevLength = 0

        const length = dateValuesConst[i].length
        dateValues.push([prevLength, length])
        dateTimes.push(dateTimesConst[i])
        prevLength = length
        if (length > mostBadgesPerMonth) mostBadgesPerMonth = length

        // if (!dateTimesConst.includes(nextMonth) && i !== dateValuesConst.length - 1) {
        //     dateTimes.push(nextMonth)
        //     dateValues.push([length, 0])
        //     prevLength = 0
        // }
    }

    function barColorCode() {
        return (ctx) => {
            const start = ctx.parsed._custom.start
            const end = ctx.parsed._custom.end

            return start <= end ? "rgba(50, 168, 82, 1)" : (start > end) ? "rgba(191, 27, 27, 1)" : "black"
        }
    }

    const config = {
        type: "bar",
        data: {
            labels: dateTimes,
            datasets: [{
                label: "Badges",
                data: dateValues,
                elements: {
                    bar: {
                        backgroundColor: barColorCode()
                    }
                },
                barPercentage: 1,
                categoryPercentage: 0.95,
                borderSkipped: false
            }]
        },
        options: {
            plugins: {
                legend: {
                    display: false
                },
                title: {
                    display: true,
                    text: "Test",
                    color: "#FFFFFF"
                }
            },
            scales: {
                x: {
                    type: 'time',
                    title: {
                        display: true,
                        text: 'Date',
                        color: "#FFFFFF"
                    },
                    time: {
                        unit: "month",
                        round: "month"
                    },
                    min: dateTimesConst[0],
                    max: dateTimesConst[dateTimesConst.length - 1],
                    grid: {
                        borderColor: "#FFFFFF",
                        color: "#FFFFFF"
                    },
                    ticks: {
                        color: "#FFFFFF"
                    }
                },
                y: {
                    title: {
                        display: true,
                        text: 'Number of Badges',
                        borderColor: "#FFFFFF",
                        color: "#FFFFFF"
                    },
                    min: 0,
                    max: mostBadgesPerMonth + 1,
                    grid: {
                        borderColor: "#FFFFFF",
                        color: "#FFFFFF"
                    },
                    ticks: {
                        color: "#FFFFFF"
                    }
                }
            }
        },
        plugins: [
            {
                id: 'custom_canvas_background_color',
                beforeDraw: (chart) => {
                    const ctx = chart.ctx;
                    ctx.save();
                    ctx.fillStyle = '#303030';
                    ctx.fillRect(0, 0, chart.width, chart.height);
                    ctx.restore();
                  }
            }
        ]
    };

    const imageBuffer = await canvasRenderService.renderToBuffer(config)

    fs.writeFileSync("./chart2.png", imageBuffer)

再次感谢@uminder给我的灵感。

相关问题