xAxis Image Disappears in Highcharts After First Refresh - highcharts

I have a page with a variety of select menus. The select options are used in an ajax call to build a Highcharts bar graph. Every time a filter changes, the graph gets recreated. I did this instead of updating the series data, because in the past I have noticed that destroying and recreating was more efficient than updating.
I want images to show on the x-axis, so I used a nice little trick of creating two x axes, used formatter to return an image on the first axis, and linked the second axis to the first. This works on first refresh. However, every time the chart gets recreated thereafter, the image disappears. I checked my console and I don't see any errors.
And idea of what's going on here?
/**
* Whenselection changes
*/
$(document).on('change', '.filter', function(){
getChartData($params)
})
});
/**
* API call to get data that will populate charts.
* #param {obj} params
*/
function getChartData(params)
{
//Get chart data
$.ajax({
url: apiURL + '/chartdata/',
data: params,
dataType: 'json',
cache: false,
success: function(data) {
initChart(data[0]);
}
});
function initChart(chartData)
{
var chart = Highcharts.chart('container', {
chart: {
type: 'bar',
backgroundColor: 'transparent', //#E8EAF6',
height: '23%',
marginLeft: 35
},
title: {
text: null
},
xAxis: {
categories: [category1, category2],
lineColor: 'transparent',
min: 0,
tickColor: 'transparent',
title: {
text: null
},
labels: {
x: -35,
useHTML: true,
overflow: 'allow',
formatter: function () {
if(this.isFirst == true)
return '<img src="../assets/img/nat-jr-grad-gold.png"><br/>';
else
return '<img src="../assets/img/nat-jr-grad-purple.png"><br/>';
}
}
},
yAxis: {
min: 0,
title: {
useHTML: true,
text: null
},
labels: {
enabled: false
},
lineWidth: 0,
minorGridLineWidth: 0,
gridLineWidth: 0,
lineColor: 'transparent',
gridLineColor: 'transparent',
},
legend: {
enabled: false
},
series: [{
name: category1,
data: [{name: category1, y:Math.round(chartData.p_grad_nongap * 100), {y: null}],
}, {
name: category2,
data: [null, {name: category2, y: Math.round(chartData.p_grad_gap * 100)}]
}]
});
}

I reproduced your problem on a simplified example: http://jsfiddle.net/BlackLabel/sm2r684n/
For the first time, the image is loaded asynchronously and the chart does not take it into account when calculating the margins. Every next time the result is different, so you should wait until the picture is loaded:
var img = new Image();
img.onload = function() {
initChart();
}
img.src = "https://www.highcharts.com/samples/graphics/sun.png";
Live demo: http://jsfiddle.net/BlackLabel/m09ok2cg/

I think ppotaczek had the cortrect root cause of the problem; the image is loaded asynchronously and the chart does not take it into account when calculating the margins. His suggestion used setTimeout function to continuously redraw the graph, which is rather inefficient. My work-around for this was to just add the images as avg elements using chart.renderer after the chart was created.
/* Render X-Axis images */
chart.renderer.image('../assets/img/img1.png', 0, 40, 32, 36)
.css({
class: 'icon-img',
zIndex: 10
})
.add();
chart.renderer.image('../assets/img/img2.png', 0, 130, 32, 36)
.css({
class: 'icon-img',
zIndex: 10
})
.add();

Related

HighChart data table keeps adding number of tables at bottom on every call

I am using HighChart library to show Pie Chart on my page, I am keeping showTable always true to show every time.
The problem is whenever data binds to charts using JQuery Ajax post dynamically, i see proper data in chart but also it keeps adding new data table everytime at the bottom. E.g. First time there will be only one table, after another call there will be two tables added and so on. Why Hightcharts not updating the same table again or is there a way to kill any existing table.?
hc = Highcharts.chart(chartName, {
chart: {
type: 'pie',
options3d: {
enabled: false,
alpha: 45
},
style: {
color: '#575962'
}
},
title: {
text: title,
style: {
color: '#575962',
fontweight: '400'
}
},
subtitle: {
text: 'Current Month(Top 10)'
},
plotOptions: {
pie: {
innerSize: 100,
depth: 45,
allowPointSelect: true,
cursor: 'pointer',
dataLabels: {
enabled: false
},
showInLegend: true
}
},
credits: {
enabled: false
},
exporting: {
showTable: true
},
series: [{
name: 'Price',
data: [],
dataLabels: {
formatter: function () {
//const point = this.point;
//return '<span style="color: #575962;font-size:11px;font-weight: 400">' +
// point.name + ': $' + point.y + '</span>';
}
}
}]
});
Why Hightcharts not updating the same table again or is there a way to kill any existing table.?
I have achieved this by removing the series on each call and adding them again with new data. Hope this helps if anyone else looking and couldn't find any other answer.
//Removing Series
while (hc.series.length > 0) {
hc.series[0].remove(false);
}
//Adding new Series
hc.addSeries({
name: 'Price',
data: [],
dataLabels: {
formatter: function () {
const point = this.point;
return '<span style="color: #575962;font-size:11px;font-weight: 400">' +
point.name + ': $' + point.y + '</span>';
}
}
});
//Binding Series data
var res = result.Data.slice(0, 10);
res.forEach(function (element, index) {
hc.series[0].addPoint({
name: element.ResourceGroupName,
y: element.TotalMonthlyPrice,
});
});

Negative bar chart of highcharts in chartjs

I been playing with Chartjs to generate something like highcharts but couldn't bring it close. The application is finished and it seems a waste to buy highcharts, migrate from chartjs for this single chart.
Is there a way Chartjs can output something like that? I did try creating two horizontal bar charts side-by-side in separate canvas; while i saw the output; the behavior is not what I want.
You could use a horizontal stacked barchart with two datasets. Add negative values to the dataset on the left (male) but display the absolute value in the callbacks of the xAxis and the tooltip.
var ctx = document.getElementById('myChart').getContext('2d');
var data = {
labels: ["20-30", "10-20", "0-10"],
datasets: [{
label: "Male",
backgroundColor: "rgba(54, 162, 235, 0.2)",
borderColor: "rgb(54, 162, 235)",
borderWidth: 2,
data: [-65, -59, -20],
}, {
label: "Female",
backgroundColor: "rgba(255,99,132,0.2)",
borderColor: "rgba(255,99,132,1)",
borderWidth: 2,
data: [72, 45, 18],
},
]
};
var myBarChart = new Chart(ctx, {
type: 'horizontalBar',
data: data,
options: {
scales: {
yAxes: [{
stacked: true
}],
xAxes: [{
ticks: {
callback: function(value, index, values) {
return Math.abs(value);
}
}
}]
},
tooltips: {
callbacks: {
label: function(tooltipItems, data) {
return data.datasets[tooltipItems.datasetIndex].label + ": " + Math.abs(tooltipItems.xLabel);
}
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script>
<canvas id="myChart"></canvas>

dashboard-style page with Highcharts graphs

I would like to make something like a dashboard (kinda like the one that you see in many financial site), using Highcharts.
I've got the hang of adding 1 chart to a page, using a container, so I told myself that many containers, duplicating the code for one graph, will do; but I can't get it to work.
I have at least 8 graph, and I would like to organize them either in 2X4 arrangement, or just stacked on top of each other.
Mainly my confusion is coming from the fact that I need a general options section (to group common options), but I also need to customize the graphs, and I need to load data from CSV, so the order in which you do what, is causing me some problems.
I tried to follow an example here, where it was suggested to use setOptions and jQuery.extend, but I was not successful in making it work.
Is there an example that show a skeleton of the webpage, so I can see where to put each function, in which order and what kind of code do I have to put in?
Here you can find example how to add multiple chart like a dashboard: http://www.highcharts.com/demo/sparkline
And copy&paste code:
$(function () {
/**
* Create a constructor for sparklines that takes some sensible defaults and merges in the individual
* chart options. This function is also available from the jQuery plugin as $(element).highcharts('SparkLine').
*/
Highcharts.SparkLine = function (options, callback) {
var defaultOptions = {
chart: {
renderTo: (options.chart && options.chart.renderTo) || this,
backgroundColor: null,
borderWidth: 0,
type: 'area',
margin: [2, 0, 2, 0],
width: 120,
height: 20,
style: {
overflow: 'visible'
},
skipClone: true
},
title: {
text: ''
},
credits: {
enabled: false
},
xAxis: {
labels: {
enabled: false
},
title: {
text: null
},
startOnTick: false,
endOnTick: false,
tickPositions: []
},
yAxis: {
endOnTick: false,
startOnTick: false,
labels: {
enabled: false
},
title: {
text: null
},
tickPositions: [0]
},
legend: {
enabled: false
},
tooltip: {
backgroundColor: null,
borderWidth: 0,
shadow: false,
useHTML: true,
hideDelay: 0,
shared: true,
padding: 0,
positioner: function (w, h, point) {
return { x: point.plotX - w / 2, y: point.plotY - h};
}
},
plotOptions: {
series: {
animation: false,
lineWidth: 1,
shadow: false,
states: {
hover: {
lineWidth: 1
}
},
marker: {
radius: 1,
states: {
hover: {
radius: 2
}
}
},
fillOpacity: 0.25
},
column: {
negativeColor: '#910000',
borderColor: 'silver'
}
}
};
options = Highcharts.merge(defaultOptions, options);
return new Highcharts.Chart(options, callback);
};
var start = +new Date(),
$tds = $("td[data-sparkline]"),
fullLen = $tds.length,
n = 0;
// Creating 153 sparkline charts is quite fast in modern browsers, but IE8 and mobile
// can take some seconds, so we split the input into chunks and apply them in timeouts
// in order avoid locking up the browser process and allow interaction.
function doChunk() {
var time = +new Date(),
i,
len = $tds.length;
for (i = 0; i < len; i++) {
var $td = $($tds[i]),
stringdata = $td.data('sparkline'),
arr = stringdata.split('; '),
data = $.map(arr[0].split(', '), parseFloat),
chart = {};
if (arr[1]) {
chart.type = arr[1];
}
$td.highcharts('SparkLine', {
series: [{
data: data,
pointStart: 1
}],
tooltip: {
headerFormat: '<span style="font-size: 10px">' + $td.parent().find('th').html() + ', Q{point.x}:</span><br/>',
pointFormat: '<b>{point.y}.000</b> USD'
},
chart: chart
});
n++;
// If the process takes too much time, run a timeout to allow interaction with the browser
if (new Date() - time > 500) {
$tds.splice(0, i + 1);
setTimeout(doChunk, 0);
break;
}
// Print a feedback on the performance
if (n === fullLen) {
$('#result').html('Generated ' + fullLen + ' sparklines in ' + (new Date() - start) + ' ms');
}
}
}
doChunk();
});
For a more simplistic start to this problem, take a look at this example:
http://jsfiddle.net/jlbriggs/4GaVj/
It's a very simple set up that defines data arrays first (you can do this as part of your CSV parsing), then defines global options via Highcharts.setOptions(), and then defines the individual charts.
There are several different ways to go about this, from this simple example up to more complex, flexible and dynamic approaches. But if you're looking to start with the basics, this should help.

Disable markers if data > 1 in highcharts

How to make markers disabled while data > 1 after insert new data? It's really confusing me..
In this case, I want to make a Grow Chart (chart example source: CDC-Weight for age percentiles)
Should I use this?
if(dataChartWeight.length > 1)
{
chart.series[19].update({
marker: {
enabled: false
}
}, true);
}
if(dataChartLength.length > 1)
{
chart.series[18].update({
marker: {
enabled: false
}
}, true);
}
Sebastian's answer is good but it can become a little more complicated to fix if you are using multiple series, using a custom tooltip, or when the chart is redrawn. In stead of using destroy(); on the graphic, I used hide();
I wrapped it up in a function and then called the function on first load, and also on the redraw event. It loops through each series first, then loops through the data to "hide" the markers with zero as value.
$(function () {
function hideZeroDots(chart) {
$.each(chart.series, function (series) {
$.each(chart.series[series].data, function () {
if(this.y === 0 && this.graphic) {
this.graphic.hide();
}
});
});
}
$('#container').highcharts({
title: {
text: 'Hide Zero Dots'
},
chart: {
events: {
redraw: function () {
hideZeroDots(this);
}
}
},
tooltip: {
useHTML: true,
hideDelay: 0,
shared: true,
backgroundColor: '#fff',
borderColor: '#eee',
shadow: false,
crosshairs: {
width: 2,
color: 'gray',
dashStyle: 'shortdot'
}
},
series: [{
name: 'responses',
data: [0, 0, 0, 0, 0, 0, 36.5, 33.3, 0, 23.9, 0]
}, {
name: 'views',
data: [0, 0, 0, 0, 0, 0, 26.5, 23.3, 0, 13.9, 0]
}]
}, hideZeroDots);
});
See Demo
Generally marker cannot be disabled for single point, but you can use workaround with hiding SVG element.
$.each(chart.series[0].data, function (point, i) {
if(this.y <10) {
this.graphic.destroy();
}
});
See example: http://jsfiddle.net/Y5HzV/2/
Instead of trying to disable the marker, you can hide it...
marker: {
radius:0,
lineWidth:0,
}

Auto chart height

The default highstock chart has height = 400px.
How can height chart be set for auto size based on chart axis and its sizes ?
See the example bellow, the navigation bar is over the volume panel.
http://jsfiddle.net/BYNsJ/
I know that I can set the height for the div but I have a solution that insert/remove axis/series dynamically in the chart and would be nice an auto height chart.
The example is the same Candlestick/Volume demo from Highchart site, but without height property in the div container.
// split the data set into ohlc and volume
var ohlc = [],
volume = [],
dataLength = data.length;
for (i = 0; i < dataLength; i++) {
ohlc.push([
data[i][0], // the date
data[i][1], // open
data[i][2], // high
data[i][3], // low
data[i][4] // close
]);
volume.push([
data[i][0], // the date
data[i][5] // the volume
])
}
// set the allowed units for data grouping
var groupingUnits = [[
'week', // unit name
[1] // allowed multiples
], [
'month',
[1, 2, 3, 4, 6]
]];
// create the chart
$('#container').highcharts('StockChart', {
rangeSelector: {
selected: 1
},
title: {
text: 'AAPL Historical'
},
yAxis: [{
title: {
text: 'OHLC'
},
height: 200,
lineWidth: 2
}, {
title: {
text: 'Volume'
},
top: 300,
height: 100,
offset: 0,
lineWidth: 2
}],
series: [{
type: 'candlestick',
name: 'AAPL',
data: ohlc,
dataGrouping: {
units: groupingUnits
}
}, {
type: 'column',
name: 'Volume',
data: volume,
yAxis: 1,
dataGrouping: {
units: groupingUnits
}
}]
});
});
});
Regards.
here is one example which is resize chart according screen.
http://jsfiddle.net/davide_vallicella/LuxFd/2/
Just don't set the height property in HighCharts and it will handle it dynamically for you so long as you set a height on the chart's containing element. It can be a fixed number or a even a percent if position is absolute.
http://api.highcharts.com/highcharts/chart.height
By default the height is calculated from the offset height of the containing element
find example here:- http://jsfiddle.net/wkkAd/149/
#container {
width:100%;
height:100%;
position:absolute;
}
hey I was using angular 2+ and highchart/highstock v.5, I think it will work in JS or jQuery also, here is a easy solution
HTML
<div id="container">
<chart type="StockChart" [options]="stockValueOptions"></chart>
</div>
CSS
#container{
height: 90%;
}
TS
this.stockValueOptions = {
chart: {
renderTo: 'container'
},
yAxis: [{
height: '60%'
},{
top: '65%',
height: '35%'
}]
}
Its working, and a easy one. Remove chart height add height in '%' for the yAxis.
Highcharts does not support dynamic height, you can achieve it by $(window).resize event:
$(window).resize(function()
{
chart.setSize(
$(document).width(),
$(document).height()/2,
false
);
});
See demo fiddle here.
You can set chart.height dynamically with chart.update method.
http://api.highcharts.com/highcharts/Chart.update
function resizeChartFromValues (chart) {
const pixelsForValue = 10
const axis = chart.yAxis[0]
chart.update({ chart: { height: (axis.max - axis.min) * pixelsForValue } })
}
const options = {
chart: {
events: {
load () {resizeChartFromValues(this)}
}
},
series: [{
data: [30, 70, 100],
}]
}
const chart = Highcharts.chart('container', options)
setTimeout(() => {
chart.series[0].setData([10, 20, 30])
resizeChartFromValues(chart)
}, 1000)
Live example: https://jsfiddle.net/a97fgsmc/
I had the same problem and I fixed it with:
<div id="container" style="width: 100%; height: 100%; position:absolute"></div>
No special options to the chart. The chart fits perfect to the browser even if I resize it.

Resources