chart.js doesn't load in flow+hilla - chart.js

Made a hilla view based on the standard start.vaadin.com bundle for version 23. I use chart.js version 3.8.2, but also later versions have the same issue.
import { html } from 'lit';
import { customElement, query } from 'lit/decorators.js';
import { View } from '../../views/view';
import * as ChartJS from 'chart.js';
#customElement('welcome-view')
export class WelcomeView extends View {
#query('#plot')
plot!: HTMLCanvasElement;
connectedCallback() {
super.connectedCallback();
this.classList.add('flex', 'p-m', 'gap-m', 'items-end');
}
createRenderRoot() {
// Do not use a shadow root
return this;
}
firstUpdated(changedProperties: Map<string | number | symbol, unknown> | undefined) {
const data = [
{ year: 2010, count: 10 },
{ year: 2011, count: 20 },
{ year: 2012, count: 15 },
{ year: 2013, count: 25 },
{ year: 2014, count: 22 },
{ year: 2015, count: 30 },
{ year: 2016, count: 28 },
];
try {
new ChartJS.Chart(
this.plot,
{
type: 'bar',
data: {
labels: data.map(row => row.year),
datasets: [
{
label: 'Acquisitions by year',
data: data.map(row => row.count)
}
]
}
}
);
} catch(e:unknown){
if (typeof e === "string") {
console.log("Chart exception:"+e);
} else if (e instanceof Error) {
console.log("Chart exception:"+e.message);
}
}
}
render() {
return html`
<div style="height: 800px;width: 800px;"><canvas id="plot"></canvas></div>
`;
}
}
Produces the following console.log message:
"Chart exception:"bar" is not a registered controller."
Any idea's?
I suspect it is related to vite? I didn't try webpack yet, since that is deprecated.

Related

Vue 3 & Chart JS not updating labels

I'm trying to make a simple Vue3 app which show graphs using Chart.js
For this I'm trying to replicate the code shown in the vue-chart-3 plugin doc, which shows an example using a Doughnut chart
My objective is to show a Line graph with a horizontal time axis
The code is a simple App.vue which template is
<template>
<LineChart v-bind="lineChartProps" />
</template>
And the script part:
<script lang="ts">
import { computed, defineComponent, ref } from "vue";
import { LineChart, useLineChart } from "vue-chart-3";
import { Chart, ChartData, ChartOptions, registerables } from "chart.js";
Chart.register(...registerables);
export default defineComponent({
name: "App",
components: { LineChart },
setup() {
const dataValues = ref([30, 40, 60, 70, 5]);
const dataLabels = ref(["Paris", "Nîmes", "Toulon", "Perpignan", "Autre"]);
const toggleLegend = ref(true);
const testData = computed<ChartData<"doughnut">>(() => ({
labels: dataLabels.value,
datasets: [
{
data: dataValues.value,
backgroundColor: [
"#77CEFF",
"#0079AF",
"#123E6B",
"#97B0C4",
"#A5C8ED",
],
},
],
}));
const options = computed<ChartOptions<"doughnut">>(() => ({
scales: {
myScale: {
type: "logarithmic",
position: toggleLegend.value ? "left" : "right",
},
},
plugins: {
legend: {
position: toggleLegend.value ? "top" : "bottom",
},
title: {
display: true,
text: "Chart.js Doughnut Chart",
},
},
}));
const { lineChartProps, lineChartRef } = useLineChart({
chartData: testData,
options,
});
function switchLegend() {
toggleLegend.value = !toggleLegend.value;
}
return {
switchLegend,
testData,
options,
lineChartRef,
lineChartProps,
};
},
mounted() {
if (localStorage.data == undefined) {
localStorage.data = JSON.stringify(this.data);
} else {
this.data = localStorage.data;
}
let dataProcessed = JSON.parse(this.data);
// console.log(JSON.parse(this.data));
console.log(dataProcessed.data);
var dates = [];
// Obtain dates from dataProcessed Array
for (var i = 0; i < dataProcessed.data.length; i++) {
dates.push(dataProcessed.data[i].date);
}
this.testData.labes = dates;
console.log(dates);
},
});
</script>
The objective is that the mounted hook gets certain parameters of the LocalStorage and put them in the "labels" array of the "testData" variable, which is the one which aparently stores the X axis data of the chart.
In the VUE developer tool, it can be seen how this assignation process is done correctly, but in the chart of the left side, the data have not been updated.
Thank you for your help :D

Ember sideload data not linked

I'm new to using Ember and was assigned to an ongoing project and need to resolve the following:
export default class OrderModel extends Model.extend(LoadableModel) {
#attr('string') status;
#attr('number') total;
#hasMany('order-item', { async: true }) orderItems;
}
export default class OrderItemModel extends Model.extend(LoadableModel) {
#attr('number', { defaultValue: 0 }) discount;
#attr('number', { defaultValue: 0 }) price;
#hasMany('item-fix', { async: false }) fixes;
}
export default class ItemFixModel extends Model.extend(LoadableModel) {
#attr('number', { defaultValue: 0 }) price;
}
and when I do let order = await this.store.findRecord('order', order_id, { reload: true });
the json response is:
data: {
type: "orders",
id: "1584",
attributes: {
status: "in_progress",
total: 1300
},
relationships: {
order-items: {
data: [
{
type: "order-items",
id: "1801
}
]
}
}
},
included: [
{
type: "order-items"
id: "1801",
attributes: {
discount: 0,
price: 1200
},
relationships: {
item-fixes: {
data: [
{
type: "item-fixes",
id: "335"
}
]
}
},
{
type: "item-fixes",
id: "335",
attributes: {
price: 100
}
}
]
but when I inspect the orderItem inside the order variable, the itemFixes are empty, but the is in the sideload of the response.
¿How can I link this nested relationship?
Also, here is the serializer.
export default DS.JSONAPISerializer.extend({
serialize(snapshot) {
let serialized = this._super(...arguments);
let { adapterOptions } = snapshot;
if (adapterOptions && adapterOptions.addPromotionCode) {
return { code: serialized.data.attributes.code }
}
serialized.included = A([]);
snapshot.eachRelationship((key, relationship) => {
if (relationship.kind === 'belongsTo') {
if (!isBlank(snapshot.belongsTo(key))) {
let node = snapshot.belongsTo(key).record.serialize({ includeId: true }).data;
delete node.relationships;
serialized.included.pushObject(node);
}
} else if (relationship.kind === 'hasMany') {
if (!isBlank(snapshot.hasMany(key))) {
snapshot.hasMany(key).forEach(ele => {
let node = ele.record.serialize({ includeId: true }).data;
delete node.relationships;
serialized.included.pushObject(node);
});
}
}
});
return serialized;
}
});
In your response the relationship name is item-fixes but in your model it's just fixes. It must be the same.

Angular 7, chart.js even when chart replaced, remaining chart in background which appears sometimes when mouseover

With angular 7, I integrated a chart.js for days of electricity charges. And in various part of my application I have similar charts which load with next/previous day button, etc.
When recreating the chart (on any next or previous day clicked) I do basically following
this.mainChart = new Chart('idOfCanvasOnHtmlComponent', {....})
this.mainChart.update()
It seems that it remains the old chart(s) which is sometimes showed on mouseover on some points and appears strangely, like there are multiple charts which are loaded on mouseover which is anoying!
ChargeUnitDailyComponent
import { Component, OnInit, AfterContentInit } from '#angular/core';
import { FormControl } from '#angular/forms';
import { MatDatepickerInputEvent } from '#angular/material/datepicker';
import { MiscHelper } from 'src/app/helpers/MiscHelper';
import { ChargeUnitService } from 'src/app/services/charge.unit.service';
import { ChargeUnit } from 'src/app/entities/charge.unit';
import { UserMessage, UserMessageType } from 'src/app/entities/user.message';
import { MessageService } from 'src/app/services/message.service';
import { Constants } from 'src/app/Constants';
import * as moment from 'moment';
import { MatIconRegistry } from '#angular/material';
import { DomSanitizer } from '#angular/platform-browser';
import Chart = require('chart.js')
import { MeasuringPoint } from 'src/app/entities/measuring.point';
import { MeasuringPointService } from 'src/app/services/measuring.point.service';
#Component({
selector: 'app-charge-unit-daily',
templateUrl: './charge-unit-daily.component.html',
styleUrls: [
'./charge-unit-daily.component.css',
'../../entities-list.component.css'
]
})
export class ChargeUnitDailyComponent implements AfterContentInit {
static readonly CHART_ID = 'canvasDaily'
currentDate: Date = moment('2019-03-06T00:00:01').toDate() //set default here
/** Header */
dateFormControl = new FormControl(this.currentDate)
statusMessage = 'Default status'
refreshButtonDisabled: boolean = false
/** CHART */
mainChart: Chart;
protected loadingDate: Date = new Date()
constructor(
iconRegistry: MatIconRegistry,
sanitizer: DomSanitizer,
protected messageService: MessageService,
protected entitiesService: ChargeUnitService,
protected parentService: MeasuringPointService,
) {
iconRegistry.addSvgIcon(
'refresh',
sanitizer.bypassSecurityTrustResourceUrl('assets/img/refresh-icon.svg'));
//Do nothing
}
public ngAfterContentInit() {
setTimeout(() => { //to avoid error...
if (this.parentService.currentEntity == null){
this.parentService.currentEntity = new MeasuringPoint(7) //TODO: remove me
}
console.debug('currentDate 1 :', this.currentDate)
this.setChart(this.currentDate)
this.dateFormControl.setValue(this.currentDate)
}, 10);
// this.setStatus('Default canvas...', true)
}//End ngOnInit
/**
*
* #param aDate
*/
protected setChart(aDate: Date){
let lStartDate = new Date(aDate)
lStartDate.setHours(0)
lStartDate.setMinutes(0)
lStartDate.setSeconds(1)
let lEndDate = new Date(aDate)
lEndDate.setHours(23)
lEndDate.setMinutes(59)
lEndDate.setSeconds(59)
this.setStatus('Loading...', false)
this.loadingDate = new Date()
console.debug('----- setChart->aDate:', aDate)
this.resetChart()
this.entitiesService.getBetween(lStartDate, lEndDate).subscribe(
lData => {
console.debug('Received data from entitiesService:', lData);
let lDataArray = (lData as unknown) as []
let lChargeUnitsArray: ChargeUnit[] = []
lDataArray.forEach(element => {
lChargeUnitsArray.push(new ChargeUnit().deserialize(element))
})
this.setChartDataFromEntities(lStartDate, lChargeUnitsArray)
},
lError => {
this.messageService.add(new UserMessage('charge-unit-daily.component->setChart Error:', lError, UserMessageType.Error));
this.setStatus('Error loading chart data:' + lError.toString(), true)
},
() => {
//loading terminated
}
);
}
onDateChanged(anEventType: string, anEvent: MatDatepickerInputEvent<Date> ) {
console.debug('onDateChanged clicked', anEvent)
let lDate = anEvent.value as Date
this.currentDate = lDate
this.setChart(lDate)
}
/**
*
* #param aDate
* #param aChargeUnitArray
*/
setChartDataFromEntities( aDate: Date, aChargeUnitArray: ChargeUnit[] ){
console.debug('setChartDataFromEntities->aChargeUnitArray', aChargeUnitArray)
let lChartDataArray = []
let lChartDataLineDataArray: Array<number> = []
let lChartLabelsArray: string[] = []
//Lines and labels
aChargeUnitArray.forEach(element => {
lChartDataLineDataArray.push(element.charge)
lChartLabelsArray.push(MiscHelper.dateTimeHMSForChart(element.timestamp))
});
//setting chart data
lChartDataArray[0] = {
data: lChartDataLineDataArray,
label: MiscHelper.dateForChartTooltips(aDate),
borderColor: Constants.CHART_DATASETS_BORDER_COLORS[0],
backgroundColor: Constants.CHART_DATASETS_BACKGROUND_COLORS[0],// removed otherwise not working
borderWidth: 2,
fill: 'origin'
}
console.debug('setChartDataFromEntities->lChartDataArray', lChartDataArray)
this.mainChart = new Chart(ChargeUnitDailyComponent.CHART_ID, {
type: 'line',
data: {
labels: lChartLabelsArray,
datasets: lChartDataArray,
},
options: {
legend: {
display: false
},
scales: {
xAxes: [{
display: true
}],
yAxes: [{
display: true
}],
},
responsive: true,
},
})
this.mainChart.update()
let lDiff = new Date().getTime() - this.loadingDate.getTime()
this.setStatus('Chart loaded:' + moment(lDiff).format('mm\'ss\'\''), true)
}
/**
*
*/
public resetChart(){
this.mainChart = new Chart(ChargeUnitDailyComponent.CHART_ID, {
type: 'line',
fillOpacity: .3,
data: {
labels: [],
datasets: []
},
options: {
legend: {
display: true
},
scales: {
xAxes: [{
display: true
}],
yAxes: [{
display: true
}],
},
events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "touchend"]
}
})
this.mainChart.update()
}
onRefreshClicked(anEvent){
console.debug('onRefreshClicked', anEvent)
this.setChart(this.currentDate)
}
onPreviousClicked(anEvent){
console.debug('onPreviousClicked', anEvent)
this.currentDate.setDate(this.currentDate.getDate() - 1)
this.dateFormControl.setValue(this.currentDate)
this.setChart(this.currentDate)
}
onNextClicked(anEvent){
console.debug('onNextClicked', anEvent)
this.currentDate.setDate(this.currentDate.getDate() + 1)
this.dateFormControl.setValue(this.currentDate)
this.setChart(this.currentDate)
}
}
Component.html
<mat-toolbar>
<span>
<mat-form-field>
<input matInput
[matDatepicker]="matDatepicker"
[formControl]="dateFormControl"
(dateChange)="onDateChanged('change', $event)"
>
<mat-datepicker-toggle matSuffix [for]="matDatepicker"></mat-datepicker-toggle>
<mat-datepicker #matDatepicker></mat-datepicker>
</mat-form-field>
</span>
<span class="fill-remaining-space">
<button mat-raised-button (click)="onPreviousClicked($event)" color="accent"><</button>
<button mat-raised-button (click)="onNextClicked($event)" color="accent">></button>
<button matTooltip="Refresh" mat-icon-button (click)="onRefreshClicked($event)" [disabled]="refreshButtonDisabled">
<mat-icon svgIcon="refresh" class="mat-icon-">Refresh</mat-icon>
</button>
</span>
<span><p>Status:{{statusMessage}}</p></span>
<script src="node_modules/chart.js/src/chart.js"></script>
</mat-toolbar>
<div class="canvasContainer" style="display: block; "><!--Mandatory div including chart-->
<canvas id="canvasDaily">{{mainChart}}</canvas>
</div>
The behaviour I figured out is that the chart creation has to be done only one time.
And then the properties set like this.mainChart.data.datasets = [...] or this.mainChart.data.datasets.push (...)
Otherwise there seem to be a zombie chart remaining in background and appearing on mouseover.
Why?? big question....

How get sum of total values in stackedBar ChartJs

I'm trying to get the sum of all values of a stackedBar and include this total in tooltip.
Note: my datasets aren't static, this is an example
var barChartData = {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [{
label: 'Corporation 1',
backgroundColor: "rgba(220,220,220,0.5)",
data: [50, 40, 23, 45, 67, 78, 23]
}, {
label: 'Corporation 2',
backgroundColor: "rgba(151,187,205,0.5)",
data: [50, 40, 78, 23, 23, 45, 67]
}, {
label: 'Corporation 3',
backgroundColor: "rgba(151,187,205,0.5)",
data: [50, 67, 78, 23, 40, 23, 55]
}]
};
window.onload = function() {
var ctx = document.getElementById("canvas").getContext("2d");
window.myBar = new Chart(ctx, {
type: 'bar',
data: barChartData,
options: {
title:{
display:true,
text:"Chart.js Bar Chart - Stacked"
},
tooltips: {
mode: 'label',
callbacks: {
label: function(tooltipItem, data) {
var corporation = data.datasets[tooltipItem.datasetIndex].label;
var valor = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
var total = eval(data.datasets[tooltipItem.datasetIndex].data.join("+"));
return total+"--"+ corporation +": $" + valor.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,');
}
}
},
responsive: true,
scales: {
xAxes: [{
stacked: true,
}],
yAxes: [{
stacked: true
}]
}
}
});
};
Now total is the sum per dataset and I need the sum per stackedBar.
Example
Label A: value A
Label B: value B
Label C: value C
TOTAL: value A + value B + value C
It is possible to get that total value?
Thanks, Idalia.
First you should know that if you return an array instead of a single string in the callback of the tooltip, it will display all the strings in your array as if it were different datasets (see this answer for more details).
So I edited a little bit your callback to the following:
callbacks: {
label: function(tooltipItem, data) {
var corporation = data.datasets[tooltipItem.datasetIndex].label;
var valor = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
// Loop through all datasets to get the actual total of the index
var total = 0;
for (var i = 0; i < data.datasets.length; i++)
total += data.datasets[i].data[tooltipItem.index];
// If it is not the last dataset, you display it as you usually do
if (tooltipItem.datasetIndex != data.datasets.length - 1) {
return corporation + " : $" + valor.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,');
} else { // .. else, you display the dataset and the total, using an array
return [corporation + " : $" + valor.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,'), "Total : $" + total];
}
}
}
You can see the full code in this jsFiddle, and here is its result :
i modified tektiv answer to show Total only for active sets and move it to tooltips footer.
tooltips: {
mode: 'label',
callbacks: {
afterTitle: function() {
window.total = 0;
},
label: function(tooltipItem, data) {
var corporation = data.datasets[tooltipItem.datasetIndex].label;
var valor = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
window.total += valor;
return corporation + ": " + valor.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
},
footer: function() {
return "TOTAL: " + window.total.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
}
}
}
Shorter version of Gaspar's answer:
tooltips: {
callbacks: {
footer: (tooltipItems, data) => {
let total = tooltipItems.reduce((a, e) => a + parseInt(e.yLabel), 0);
return 'Total: ' + total;
}
}
}
Example: https://jsfiddle.net/g3ba60zc/2/
In the other answers you replace the last dataset, with this you don't need to
tooltips: {
callbacks: {
title: function(tooltipItems, data) {
return _this.chart.data.labels[tooltipItems[0].index];
},
footer: function(tooltipItems, data) {
let total = 0;
for (let i = 0; i < tooltipItems.length; i++) {
total += parseInt(tooltipItems[i].yLabel, 10);
}
return 'Total: ' + total;
}
}
}
Ps: It's typescript lang.
#Haider this is what you were looking for, I had the same problem.
I have reused your code and built upon it #tektiv
I have made one small change where instead of building into the label I have made use of the afterbody. This removes the key color
afterBody code:
afterBody: function (tooltipItem, data) {
var corporation = data.datasets[tooltipItem[0].datasetIndex].label;
var valor = data.datasets[tooltipItem[0].datasetIndex].data[tooltipItem[0].index];
var total = 0;
for (var i = 0; i < data.datasets.length; i++)
total += data.datasets[i].data[tooltipItem[0].index];
return "Total : $" + total;
}
Full code here at JSFiddle
Picture demonstration of the finished tooltip
Using Chart.js 2.5.0
var valor = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
returns a string value. To calculate the correct sum value you have to add a parseFloat statement:
tooltips: {
mode: 'label',
callbacks: {
afterTitle: function() {
window.total = 0;
},
label: function(tooltipItem, data) {
var corporation = data.datasets[tooltipItem.datasetIndex].label;
var valor = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
//THIS ONE:
valor=parseFloat(valor);
window.total += valor;
return corporation + ": " + valor.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
},
footer: function() {
return "TOTAL: " + window.total.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
}
}
}
If you are on older ChartJs version you have to handle this in a different way. I'm using Chart.js Version 2.7.2 and this is how I handled it:
tooltips: {
mode: 'label',
callbacks: {
footer: function (data) {
var total = 0;
for (var i = 0; i < data.length; i++) {
total += data[i].yLabel;
}
return 'Total: ' + total
}
}
}
Chart.js made their own, very satisfying solution:
options: {
interaction: {
intersect: false,
mode: 'index',
},
plugins: {
tooltip: {
callbacks: {
footer: footer,
}
}
}
}
and somewhere else in your code:
const footer = (tooltipItems) => {
let sum = 0;
tooltipItems.forEach(function(tooltipItem) {
sum += tooltipItem.parsed.y;
});
return 'Sum: ' + sum;
};
Worked just fine for me!

find object from mongodb

In the data bellow, I would like to find the reminder where _id=abc1 and the month is 1. The date stored in db is text.
I try to use this command but it have error: db.check.find( {_id:"abc1"}, { reminder: { $regex: {date:2015-1/} }} ).pretty();
How can I do it?
The expected result is { date: "2005-1-5", event: "MeetingB" }, { date: "2005-1-4", event: "MeetingA" }
{
_id: "abc1",
reminder:[
{
date: "2005-1-5",
event: "MeetingB"
},
{
date: "2005-1-4",
event: "MeetingA"
},
{
date: "2005-2-4",
event: "MeetingA"
}
]
}
{
_id: "abc2",
reminder:[
{
date: "2005-1-5",
event: "MeetingB"
}
]
}
It think you have 2 solutions :
The first one is to aggregate your search in another to get
only the month.
Query on the date
With this example (I haven't tested but it should looks like this):
db.check.find( {
$and: [
{ "_id": { $in: ["abc1"] } },
{ "reminder.date": { $in: [/2005-1*/] } }
]
} );
You cannot use regex in a in and you have to use JavaScript regex
However it will return the full object and not a partial object as apparently you want to.