c3 JS scroll bar jumping when loading new data - javascript

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.

Related

ChartJS not rendering after update

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

ZingChart Zoom Date issue

When I do a zoom on a date in the x-axis the chart gets stuck.
This happens only when I work with longs.
With dates it works fine.
I'm new in zing charts and I'm not sure what i am doing wrong
zingchart.exec('myChart', 'zoomtovalues', {
'xmin':1425312000000,
'xmax':1425657600000,
});
and my values are
"values": [
[1425225600000,1],
[1425312000000,1],
[1425398400000,1],
[1425484800000,1],
[1425571200000,1],
[1425657600000,1],
[1425744000000,1],
[1425826800000,1],
[1425913200000,1],
[1425999600000,1]
],
UPDATE
The reason the chart was getting stuck was the step, It works without scrollX
scaleX:{
label:{},
minValue:1425196800000,
step:"day",
transform: {
type: 'date',
all:"%m/%d/%y"
}
},
You have not given much information related to your chart or chart configuration. Based on what you are saying I'm taking a wild guess at what you are asking. If I'm wrong feel feel to follow up.
What you are missing is the scrollX attribute. This enables the scrollbar. Another option is to enable the preview window. Both of these options work in cohesion with zooming.
Information related to scrollX, preview and zooming in general.
https://www.zingchart.com/docs/tutorials/interactive-features/chart-zoom-pan-scroll/
https://www.zingchart.com/docs/api/json-configuration/graphset/scroll-x-scroll-y/
https://www.zingchart.com/docs/api/json-configuration/graphset/preview/
var myConfig = {
type: 'line',
title: {
text: 'After 2 seconds call API method \`zoomtovalues\`'
},
scaleX:{
transform: {
type: 'date',
}
},
scrollX:{},
series: [
{
values: [
[1425225600000,1],
[1425312000000,1],
[1425398400000,1],
[1425484800000,1],
[1425571200000,1],
[1425657600000,1],
[1425744000000,1],
[1425826800000,1],
[1425913200000,1],
[1425999600000,1]
],
}
]
};
setTimeout(function() {
zingchart.exec('myChart', 'zoomtovalues', {
'xmin':1425312000000,
'xmax':1425657600000,
});
}, 2000);
zingchart.render({
id: 'myChart',
data: myConfig,
height: '100%',
width: '100%'
});
html, body {
height:100%;
width:100%;
margin:0;
padding:0;
}
#myChart {
height:100%;
width:100%;
min-height:150px;
}
.zc-ref {
display:none;
}
<!DOCTYPE html>
<html>
<head>
<!--Assets will be injected here on compile. Use the assets button above-->
<script src= "https://cdn.zingchart.com/zingchart.min.js"></script>
</head>
<body>
<div id="myChart"><a class="zc-ref" href="https://www.zingchart.com">Powered by ZingChart</a></div>
</body>
</html>

Prevent click event in angular nvD3 Stacked Area Chart

I'm trying to prevent the default behavior when I click on the angular-nvD3 Stacked Area Chart. I managed to access the onclick function, but I don't know how to prevent the event (modifies the graphic) from happening. I don't want the graphic to change when the user clicks on it.
.js:
$scope.stackedAreaChartOptions = {
chart: {
type: 'stackedAreaChart',
height: 450,
margin : {
top: 20,
right: 20,
bottom: 30,
left: 40
},
x: function(d){return d[0];},
y: function(d){return d[1];},
useVoronoi: false,
clipEdge: true,
duration: 100,
useInteractiveGuideline: true,
xAxis: {
showMaxMin: false,
tickFormat: function(d) {
return d3.time.format('%H:%M')(new Date(d))
}
},
yAxis: {
tickFormat: function(d){
return d3.format(',.2f')(d);
}
},
zoom: {
enabled: false,
scaleExtent: [1, 10],
useFixedDomain: false,
useNiceScale: false,
horizontalOff: false,
verticalOff: true,
unzoomEventType: 'dblclick.zoom'
},
//chart events
stacked: {
dispatch: {
areaClick:
function (t,u){ null; console.log("areaClick");}
,
areaMouseover:
function (t,u){ null; console.log("areaMouseover");}
,
areaMouseout:
function (t,u){null; console.log("areaMouseout");}
,
renderEnd:
function (t,u){null; console.log("renderEnd");}
,
elementClick:
function (t,u){null; console.log("elementClick");}
,
elementMouseover:
function (t,u){null; console.log("elementMouseover");}
,
elementMouseout:
function (t,u){ null;console.log("elementMouseout");}
}
},
controlLabels: {stacked:"Absoluto", expanded:"Relativo"},
controlOptions:
[
"Stacked",
false,
"Expanded"
]
},
title: {
enable: true,
text: '',
css: {
'font-weight': 'bold'
}
},
caption: {
enable: true,
html: 'Visualización por horas de acceso a noticia',
css: {
'text-align': 'center',
'margin': '2px 13px 0px 7px',
'font-style': 'italic'
}
}
};
HTML:
<nvd3 options="stackedAreaChartOptions" data="stackedAreaChartData" api="api"></nvd3>
When I click on the graphic, the messages (console.log) are being shown, but I need to prevent the click event from happening.
I know this is an old question, but I run into this problem for my project and here is how I solved it.
It seems it's not possible to disabled these events using angular-nvd3. You must disable them using NVD3.
Get the chart api object available on your angular-nvd3 chart and disable the events on the chart object binded to this api:
HTML
<nvd3 options="options" data="data" api="chartAPI"></nvd3>
Javascript
$timeout( function() {
if ($scope.chartAPI) {
var chart = $scope.chartAPI.getScope().chart;
chart.stacked.dispatch.on('areaClick.toggle', null);
chart.stacked.dispatch.on('areaClick', null);
}
}, 1000);
I made a timeout be sure to have the chartAPI when doing the changes.
Note : It seems you have to disable these events again when you update or refresh the chart (chart.refresh()).
Working example here: https://codepen.io/mvidailhet/pen/JNYJwx
It seems there is a glitch in the chart update on Codepen, but you get the point :)
Hope it helps!
You were close. CSS pointer-events:none; has the disadvantage that it turns off every pointer event (most importantly hover, mouseenter and mouseout).
So IMHO you should avoid to use it.
Actually you were close. You should not pass an it-does-nothing function but null or undefined instead to options.chart.stacked.dispatch.areaClick. Like this:
//chart events
stacked: {
dispatch: {
areaClick: void 0
}
}
I had this very same problem and spent more than an hour to find it out.
EDIT
Turned out that I was wrong. It solved just because it ran into an error that prevented the event. So you can throw an error and everything is fine... :)
Also found a workaround but that causes memory leak, so I'll not share that.
My solution was: accept that it applies click event and hides all other layers. Too small issue to invest more time and effort in it.

Get element that called qTip?

I would like to get the element that called the qtip popup. In the documentation here it lets you set the position. I want to set the position using a jquery selector like $(this).find('.icon'). The problem is that this isn't the element that called the qtip (I think it's window).
Does anyone know how I can get the handle that called it (like it would if I set target to false)?
Thanks.
In the qtip source code I found this:
if(config.position.target === false) config.position.target = $(this);
Here's the solution I came up with and it seems to work. There probably is a better way to do it if I modified the qtip script but I want to leave that alone.
$(".report-error").qtip(
{
content: 'test content',
position:
{
adjust:
{
screen: true
},
target: false, //it is changed in the 'beforeRender' part in api section. by leaving it as false here in the qtip it will set it as the position I need using $(this)
corner:
{
target: 'bottomMiddle',
tooltip: 'topRight'
}
},
show:
{
when:
{
event: 'click'
}
},
style:
{
name: 'cream',
tip:
{
corner: 'topRight'
},
padding: 0,
width: 400,
border:
{
radius: 5,
width: 0
}
},
hide:
{
when:
{
event: 'unfocus'
}
},
api:
{
beforeRender: function() { //get the position that qtip found with $(this) in it's script and change it using that as the start position
this.options.position.target = $(this.elements.target).find('.icon');
this.elements.target = this.options.position.target; //update this as well. I don't actually know what it's use is
}
}
});
It's working on the site now at http://wncba.co.uk/results/

Adding data to highcharts(Highstock) when user scrolling reaches left end

I'm using highstock on my website. The scroll bar in the navigator of stock chart is drawn using SVG. I want to add more data(via ajax) to the graph when user scrolls to the leftmost end.
I am new to SVG and not sure how to detect that user has scrolled to the end and fire a ajax query based on that. Can anyone help me with this.
Thanks,
Sivakumar.
So, today I had the same problem, and I just found your question.
I don't know if you have any reason for loading the new data only when the user moves the scrollbar, I would recommend to fire the ajax query if the user visualizes the left-most data, instead (that is: scrolling the bar, pressing the left arrow, dragging the area of the navigation chart, and so on).
If this solution applies to you, you can try with something like this:
chart = new Highcharts.StockChart({
chart: {
renderTo: 'chart',
events: {
redraw: function(event) {
if (chart.xAxis) {
var extremes = chart.xAxis[0].getExtremes();
if (extremes && extremes.min == extremes.dataMin) {
console.log("time to load more data!");
}
}
}
}
}, [...]
I solved this problem in the following way.
<script>
import axios from 'axios';
export default {
name: 'Chart',
data() {
return {
chartOptions: {
chart: {
events: {
// we will lose the context of the component if we define a function here
}
},
series: [{
type: 'candlestick',
data: null,
}],
},
};
},
methods: {
onRedraw: function () {
let chart = this.$refs.chart.chart
if (chart.xAxis) {
let extremes = chart.xAxis[0].getExtremes()
console.log(extremes)
if (extremes && extremes.min <= extremes.dataMin) {
console.log("time to load more data!");
}
}
}
},
created() {
axios.get('https://demo-live-data.highcharts.com/aapl-ohlcv.json').then((response) => {
this.chartOptions.series[0].data = response.data
this.chartOptions.series[0].name = 'AAPL'
});
this.chartOptions.chart.events.redraw = this.onRedraw
},
};
</script>
<template>
<highcharts :constructor-type="'stockChart'" :options="chartOptions" ref="chart"></highcharts>
</template>

Categories

Resources