ChartJS not rendering after update - javascript

I'm trying to update the values of my chart after aplying or clearing filters with the following code
<div class="col-xs-8 card-container" style="padding: 0 0 0 0">
<mat-card height="100%" style="padding: 16px 0px 16px 16px !important; height:100%">
<mat-card-title style="text-align:center;">Gráfica</mat-card-title>
<mat-card-content>
<div style="display: block; width: 100%" id="canvasContainer" *ngIf="!emptyData()">
<canvas id="myChart"></canvas>
</div>
<div style="display: block; width: 100%" *ngIf="emptyData()">
<h3>Pendiente de evaluar.</h3>
</div>
</mat-card-content>
</mat-card>
this is the html where I render the chart on the canvas with id myChart
this is the code where I generate my first chart, it only executes once and it works
setup(): void{
const maxValue = this.getMaxValue();
var ctddx = document.getElementById('myChart');
var canvas = <HTMLCanvasElement> document.getElementById('myChart');
if(canvas !== null) {
this.ctx = canvas.getContext("2d");
this.chart= new Chart(this.ctx, {
type: 'bar',
data: {
labels: this.labels,
datasets: [{
data: this.values,
borderWidth: 3,
fillColor: this.colors,
backgroundColor: this.colors,
borderColor: this.colors
}]
},
options: {
responsive: true,
legend: {
display: false
},
hover: {
mode: 'label'
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
callback: function (value) { if (Number.isInteger(value)) { return value; } },
suggestedMax: maxValue,
}
}],
xAxes: [{
fontSize: 35,
barThickness : 65
}],
},
plugins: {
datalabels: {
// align: 'start',
// anchor: 'start',
clamp: true,
}
}
}
});
this.initialized = true;
}
}
till this point the code works and render the first chart correctly.
Now when I try to apply filters or clear filters it doesn't render anything this is the following code
modifyChart(labels,values){
let oldchart = this.chart;
this.removeData();
this.addData(labels,values);
console.log(oldchart===this.chart);
}
removeData() {
this.chart.data.labels.pop();
this.chart.data.datasets.forEach((dataset) => {
dataset.data.pop();
});
this.chart.update();
}
addData(label, data) {
this.chart.data.labels.push(label);
this.chart.data.datasets.forEach((dataset) => {
dataset.data.push(data);
});
this.chart.update();
}
It doesn't work, it render nothing and I even tried to compare previously chart with new chart introducing the same values it had, and it returns true, so even being the same Chart, it isn't rendering
I think the problem has to be because update deletes de canvas and create it again, but after a while I can't find a workaround

Ok, so I managed to fix this, I hope this answer can help someone who faces the same problem with re rendering Charts on the same canvas if update doesn't work.
first of all I had this lifecycle method
ngAfterContentChecked(): void {
if (!this.initialized && !this.emptyData()) {
this.setup();
}
}
So I can render the first chart it can't be done on OnInit because you need the canvas to be ready, so with this I'm always waiting for canvas to be ready like my method setup I posted on the question
setup(): void{
const maxValue = this.getMaxValue();
var canvas = <HTMLCanvasElement> document.getElementById('myChart');
if(canvas !== null) {
...
this.initialized = true;
}
}
the problem with Update I think that it also destroys the canvas and create a new one with the same Id, so I think it tries to render the chart before the canvas is ready, so I made a workaround like this
after applying all filters needed and retrieving the new data
modifyChart() {
this.chart.destroy();
this.initialized = false;
}
with this code, my ngAfterContentChecked is going to call for setup till canvas is ready and change initialized to true again
I know this is probably not the best workaround, but at least it fixed my problem

Related

How to display two C3 charts in the same row in two different divs

I've some problems with c3 plugins.
I'm trying to put 2 charts in a structure like this:
<div class="row">
<div class="col-6">
<div id="chart1"></div>
</div>
<div class="col-6">
<div id="chart2"></div>
</div>
</div>
My output is the attached one, and i couldn't find the reason why the charts go out of the div.
I've already tried to use chart.resize() but it doesn't work (maybe i put it in the wrong place).
Can you help me ?
You can find my code here:
js1, js2, html
Thank you !
The problem is that you are loading the charts (I think!) within a div that is not displayed when the page loads, the C3 doesn't know how to size the charts correctly.
Instead of loading every chart in the Document Ready, wrap your posts in a function like this:
function loadStatArticoliCharts() {
$.post(
'{{ url('myGetter') }}/{{ data.listId }}',
{},
function(data) {
grafico_fatturato = c3.generate({
bindto: "#fatturato-mensile-barre",
data: {
columns: [
[new Date().getFullYear() - 1, 0,0,0,0,0,0,0,0,0,0,0,0],
[new Date().getFullYear(), 0,0,0,0,0,0,0,0,0,0,0,0],
],
type : 'bar',
colors: data.colors
},
bar: {
width: 30
},
axis: {
x: {
type: 'category',
categories: months
},
y: {
tick: {
format: function(value) { return value.formatMoney(2, ',', '.') }
}
}
},
tooltip: {
format: {
value: function(value) { return "€ " + value.formatMoney(2, ",", "."); }
}
},
transition: {
duration: 1000
}
});
setTimeout(function() {
grafico_fatturato.load({
columns: [
data.columns.current,
data.columns.past
],
});
grafico_fatturato.resize();
}, 500);
}
);
}
Create a global boolean variable to store if you've already loaded the charts (so you won't trigger the load multiple times) with
let loadedChart1 = false;
let loadedChart2 = false;
Finally create a controller that will trigger the load function when you click the tab:
$('a[data-toggle="tab"]').on('shown.bs.tab', function (ev) {
let tabId = $(ev.target).attr("aria-controls");
switch (tabId) {
case "chart1":
if (!loadedChart1) {
loadChart1();
loadedChart1 = true;
}
break;
case "chart2":
if (!loadedChart2) {
loadChart2();
loadedChart2 = true;
}
break;
}
let oldTabId = $(ev.relatedTarget).attr("aria-controls");
$('#' + oldTabId).removeClass("active");
}

Using data from API with Chart JS

I am getting data from an api and then reformatting part of it into an array using .map(), I am successfully able to do this, but when it comes time to pass it into Chart JS as data it does work. I am able to pass in a normal, hard coded, array but not my own data...
I tried using an Angular directive (NG2-Charts) to help out thinking maybe that was the problem, but that doesn't work either...
Component.ts:
... Other variable and stuff up here...
getStockData() {
this.stocksService.getStockData()
.subscribe(
(response) => {
for(var i = 0; i < response.length; i++) {
this.stockOpen.push(response[i]['open']);
}
console.log('after loop: ', this.stockOpen);
},
(error) => console.error(error)
);
console.log('real: ', this.stockOpen);
console.log('test: ', this.testData);
}
// Chart JS version
buildStockChart() {
var ctx = document.querySelector("#chart");
this.chart = new Chart(ctx, {
type: 'bar',
data: {
labels: [1,2,3,4,5],
datasets: [
{
data: this.stockOpen,
borderColor: "#3cba9f",
fill: false
}
]
},
options: {
legend: {
display: false
},
scales: {
xAxes: [{
display: true
}],
yAxes: [{
display: true
}],
}
}
});
}
// NG2-Charts version
public lineChartData:Array<any> = [
{data: this.testData},
];
public lineChartLabels:Array<any> = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
public lineChartOptions:any = {
responsive: true
};
Result from console.log():
i also have same problem with chart JS on angular so i force to use another chart.
im now using angular 2 chart js.
i think the problem here is the delay of data fetch by API, the CHART component is already render on html view but the data is still not fetch by the API service.
try to add this code on your code block. This will handle the data if API service data is available.
()=>{this.buildStockChart();}
this.stocksService.getStockData()
.subscribe(
(response) => {
for(var i = 0; i < response.length; i++) {
this.stockOpen.push(response[i]['open']);
}
console.log('after loop: ', this.stockOpen);
},
()=>{
this.buildStockChart();
}
);
console.log('real: ', this.stockOpen);
console.log('test: ', this.testData);
}
This chart is easy to manage for dynamic instances.
Hope this chart will work on you.
https://www.npmjs.com/package/angular2-chartjs
When are you calling the buildStockChart() method?
You should call it right after the for loop into the callback you pass to the subscribe method, since that's the moment when this.stockOpen is populated (before that moment it will be empty as you are seeing in the console).
As #Joseph Agbing, I was unable to get it work with angular 7. I'm now using chart.js only
npm install chart.js --save
with into my someChart.component.html
<div style="display: block"><!--Mandatory div including chart-->
<canvas id="canvas">{{chart}}</canvas>
</div>
into my someChart.component.ts
called from my httpClient.post(...).subscribe(lData => (setChartDataFromEntities(lDataProcessed), ...)
import { Chart } from 'chart.js';
export class someClass {
/**
*
* #param aDate
* #param aChargeUnitArray
*/
setChartDataFromEntities( aDate: Date, aChargeUnitArray: ChargeUnit[] ){
console.debug('setChartDataFromEntities->', aChargeUnitArray)
let lChartDataArray = []
let lChartDataLineDataArray: Array<Number> = []
let lChartLabelsArray: string[] = []
let l_s: string
aChargeUnitArray.forEach(element => {
lChartDataLineDataArray.push(element.charge)
lChartLabelsArray.push(MiscHelper.dateTimeHMSForChart(element.timestamp))
});
lChartDataArray.push(
{
data: lChartDataLineDataArray,
label: MiscHelper.dateForGui(aDate),
}
)
this.chart = new Chart('canvas', {
type: 'line',
data: {
labels: lChartLabelsArray,
datasets: lChartDataArray
},
options: {
legend: {
display: false
},
scales: {
xAxes: [{
display: true
}],
yAxes: [{
display: true
}],
}
}
});
this.statusMessage = 'Chart loaded'
}
hope it helps somebody more than the day I wasted trying to get it work...

Changing cursor to pointer on Chart.js bar chart when hover (mousemove) event is disabled?

We are using Chart.js (version 2.6.0) for a bar chart in an Angular 5 application and the client wanted us to disable hover events for chart interactions(they only wanted the bar to change and the tooltips to show up when the user clicked on a bar).
in the bar chart options object, we have the following defined for the events property:
events: ["touchstart","touchmove","click"]
That disables hovering events over the bar chart. Now however, the client wants us to change the cursor to a pointer when the user hovers over one of the bars, so that they know they can click on it, which is a valid point. I've found several solutions here on SO, but I can't seem to find a way to do it without adding "mousemove" to the events property, which just enables hovering interactions on the entire chart.
What really confuses me is that options.hover has an event property called "onHover" that has a callback, but it fires when ANY of the defined events happens, including clicks.
http://www.chartjs.org/docs/latest/general/interactions/events.html
Is this even possible without re-enabling the hover interaction in general? Any help would be greatly appreciated.
With Chart.js 3.x:
onHover: (event, chartElement) => {
event.native.target.style.cursor = chartElement[0] ? 'pointer' : 'default';
}
With Chart.js 2.x:
onHover: (event, chartElement) => {
event.target.style.cursor = chartElement[0] ? 'pointer' : 'default';
}
Based on #Luca Fagioli answer, in my case, I didn't want to disable the click events in my chart so i added:
events: ['mousemove', 'click'],
onHover: (event, chartElement) => {
event.target.style.cursor = chartElement[0] ? 'pointer' : 'default';
}
now that you have a cursor on the chart you want the cursor in the legend too - if they are clickable - so in the legend settings toy hold add:
onHover: (event) => {
event.target.style.cursor = 'pointer';
}
For versions > 3.x
you find the target under native
options: {
plugins : {
legend: {
labels: {
onHover: function (e) {
e.native.target.style.cursor = 'pointer';
},
onLeave: function (e) {
e.native.target.style.cursor = 'default';
}
}
}
}
}
This is almost 5 years old question, using the version 3.x.x of ChartJS we just need to declare some event handlers like onHover, onClick and define the events handle by the tooltip like events: ['click'].
Here we have a working snippet:
const isArray = a => a instanceof Array
const isNull = a => a == null
const cursor = a => {
if (isNull(a)) return 'default'
if (!isArray(a)) return 'pointer'
if (isArray(a) && a.length > 0) return 'pointer'
return 'default'
}
const $canvas = document.getElementById('chart')
const onHover = (e, item) => {
$canvas.style.cursor = cursor(item)
}
const onLeave = () => {
$canvas.style.cursor = 'default'
}
const onClick = (event, items) => {
if (items.length === 0) return
console.log('onclick')
}
const lineChart = new Chart($canvas, {
type: 'bar',
data: {
labels: ['May', 'June', 'July'],
datasets: [{
data: [15, 25, 15],
label: "My Dataset1",
backgroundColor: "#00F",
fill: false
}, {
data: [35, 15, 25],
label: "My Dataset2",
backgroundColor: "#F00",
fill: false
}]
},
options: {
responsive: true,
onHover,
onClick,
plugins: {
tooltip: {
// Tooltip will only receive click events
events: ['click'],
},
legend: {
onHover,
onLeave,
},
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<canvas id="chart" width="600" height="180"></canvas>

Highlight Single Bar/Column in Highcharts on load

I have an existing highchart on which I need to highlight a single column.
It is a percentile graph that has been around for a while, I am still pretty new to high charts, but I have seen a similar question here on SO, this question, though deals with stacked bars and a click event...
The code makes sense to me in the example, but I guess I am missing something,
Here is my sample (trying to highlight the 24th column)
https://jsfiddle.net/52t43y3k/2/
Here is the question I saw:
Highlight one bar in a series in highcharts?
for ref, my code is
var col_chart = $('#section-2-chart').highcharts({
chart: {
type: 'column'
},
tooltip: { enabled: false },
credits:false,
title: false,
xAxis: {
title:{text:'PERCENTILES'},
type: 'Percentile',
labels: {
enabled:true,
formatter: function() {
return this.value*2;
}
}
},
yAxis: {
min: 0,
title:{text:'Total Image Weight'}
},
legend: {
enabled: false
},
series: [{
data: [169,12003,38308.5,61739.7,97069,131895.5,161086.7,198758.7,219779.3,243567.7,276607.7,296931.5,327457.5,362840.3,383978,410685.5,443774,467039.5,491654,517205,544754.7,578468.3,605392.5,644214.5,693765,766953.7,806616,855380.7,894161,942282,1001179.7,1062697.7,1125773.3,1186437,1236893.7,1314379.5,1378944,1454090.3,1553065,1689346,1833150,1957396,2077851.5,2228644.7,2390102,2725365.5,3147844.3,3607372,4239281.5,5190061,9422370.8],
tooltip: {
pointFormat: '<b>{point.y:f} Bytes</b>'
}
}]
});
//TRIED THIS AND series.data[24] - essentially the 24th bar should be highlighted
col_chart.series[0].data[24].update({color:'red'});
You need to access the highcharts off of your jquery object:
col_chart.highcharts().series[0].data[24].update({
color: 'red'
});
For clarity
In your example, the following is true:
console.log(col_chart instanceof jQuery); // true
From the highcharts source:
/**
* Register Highcharts as a plugin in jQuery
*/
if (win.jQuery) {
win.jQuery.fn.highcharts = function () {
var args = [].slice.call(arguments);
if (this[0]) { // this[0] is the renderTo div
// Create the chart
if (args[0]) {
new Highcharts[ // eslint-disable-line no-new
isString(args[0]) ? args.shift() : 'Chart' // Constructor defaults to Chart
](this[0], args[0], args[1]);
return this;
}
// When called without parameters or with the return argument, return an existing chart
return charts[attr(this[0], 'data-highcharts-chart')];
}
};
}
Meaning, highcharts() is a plugin for jQuery, so you can access it (assuming it's been attached to the dom element already, as in your case above) by calling highcharts off a jQuery selector instance.

c3 JS scroll bar jumping when loading new data

We are using c3 as a wrapper around d3 javascript charting library. You can see even in their own demo when the data is updated the scroll bar flickers momentarily.
This isn't a problem when there is already a scrollbar on the page as it is in their case. But if the page is smaller the addition and sudden removal or the scrollbar can be jarring.
We aren't doing anything wildly different than they do in their examples. The mystery is why the scrollbars jump. Any ideas? If you want to look at my code it is blow:
Data is getting passed to our AngularJS Directive using SignalR
$scope.$watch('data', function () {
normalizedData = normalize($scope.data);
chart.load({
columns: getChartDataSet(normalizedData)
});
});
After we take the normalized data it simply gets set into an array then passed to C3
var chart = c3.generate({
bindto: d3.select($element[0]),
data: {
type: 'donut',
columns: [],
colors: {
'1¢': '#2D9A28',
'5¢': '#00562D',
'10¢': '#0078C7',
'25¢': '#1D3967',
'$1': '#8536C8',
'$5': '#CA257E',
'$10': '#EC3500',
'$20': '#FF7D00',
'$50': '#FBBE00',
'$100': '#FFFC43'
}
},
tooltip: {
show: true
},
size: {
height: 200,
width: 200
},
legend: {
show: true,
item: {
onmouseover: function (id) {
showArcTotal(id);
},
onmouseout: function (id) {
hideArcTotal();
}
}
},
donut: {
width: 20,
title: $scope.label,
label: {
show: false,
format: function(value, ratio, id) {
return id;
}
}
}
});
body > svg { height: 0; } did not help me. But I had experimented a bit and found a solution:
body > svg {
position: absolute;
z-index: -10;
top: 0;
}
Unfortunately, this method can't fix the issue if a window's height is too small.
Also, you can get rid of jumping by adding scrollbar by default:
body {
overflow-y: scroll;
}
When C3 draws the chart it appends an SVG at the bottom of the <body> element, even with `style="visibility:hidden". I just added a CSS class
body > svg { height:0px !important }
That fixed the issue for me.

Categories

Resources