typescriptvuejs3vue-composition-apivue-chartjs

Vue3 Composition API updating vue-chartjs with reactive data from an object


I'm trying to update a Bar Graph every time data in a object changes, however I'm not able to get it working in any way. I'm using reactive() and ref(), but the graph will not update until I refresh the page.

Here's my chart component:

<script setup lang="ts">
import { Bar } from "vue-chartjs"
import {
    Chart as ChartJS,
    Title,
    Tooltip,
    Legend,
    BarElement,
    CategoryScale,
    LinearScale,
} from "chart.js"

ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)

const props = defineProps<{ userHours: {} }>()

const chartData = ref({
    labels: Object.keys(props.userHours),
    datasets: [
        {
            label: "Worker Hours per Employee",
            data: [...Object.values(props.userHours)] as number[],
            maxBarThickness: 32,
            backgroundColor: "#ffb53c",
        },
    ],
})

const chartOptions = ref({
    responsive: true,
    maintainAspectRatio: true,
    scales: {
        x: {
            grid: {
                color: "black",
            },
            ticks: {
                color: "#eee",
            },
        },
        y: {
            grid: {
                color: "black",
            },
            ticks: {
                color: "#eee",
            },
        },
    },
    legend: {
        labels: {
            fontColor: "#eee",
        },
    },
})

const chartStyles = computed(() => {
    return {
        width: "100%",
        height: "20ch",
    }
})
</script>

<template>
    <Bar
        id="workhr-chart"
        :options="chartOptions"
        :data="chartData"
        :style="chartStyles"
    >
    </Bar>
</template>

And here is my main page (parent):

<script setup lang="ts">
import { User, Subtask } from ".prisma/client"
import { optionalMemberExpression } from "@babel/types"

const memberHours = reactive(initHours())

function initHours(): { [key: string]: number } {
    const hoursByMember: { [key: string]: number } = {}

    const tasks = project.value!.tasks.filter(
        task => task.status === 0 || task.status === 1,
    )
    for (const task of tasks) {
        for (const member of projectMembers) {
            if (task.assignees.includes(member)) {
                if (hoursByMember.hasOwnProperty(member.name)) {
                    hoursByMember[member.name] += task.workerHours
                } else {
                    hoursByMember[member.name] = task.workerHours
                }
            }
        }
    }
    return hoursByMember
}

function updateHours(uid: number, status: boolean) {
    let task = project.value!.tasks.find(task => task.uid === uid)
    let foundSubtask: Subtask | undefined

    if (!task) {
        // Check if the uid is a subtask
        for (const t of project.value!.tasks) {
            foundSubtask = t.subtasks.find(subtask => subtask.uid === uid)
            if (foundSubtask) {
                task = t
                break
            }
        }
    }
    if (!task) {
        return
    }

    const assignees = new Set(task.assignees.map(member => member.name))

    for (const memberName of Object.keys(memberHours)) {
        if (assignees.has(memberName)) {
            if (status) {
                memberHours[memberName] -= foundSubtask
                    ? foundSubtask.workerHours
                    : task.workerHours
            } else {
                memberHours[memberName] += foundSubtask
                    ? foundSubtask.workerHours
                    : task.workerHours
            }
        }
    }

    console.log(memberHours)
}

}
</script>

<template>
    <TaskSwitcher :tasks="project!.tasks" @update="updateHours" />

    <ProjectChart :user-hours="memberHours" />
</template>

Can someone please help with making my chart reactive? I've been trying to do that for the past few days but I'm not sure how I can do it.

Thanks!


Solution

  • Because chartData is a ref, it will not listen to props change. You need to either watch on props change and update chartData or change chartData to computed property which will listen to props change.