Controlling the the spacing between the series to avoid cluttering in Highcharts - javascript

In the highcharts example above suppose I have 100 series in Bananas which is 1 right now and just one series in Apples ,and if there is a lot of empty space between Bananas and Oranges can we reduce the spacing between them ?
The reason is if there are 100 series in Bananas due to space constraint every line gets overlapped even though there is extra space available between Bananas and Apples . Also is it possible to remove "Oranges" if it doesnt have any series at all and accomodate only series from "Bananas"?

Categories functionality works only for constant tick interval equaled to 1. What you're trying to achieve is having a different space reserved for every category. That means that tick interval has to be irregular.
Unfortunately Highcharts doesn't provide a property to do that automatically - some coding and restructuring the data is required:
All the points have specified x position (integer value)
xAxis.grouping is disabled and xAxis.pointRangeis 1
Following code is used to define and position the labels:
events: {
render: function() {
var xAxis = this.xAxis[0];
for (var i = 0; i < xAxis.tickPositions.length; i++) {
var tickPosition = xAxis.tickPositions[i],
tick = xAxis.ticks[tickPosition],
nextTickPosition,
nextTick;
if (!tick.isLast) {
nextTickPosition = xAxis.tickPositions[i + 1];
nextTick = xAxis.ticks[nextTickPosition];
tick.label.attr({
y: (new Number(tick.mark.d.split(' ')[2]) + new Number(nextTick.mark.d.split(' ')[2])) / 2 + 3
});
}
}
}
}
(...)
xAxis: {
tickPositions: [-0.5, 6.5, 7.5],
showLastLabel: false,
labels: {
formatter: function() {
switch (this.pos) {
case -0.5:
return 'Bananas';
case 6.5:
return 'Apples';
}
}
}
}
Live demo: http://jsfiddle.net/BlackLabel/2Lcs5up5/

Related

Highstock, True way of Get count of shown points after setExtreme (Zooming) - WITHOUT counting all data with MIN and MAX

Sure there is a way to count all of points that shown after zooming or any changing view and do reaction for that.
My target is in Highchart 7.2.0 stockChart, IF "viewed points" x "radius of circles", gone more than (>) "view-port pixels", i just hide them, or doing something special with points, because some of them are Special and still should be shown.
so i need :
HOW GET : Count of points that JUST viewed now (WITHOUT PUTTING A "FOR" TO ALL OF DATA's)
(I just think if there is no true way for it, it is better to i count svg objects instead of : counting all of my data and using isInside with min and max)
The Best Events for : "afterSetExtremes" and "events:{redraw:" [Solved i think]
events: {
afterSetExtremes: function(event) {
console.log(event.min);
console.log(event.max);
}
}
How i turn them off [Solved i think]
if (chart.userOptions.plotOptions.line.marker.enabled) {
chart.userOptions.plotOptions.line.marker.enabled=false;
chart.update({
plotOptions: {
marker: {
enabled:false
}
}
});
}
If there is automatic way like "amchart" options that i just ask "marker: { enabled: true" (when no problem) and "marker: { enabled: false" when it is tight. [Solved i think]
Solved by this:
plotOptions: {
series: {
marker: {
enabled:undefined,
enabledThreshold: 4,
symbol: 'circle',
radius: 4,
},
}
}
It was like this :
marker: {enabled:true,
enabledThreshold: 0, (By Default)
Should be :
marker: {enabled:undefined,
enabledThreshold: 4, (More than Zero)
Got help from here : https://stackoverflow.com/a/54417034/7514010
The easiest way is to loop through the data and check isInside point property or point position. As an alternative you can overwrite translate method and count the number of visible points in the existing loop:
var counter;
(function(H) {
H.Series.prototype.translate = function() {
...
counter = 0;
// Translate each point
for (i = 0; i < dataLength; i++) {
...
point.isInside =
plotY !== undefined &&
plotY >= 0 &&
plotY <= yAxis.len && // #3519
plotX >= 0 &&
plotX <= xAxis.len;
// CHANGE
if (point.isInside) {
counter++;
}
// CHANGE
...
}
series.closestPointRangePx = closestPointRangePx;
H.fireEvent(this, 'afterTranslate');
}
})(Highcharts)
Live demo: http://jsfiddle.net/BlackLabel/yx1cj0at/
Docs: https://www.highcharts.com/docs/extending-highcharts
It answered by #ppotaczek here in the third comment by this link jsfiddle.net/BlackLabel/j1tLfaxu and also in GitHub issues : https://github.com/highcharts/highcharts/issues/12017
If need to get count of points that just viewed now (by using afterSetExtremes or redraw or render events) :
chart: {
events: {
render: function() {
console.log(this.series[0].points.length)
}
}
},
processedXData can be used too instead of points
points object have enough options like isInside :
this.series[0].points[0].isInsdie.
Because it is possible the first or last point be not shown and just affect the lines in line chart or in other type of chart be not shown because zooming in Y too.
and for just calculation where the extreme started you may need :
this.series[0].cropStart
and comparing that with your main data.

Highcharts Stacked Columns Labels on Top

I want to have all the stacked column labels at the top on top of each other.
Is this possible. I tried various combos, such as moving the labels up via padding but this isnt dynamic and wont work if the values of the category change.
One options is to simply loop over your data in the formatter for your stack labels. For example (JSFiddle):
yAxis: {
stackLabels: {
enabled: true,
y: -30,
formatter: function() {
const series = this.axis.chart.series
const values = [];
for(let i = 0; i < series.length; i++) {
values.push(series[i].yData[this.x]);
}
return values.join("<br>");
},
}
}
Do note that you might have to do some kind of y offset (as in the example) to make it appear on top when using <br> as your separator value.

X-axis range changes based on hidden Series

I have 2 Series on one graph. Only one series can show at a time but the hidden graph affects the range on the x-axis.
The data is dynamically generated via PHP but here is 2 fiddles to show what I mean:
Fiddle With Changed Scale and Hidden Data
Fiddle With removed Hidden Data and correct scale
This code snippet is to ensure that only one series can be shown at any given time.
events: {
show: function () {
var chart = this.chart,
series = chart.series,
i = series.length,
otherSeries;
var seriesName = this['options']['name'];
chart.yAxis[0].axisTitle.attr({
text: seriesName
});
while (i--) {
otherSeries = series[i];
if (otherSeries != this && otherSeries.visible) {
otherSeries.hide();
}
}
}
I am not sure why the graph with the hidden data shows until 16:00 but the graph without any additional data shows until the last data point at 15:38
It appears that Highcharts is taking into account the pointRange of the series with the largest pointRange (although it is hidden) and displaying the x-axis based on that. The range of your "Calls/Hour" series is 1 hour, so it makes sure that if that series had a point at the very end, it would still have room to show.
I'm not sure if there's any elegant way of solving this, but a bit of a "hack" in your case is to change the pointRange of all series to that of the currently showing one.
My crude implementation of this has three changes to your code:
Your series that are visible: false by default also get pointRange: 1 so they don't disrupt the x-axis range for the only visible series.
When the chart has been created we store the correct point range of each series for future reference, for example with the callback function:
$('#callFrequencyGraph').highcharts({
// Options...
}, function(event) {
var series = this.series;
// Store the correct point ranges
for(var i = 0; i < series.length; i++) {
series[i].update({
historicalPointRange: (series[i].closestPointRange ? series[i].closestPointRange : 3600000)
}, false);
this.redraw();
}
}
Extend your events.legendItemClick function to update all series pointRange to that of the series which will be showing after the click is completed:
legendItemClick: function() {
if(this.visible){
return false;
}
else {
var series = this.chart.series;
for(var i = 0; i < series.length; i++) {
series[i].update({
pointRange: this.options.historicalPointRange
}, false);
}
}
}
See this updated JSFiddle for the result of all these changes.
Edit: jsFiddle Update for bug

Dygraphs - Highlight two series at the same time

I know the question was already asked before but I am very new to Dygraphs and struggling to find the answer.
I have the following datastructure in javascript:
x , Label1, Label2, label3.... label1_2, label1_3, etc...
new Date(...), 1.23,1.45,.... , .... , ....,
new Date(...), null, null, ......., 1.23,1.434
new Date(....), 1.4656, 1.6765.......,null, null,null
The whole idea is to have a plot on which a certain part of the line is dashed and the remaining part is not. I initially have 7 time series, I splitted each time serie in two (the dashed part and the non-dashed part), now I would like to highlight the whole time series ( so 2 distinct series in terms of Dygraphs the dashed serie, and the non-dashed that I splitted in two) when I pass the mouse over either the dashed region either the non dashed region.
I ve seen that people were stipulating using HihlightCallback but I am struggling to put it in practice.
What I have for the moment:
data =[new Date(), ..,..,.,,.,,.]
labels= {'A','B', ..... }
series= {'A': {strokePattern: [10, 20] }, 'B': .......}
g = new Dygraph( demo, data, {width: 1000,height: 700,labelsDivStyles: { 'textAlign': 'right' }, labels: labels,series:series, visibility: visibility, gridLineColor: 'red', gridLinePattern: [5,5], highlightCircleSize: 2,strokeWidth: 1, strokeBorderWidth: 1,highlightSeriesOpts: { strokeWidth: 3,strokeBorderWidth: 1,highlightCircleSize: 5}});
I believe my structure should be as follows:
g.updateOptions({ highlightCallback: function(event, x, points, row, seriesName) {
//1)here I need to somehow reference the other series whose label is situated N columns from the highlighted serie ( I can also reference it by its name).
// 2) Hilight the other serie
}});
I tried many different syntaxe but nothing seems to be working properly.
Could anyone please help me on this I am lost.
Here is what I would like to achieve :
http://www.google.co.uk/publicdata/explore?ds=k3s92bru78li6_#!ctype=l&strail=false&bcs=d&nselm=h&met_y=ggxwdn_ngdp&scale_y=lin&ind_y=false&rdim=world&idim=world:Earth&idim=country:AR:DZ:AU:AZ&ifdim=world&tstart=343382400000&tend=1574064000000&hl=en_US&dl=en_US&ind=false
Thanks a lot!
If I understand correctly, you've set up something like this: jsbin
Typically you style the highlighted series using highlightSeriesOpts, but that comes with the assumption that there's only a single highlighted series.
If you want to model the data this way (as separate series for actual & projected), you'll need to style the series yourself using highlightCallback. There are a few gross things about this which I'll mention below, but this is doable.
Demo: jsbin
g = new Dygraph(document.getElementById("graph"),
"X,Y,Y projected,Z,Z projected\n" +
"2006,0,,3,\n" +
"2008,2,,6,\n" +
"2010,4,,8,\n" +
"2012,6,,9,\n" +
"2014,8,8,9,9\n" +
"2016,,10,,8\n" +
"2018,,12,,6\n" +
"2020,,14,,3\n",
{
colors: ['blue', 'blue', 'red', 'red'],
series: {
'Y': { },
'Y projected': { strokePattern: [5, 5] },
'Z': { },
'Z projected': { strokePattern: [5, 5] }
},
highlightCallback: function(_, _, _, row, seriesName) {
update(seriesName, row);
},
unhighlightCallback: function() {
update();
},
highlightSeriesOpts: {},
highlightSeriesBackgroundAlpha: 1.0
});
function update(selectedSeries, row) {
var newOptions = {};
var seriesNames = g.getLabels().slice(1);
seriesNames.forEach(function(label) {
newOptions[label] = {strokeWidth: 1};
});
if (selectedSeries == 'Y' || selectedSeries == 'Y projected') {
newOptions['Y'] = newOptions['Y projected'] = {strokeWidth: 3};
} else if (selectedSeries == 'Z' || selectedSeries == 'Z projected') {
newOptions['Z'] = newOptions['Z projected'] = {strokeWidth: 3};
}
g.updateOptions({series: newOptions});
if (typeof(row) !== 'undefined') {
g.setSelection(row);
}
}
The idea is that you call updateOptions in your highlightCallback, setting the strokeWidth property for each series according to whether it (or its paired series) is selected.
There are a few gross things about this:
You have to set highlightSeriesOpts for the seriesName parameter to be passed to highlightCallback.
You need to counteract the default fading behavior of highlightSeriesOpts by setting highlightSeriesBackgroundAlpha.
Calling updateOptions clears the selection, so you have to call setSelection explicitly to re-select.
If you're willing to model the measured & projected values as a single series, then you can accomplish this more cleanly by writing a custom plotter which switches from solid to dashed lines at some point.
Here's a demo: jsbin
g = new Dygraph(document.getElementById("graph"),
"X,Y,Z\n" +
"2004,0,3\n" +
"2006,2,6\n" +
"2008,4,8\n" +
"2010,6,9\n" +
"2012,8,9\n" +
"2014,10,8\n" +
"2016,12,6\n" +
"2018,14,3\n",
{
plotter: function(e) {
var ctx = e.drawingContext;
ctx.beginPath();
ctx.moveTo(e.points[0].canvasx, e.points[0].canvasy);
for (var i = 1; i < e.points.length; i++) {
var p = e.points[i];
ctx.lineTo(p.canvasx, p.canvasy);
if (p.xval == 2014) {
ctx.stroke();
ctx.beginPath();
ctx.moveTo(p.canvasx, p.canvasy);
ctx.setLineDash([5]);
}
}
ctx.stroke();
ctx.setLineDash([]);
},
highlightSeriesOpts: {
strokeWidth: 3
}
});
Because your data is a single series, you no longer need to highlight multiple series simultaneously and hence you can use highlightSeriesOpts.

Limit labels number on Chart.js line chart

I want to display all of the points on my chart from the data I get, but I don't want to display all the labels for them, because then the chart is not very readable. I was looking for it in the docs, but couldn't find any parameter that would limit this.
I don't want to take only three labels for example, because then the chart is also limited to three points. Is it possible?
I have something like that right now:
If I could just leave every third-fourth label, it would be great. But I found absolutely nothing about labels options.
Try adding the options.scales.xAxes.ticks.maxTicksLimit option:
xAxes: [{
type: 'time',
ticks: {
autoSkip: true,
maxTicksLimit: 20
}
}]
For concreteness, let's say your original list of labels looks like:
["0", "1", "2", "3", "4", "5", "6", "7", "8"]
If you only want to display every 4th label, filter your list of labels so that every 4th label is filled in, and all others are the empty string (e.g. ["0", "", "", "", "4", "", "", "", "8"]).
For anyone looking to achieve this on Chart JS V2 the following will work:
var options = {
scales: {
xAxes: [{
afterTickToLabelConversion: function(data){
var xLabels = data.ticks;
xLabels.forEach(function (labels, i) {
if (i % 2 == 1){
xLabels[i] = '';
}
});
}
}]
}
}
Then pass the options variable as usual into a:
myLineChart = new Chart(ctx, {
type: 'line',
data: data,
options: options
});`
UPDATE:
I'v updated my fork with the latest pull (as of Jan 27, 2014) from NNick's Chart.js master branch.
https://github.com/hay-wire/Chart.js/tree/showXLabels
ORIGINAL ANSWER:
For those still facing this issue, I forked Chart.js a while back to solve the same problem. You can check it out on:
https://github.com/hay-wire/Chart.js/tree/skip-xlabels => Older branch! Check showXLabels branch for latest pull.
How to use:
Applicable to bar chart and line chart.
User can now pass a { showXLabels: 10 } to display only 10 labels (actual displayed labels count might be a bit different depending on the number of total labels present on x axis, but it will still remain close to 10 however)
Helps a lot when there is a very large amount of data. Earlier, the graph used to look devastated due to x axis labels drawn over each other in the cramped space. With showXLabels, user now has the control to reduce the number of labels to whatever number of labels fit good into the space available to him.
See the attached images for a comparison.
Without showXLabels option:
With { showXLabels: 10 } passed into option:
Here's some discussion on it:
https://github.com/nnnick/Chart.js/pull/521#issuecomment-60469304
For Chart.js 3.3.2, you can use #Nikita Ag's approach with a few changes. You can check the documentation. Put ticks in xAxis in scales. Example:
...
options: {
scales: {
xAxis: {
ticks: {
maxTicksLimit: 10
}
}
}
}
...
for axis rotation
use this:
scales: {
xAxes: [
{
// aqui controlas la cantidad de elementos en el eje horizontal con autoSkip
ticks: {
autoSkip: true,
maxRotation: 0,
minRotation: 0
}
}
]
}
In Chart.js 3.2.0:
options: {
scales: {
x: {
ticks: {
maxTicksLimit: 10
}
}
}
}
According to the chart.js github issue #12. Current solutions include:
Use 2.0 alpha (not production)
Hide x-axis at all when it becames too crowd (cannot accept at all)
manually control label skip of x-axis (not in responsive page)
However, after a few minutes, I thinks there's a better solution.
The following snippet will hide labels automatically. By modify xLabels with empty string before invoke draw() and restore them after then. Even more, re-rotating x labels can be applied as there's more space after hiding.
var axisFixedDrawFn = function() {
var self = this
var widthPerXLabel = (self.width - self.xScalePaddingLeft - self.xScalePaddingRight) / self.xLabels.length
var xLabelPerFontSize = self.fontSize / widthPerXLabel
var xLabelStep = Math.ceil(xLabelPerFontSize)
var xLabelRotationOld = null
var xLabelsOld = null
if (xLabelStep > 1) {
var widthPerSkipedXLabel = (self.width - self.xScalePaddingLeft - self.xScalePaddingRight) / (self.xLabels.length / xLabelStep)
xLabelRotationOld = self.xLabelRotation
xLabelsOld = clone(self.xLabels)
self.xLabelRotation = Math.asin(self.fontSize / widthPerSkipedXLabel) / Math.PI * 180
for (var i = 0; i < self.xLabels.length; ++i) {
if (i % xLabelStep != 0) {
self.xLabels[i] = ''
}
}
}
Chart.Scale.prototype.draw.apply(self, arguments);
if (xLabelRotationOld != null) {
self.xLabelRotation = xLabelRotationOld
}
if (xLabelsOld != null) {
self.xLabels = xLabelsOld
}
};
Chart.types.Bar.extend({
name : "AxisFixedBar",
initialize : function(data) {
Chart.types.Bar.prototype.initialize.apply(this, arguments);
this.scale.draw = axisFixedDrawFn;
}
});
Chart.types.Line.extend({
name : "AxisFixedLine",
initialize : function(data) {
Chart.types.Line.prototype.initialize.apply(this, arguments);
this.scale.draw = axisFixedDrawFn;
}
});
Please notice that clone is an external dependency.
i had a similar type of issue, and was given a nice solution to my specific issue show label in tooltip but not in x axis for chartjs line chart. See if this helps you
you can limit at as
scales: {
x: {
ticks: {
// For a category axis, the val is the index so the lookup via getLabelForValue is needed
callback: function(val, index) {
// Hide the label of every 2nd dataset
return index % 5 === 0 ? this.getLabelForValue(val) : '';
},
}
}
}
this will skip 4 labels and set the 5th one only.
you can use the following code:
xAxes: [{
ticks: {
autoSkip: true,
maxRotation: 90
}
}]
You may well not need anything with this new built-in feature.
A built-in label auto-skip feature detects would-be overlapping ticks and labels and removes every nth label to keep things displaying normally. https://www.chartjs.org/docs/latest/axes/
To set a custom number of ticks regardless of your chartsjs version:
yAxes: [{
ticks: {
stepSize: Math.round((Math.max.apply(Math, myListOfyValues) / 10)/5)*5,
beginAtZero: true,
precision: 0
}
}]
10 = the number of ticks
5 = rounds tick values to the nearest 5. All your y values will have the same step size.
Similar will work for xAxes too.
This answer works like a charm.
If you are wondering about the clone function, try this one:
var clone = function(el){ return el.slice(0); }
In the Chart.js file, you should find (on line 884 for me)
var Line = function(...
...
function drawScale(){
...
ctx.fillText(data.labels[i], 0,0);
...
If you just wrap that one line call to fillText with if ( i % config.xFreq === 0){ ... }
and then in chart.Line.defaults add something line xFreq : 1 you should be able to start using xFreq in your options when you call new Chart(ctx).Line(data, options).
Mind you this is pretty hacky.

Categories

Resources