Using various posts/questions in SO as reference I created a scatter highchart jsfiddle
xAxis: {
opposite:true,
type: 'datetime',
gridLineWidth: 1,
gridLineDashStyle: 'ShortDot',
gridLineColor:'black',
alternateGridColor: 'lightgrey',
tickInterval: 3 * 30 * 24 * 3600 * 1000, // 1 quarter
labels: {
//align: "left",
//padding:200,
formatter: function () {
var s = "";
if (Highcharts.dateFormat('%b', this.value) == 'Jan') {
s = s + "Q1"
};
if (Highcharts.dateFormat('%b', this.value) == 'Apr') {
s = s + "Q2"
};
if (Highcharts.dateFormat('%b', this.value) == 'Jul') {
s = s + "Q3"
};
if (Highcharts.dateFormat('%b', this.value) == 'Oct') {
s = s + "Q4"
};
s = s + " " + Highcharts.dateFormat('%Y', this.value);
return s;
}
},
plotLines: [{
color: 'red', // Color value
value: now, // Value of where the line will appear
width: 2, // Width of the line
label: {
text: 'Now',
align: 'center',
verticalAlign: 'bottom',
y: +20,
rotation: 0
}
}]
},
But I'm struck with having the X-axis label positioned near the tick.
How to move to middle of the grid?
Is there anyway I can achieve the below?
I tried align, padding but didn't help. When the timeline increases I should still have the labels positioned in the middle.
should I do something with tickInterval? It might be a simple property I'm missing.
I found this link jsfiddle which addresses my concern but with 2 x-axis and I'm populating the data from a list.
I implemented Christopher Cortez' solution found here:
However, also changed to to fire on the highcharts load event, rather than the callback, and I've changed it to be recalled when the HighCharts redraw event is fired, so that they stay aligned when the page is resized.
$('#container').highcharts({
chart: {
defaultSeriesType: 'scatter',
events: {
load: centerLabels,
redraw: centerLabels
}
},
/* ... all the other options ...*/
});
Where
function centerLabels(chart) {
var $container = $(chart.target.container);
var axes = chart.target.axes;
var $labels = $container.find('.highcharts-axis-labels .timeline_label');
var $thisLabel, $nextLabel, thisXPos, nextXPos, delta, newXPos;
$labels.each(function () {
$thisLabel = $(this).parent('span');
thisXPos = parseInt($thisLabel.css('left'));
$nextLabel = $thisLabel.next();
// next position is either a label or the end of the axis
nextXPos = $nextLabel.length ? parseInt($nextLabel.css('left')) : axes[0].left + axes[0].width;
delta = (nextXPos - thisXPos) / 2.0;
newXPos = thisXPos + delta;
// remove the last label if it won't fit
if ($nextLabel.length || $(this).width() + newXPos < nextXPos) {
$thisLabel.css('left', newXPos + 'px');
} else {
$thisLabel.remove();
}
});
}
JSFiddle
Related
I want to hide some bars that dont have a useful value ("0") but I am not sure about how to do it, since I cannot use any Chart.Js plugins and I cannot just hide the whole Dataset since I am using a SQL Table to get the Values "into" Chart.Js.
I would like to hide the 0-Values on the chart
Note: I only want to hide the Value not the whole bar itself.
//coloring the bars from light blue (receiverColor) to dark blue (performancePayColor)
var receiverColor = "#7ec0ee";
var timeWageColor = "#368DD6";
var performancePayColor = "#214B7D";
//removing of chart when openening a new OE
// var chartContainer = document.getElementById('chartContainer');
// chartContainer.innerHTML = "";
for (var x = 0; x < charts.length; x++) {
var chartData = charts[x];
var sumChartValues = [];
//adding up the Arrays
for (var i = 0; i < chartData.GE.length; i++) {
sumChartValues.push(chartData.GE[i] + chartData.LL[i] + chartData.ZL[i]);
}
//creating a TempChart canvas with same values as chart1 to be able to delete Chart1 canvas
var chart = document.createElement("canvas");
chart.id = "Chart" + x;
//make the Charts responsive
var width = $(window).width();
var paddingL; //= 0;
var paddingR; //= 0;
if (width >= 2560) {
// chart.height = height / 2.2;
chart.width = width / 2.2;
paddingL = 25;
paddingR = 25;
}
else if (width >= 1440) {
// chart.height = height / 2.2;
chart.width = width / 2.2;
paddingL = 25;
paddingR = 25;
}
else if (width >= 1024) {
// chart.height = height / 2.2;
chart.width = width / 2.5;
paddingL = 20;
paddingR = 20;
}
else if (width >= 768) {
// chart.height = height / 2;
chart.width = width / 1.25;
paddingL = 30;
paddingR = 30;
}
else {
//chart.height = height;
chart.width = width / 1.25;
}
document.getElementById("chartContainer").appendChild(chart);
//var originalOnClick = Chart.defaults.global.legend.onClick;
new Chart("Chart" + x, {
//barchart is what we use normally, change type if something else is needed.
type: "bar",
data: {
//labels is for the Values of the first x-Axis (we normally use KW)
labels: chartData.KW,
//datasets are the Bars that are displayed on the charts (each Dataset is one different Bar part a.e. a different color and value on one bar and KW)
datasets: [
{
label: "Leistungslohn",
backgroundColor: performancePayColor,
data: chartData.LL,
xAxesID: "lowerXAxis",
},
{
label: "Zeitlohn",
backgroundColor: timeWageColor,
data: chartData.ZL,
xAxesID: "lowerXAxis"
},
{
label: "Gehaltsempfänger",
backgroundColor: receiverColor,
data: chartData.GE,
xAxesID: "lowerXAxis"
}]
},
options: {
//Set the Chart responsive
maintainAspectRatio: true,
responsive: false,
layout: {
padding: {
left: paddingL,
right: paddingR
}
},
//Dynamic Chart title display on top of the Legend
title: {
size: '200',
display: true,
text: chartData.O_Long[0] + ' (' + chartData.O_Short[0] + ')',
},
//Enables legend Over the bars (Leistungslohn, Zeitlohn and Gehaltsempfänger)
legend: {
display: true,
position: 'bottom'
},
//Enables tooltips when hovering over bars
tooltips: {
enabled: true
},
hover: {
//disables number flickering when moving mouse across bars
animationDuration: 0,
//disables hover effect (change of bar color when hovering over a bar)
mode: null
},
animation: {
//duration = build up time for the chart in ms, 0 = no visible animation.
duration: 0,
onComplete: function () {
//Shows bar values on top of each bar and Formats them
var ctx = this.chart.ctx;
var legendItems = this.chart.legend.legendItems;
var max = this.chart.scales['y-axis-0'].max;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, 'bold', Chart.defaults.global.defaultFontFamily);
//coloring the values white
ctx.fillStyle = this.chart.config.options.defaultFontColor = "#FFF";
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
//Calculation of the required scale
var chartHeight = this.height;
this.data.datasets.forEach(function (dataset) {
var index = -1;
for (var i = 0; i < legendItems.length; i++) {
if (legendItems[i].text == dataset.label && !legendItems[i].hidden) {
index = i;
break;
}
}
if (index != -1) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model;
ctx.fillText(dataset.data[i], model.x, model.y + (dataset.data[i] / max * ((chartHeight / 2) - 50)));
}
}
})
//hide zero values
},
},
scales: {
xAxes: [{
//Stacks mutliple Datasets on top of each other
ID: "lowerXAxis",
stacked: true,
gridLines: {
//removes x-Axes grid lines
display: false
}
},
//Second x-Axes:
{
ID: "upperXAxis",
position: "top",
offset: true,
gridLines: {
//removes x-Axes grid lines
display: false
}
}],
yAxes: [{
//Stacks mutliple Datasets on top of each other
stacked: true,
scaleLabel: {
offset: true,
display: true,
labelString: 'Anzahl Personen',
},
ticks: {
//Makes the chart start at 0 to prevent negative values and to prevent formatting errors
beginAtZero: true,
}
}]
}
}
});
}
}```
Inside the options.animation.onComplete callback function, you draw the text using ctx.fillText(...). Simply don't draw the text in case the value is zero.
To do so, you should add an if statement as follows:
if (dataset.data[i] > 0) {
ctx.fillText(dataset.data[i], model.x, model.y + (dataset.data[i] / max * ((chartHeight / 2) - 50)));
}
In case the values are defined as strings as your question could suggest, the if statement may have to be changed slightly (i.e if (dataset.data[i] != '0') {).
This solution works for me (using ChartJS version 4.2):
Prerequisites: OP says he cannot use plugins, so step 3 of this solution would need to be modified if that is still the case - however if you can use the newly separated label plugins for version 4.0 and higher of ChartJS, the below will work (but you have to import the below plugin CDN below your ChartJS import). Use of this plugin is now the officially supported means of displaying labels on top of charts for v4 and above of ChartJS.
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels#2.0.0"></script>
First define a set of formatter functions (I have 4 such functions: one simply returns the value as the label, another returns the value formatted as a percentage; the other two do the same, but return null when the value is equal to 0. We use these for chart types with touching data regions (e.g. stacked bars, pies and donuts).
const absoluteFormatter = (value) => {
return value;
}
const percentageFormatter = (value) => {
return Math.round(value) + '%';
}
const absoluteFormatterTouching = (value) => {
if (value > 0) {
return value;
} else {
return null;
}
}
const percentageFormatterTouching = (value) => {
if (value > 0) {
return Math.round(value) + '%';
} else {
return null;
}
}
Note the above formatters won't work if you deal with any negative values or string values, so you'd need to adjust them accordingly in that case.
Then when working with plugins, we use a config file like the below for a stacked bar chart:
const absoluteFormatterTouching = (value) => {
if (value > 0) {
return value;
} else {
return null;
}
}
const labels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];
const data = {
labels: labels,
datasets: [
{
label: 'Dataset 1',
data: [0,4,5,2,10,20,30],
backgroundColor: 'rgb(255, 99, 132)',
},
{
label: 'Dataset 2',
data: [12,19,3,0,2,3,10],
backgroundColor: 'rgb(54, 162, 235)',
},
{
label: 'Dataset 3',
data: [11,20,30,4,51,60,0],
backgroundColor: 'rgb(255, 205, 86)',
},
]
};
const config = {
type: "bar",
data: data,
plugins: [ChartDataLabels],
options: {
plugins: {
title: {
display: true,
text: "Chart.js Bar Chart - Stacked",
},
datalabels: {
display: true,
formatter: absoluteFormatterTouching,
},
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true,
},
},
},
};
const ctx = document.getElementById('myChart').getContext('2d');
const myChart = new Chart(ctx, config);
Note the inclusion of the relevant formatter within the chart config.options.plugins.datalabels.formatter: absoluteFormatterTouching. This is where you place a pointer to the formatter you wish to use. You also have to include the line config.plugins: [ChartDataLabels] to have access to the imported DataLabels plugin from the CDN.
I have a high chart on my web page which is a line chart. It has a functionality to zoom and I capture the zoom event using chart.events.selection. That all is working fine.
But I want to continuously capture the selection event (i.e. basically click and drag event) to show a tooltip in the beginning and end of the selection to show the time user has selected. Couldn't find in high charts documentation. Any help would be appreciated.
Here is my current code to capture selection event:
$(obj.id).highcharts({
chart: {
type: 'areaspline',
backgroundColor:"rgba(0,0,0,0)",
zoomType:"x",
events: {
selection: function(event){
if(!event.xAxis)
return;
.....
Updated:
Updated example in which labels following selection marker: http://jsfiddle.net/pq0wn0xx/2/
I do not think there is a drag event (not for points), but you can wrap drag pointer's method.
Highcharts.wrap(Highcharts.Pointer.prototype, 'drag', function (p, e) {
p.call(this, e);
var H = Highcharts,
chart = this.chart,
selectionMarker = this.selectionMarker,
bBox,
xAxis,
labelLeft,
labelRight,
labelY,
attr,
css,
timerLeft,
timerRight;
if (selectionMarker) {
if (!chart.customLabels) {
chart.customLabels = [];
}
bBox = selectionMarker.getBBox();
xAxis = chart.xAxis[0];
labelLeft = chart.customLabels[0];
labelRight = chart.customLabels[1];
labelY = chart.plotTop + 10;
if (!labelLeft || !labelRight) {
attr = {
fill: Highcharts.getOptions().colors[0],
padding: 10,
r: 5,
zIndex: 8
};
css = {
color: '#FFFFFF'
};
labelLeft = chart.renderer.label('', 0, 0).attr(attr).css(css).add();
labelRight = chart.renderer.label('', 0, 0).attr(attr).css(css).add();
chart.customLabels.push(labelLeft, labelRight);
}
clearTimeout(timerLeft);
clearTimeout(timerRight);
labelLeft.attr({
x: bBox.x - labelLeft.getBBox().width,
y: labelY,
text: 'min: ' + H.numberFormat(xAxis.toValue(bBox.x), 2),
opacity: 1
});
labelRight.attr({
x: bBox.x + bBox.width,
y: labelY,
text: 'max: ' + H.numberFormat(xAxis.toValue(bBox.x + bBox.width), 2),
opacity: 1
});
timerLeft = setTimeout(function () {
labelLeft.fadeOut();
}, 3000);
timerRight = setTimeout(function () {
labelRight.fadeOut();
}, 3000);
}
});
Old answer:
The example from the official API
can be extended to what you need.
The code and the example on jsfiddle are below:
function positionLabels(e, chart) {
if (!chart.customLabels) {
chart.customLabels = [];
}
var labelLeft,
labelRight,
attr,
css,
xAxis,
xMin,
xMax,
yAxis,
yMin,
yMax,
yMiddle,
timerLeft,
timerRight;
if (!e.resetSelection) {
labelLeft = chart.customLabels[0];
labelRight = chart.customLabels[1];
if (!labelLeft || !labelRight) {
attr = {
fill: Highcharts.getOptions().colors[0],
padding: 10,
r: 5,
zIndex: 8
};
css = {
color: '#FFFFFF'
};
labelLeft = chart.renderer.label('', 0, 0).attr(attr).css(css).add();
labelRight = chart.renderer.label('', 0, 0).attr(attr).css(css).add();
chart.customLabels.push(labelLeft, labelRight);
}
clearTimeout(timerLeft);
clearTimeout(timerRight);
xAxis = e.xAxis[0].axis;
xMin = e.xAxis[0].min;
xMax = e.xAxis[0].max;
yAxis = chart.yAxis[0];
yMin = yAxis.min;
yMax = yAxis.max;
yMiddle = (yMax - yMin) * 0.95;
labelLeft.attr({
x: xAxis.toPixels(xMin) - labelLeft.getBBox().width,
y: yAxis.toPixels(yMiddle),
text: 'min: ' + Highcharts.numberFormat(xMin, 2),
opacity: 1
});
labelRight.attr({
x: xAxis.toPixels(xMax),
y: yAxis.toPixels(yMiddle),
text: 'max: ' + Highcharts.numberFormat(xMax, 2),
opacity: 1
});
timerLeft = setTimeout(function () {
labelLeft.fadeOut();
}, 2000);
timerRight = setTimeout(function () {
labelRight.fadeOut();
}, 2000);
}
}
example: http://jsfiddle.net/pq0wn0xx/
I want to show "N/A" in a column chart, when the data is null.
This is what I am currently trying to do:
http://jsfiddle.net/90amxpc1/1/
dataLabels: {
formatter: function () {
if (this.y == null) {
return "N/A";
}
else {
return this.y;
}
}
}
But, its not working. What I want to achieve is similar to the accepted Answer to this Question:
Highcharts: Returning N/A value instead of 0% in data label
or this fiddle: http://jsfiddle.net/v5vJR/3/
I tried modifying it for the column chart, but it doesnt works out.
There is just different logic for bar (inverted chart) and column chart, example for column:
formatter: function () {
if (this.y == null) {
var chart = this.series.chart,
categoryWidth = chart.plotWidth / chart.xAxis[0].categories.length,
offset = (this.point.x) * categoryWidth + categoryWidth / 2,
text = chart.renderer.text('N/A', -999, -999).add();
text.attr({
x: chart.plotLeft + offset - text.getBBox().width / 2, //center label
y: chart.plotTop + chart.plotHeight - 8 // padding
});
} else {
return this.y;
}
},
Live demo: http://jsfiddle.net/90amxpc1/4/
Note:
You may want to store text variable to remove that text when hiding series or zooming into the chart.
My chart displays columns with min and max values:
plotOptions: {
series: {
dataLabels: {
enabled: true,
color: 'auto'
}
}
}
JSFiddle here
How can I display additional labels with average values in the middle of the corresponding columns?
You can use formatter in tooltip and plotOptions.series.dataLabels and use this.point.high and this.point.low to get the high and low of the serie and calculate the average:
formatter : function() {
var res = "";
if(this.point.high == this.y)
res += "Average: " + (this.point.high + this.point.low)/2 + "<br><br><br><br>";
res += this.y;
return res;
}
Here's the DEMO
EDIT:
You cannot add another dataLabel in your chart. But, I managed to add the Average to one of your dataLabels and then using jQuery I changed it's position considering the amount of each average:
$.each($( "tspan:contains('Average')" ),
function(i, t) { t.setAttribute('dy', parseInt(t.textContent.slice(9))/135); });
Please see this DEMO
In general, it's not supported. However, you can extend Highcharts, so you will render any text you want to. Simple example for you: http://jsfiddle.net/8e0m2w5h/8/
(function (H) {
// wrap drawDataLabels, which will draw dataLabels when required
H.wrap(H.seriesTypes.columnrange.prototype, 'drawDataLabels', function (p) {
var x, y, series = this;
p.call(series);
// loop over all points to generate or update your label
H.each(series.data, function (point, i) {
if (point.dataLabel && point.dataLabelUpper) {
y = (point.plotLow + point.plotHigh) / 2;
x = point.shapeArgs.x + point.shapeArgs.width / 2;
if (point.dataLabelMiddle) {
// animate dataLabel after redraw/resize etc.
point.dataLabelMiddle.animate({
x: x,
y: y
});
} else {
point.dataLabelMiddle = series.chart.renderer.text((point.low + point.high) / 2, x, y).attr({
align: 'center' //align dataLabel in the middle
}).add(series.dataLabelsGroup);
}
}
});
});
})(Highcharts);
I'm doing a responsive pie chart which holds title in centered position inside it.I've used,
title: {
text: "",
margin: 0,
y:0,
x:0,
align: 'center',
verticalAlign: 'middle',
},
but it's not perfectly centered inside the chart.Any suggestions would be appreciated.Thank you in advance.
Here's the Link : http://jsfiddle.net/LHSey/128/
Better is remove title and use renderer which allows to add custom text, which can be repositioned each time (when you redraw chart). Only what you need is catch this event.
function addTitle() {
if (this.title) {
this.title.destroy();
}
var r = this.renderer,
x = this.series[0].center[0] + this.plotLeft,
y = this.series[0].center[1] + this.plotTop;
this.title = r.text('Series 1', 0, 0)
.css({
color: '#4572A7',
fontSize: '16px'
}).hide()
.add();
var bbox = this.title.getBBox();
this.title.attr({
x: x - (bbox.width / 2),
y: y
}).show();
}
chart:{
events: {
load: addTitle,
redraw: addTitle,
},
}
Example: http://jsfiddle.net/LHSey/129/
We can customize any properties of title using chart.setTitle() as shown below.I've added a title and set useHTML property to true.
chart: {
events: {
load: function () {
this.setTitle({text: 'Title',useHTML:true});
}
}
}