Highcharts - series.remove(); 4 series in chart, only 2 removed - javascript

Live example:
http://jsfiddle.net/jlbriggs/6dgs4cav/
I have a set up where I have a chart with four series.
I want to allow the user to select a different data set, and then
remove the existing series
add the new series
I am stuck at the 'remove' phase, however, because after the first two series are removed, it tells me the next 2 are undefined.
The function to remove the series:
function updateData(chart, dataSet) {
console.log(chart);
$.each(chart.series, function(i,ser) {
if(typeof(ser) != 'undefined') {
console.log(ser);
ser.remove();
}
});
}
Which leaves 2 series still on the chart.
When I console.log the chart object, it only lists two series.
The questions:
Am I doing this wrong? Is there a better way to remove all of the series?
Why does the chart only show two series instead of four?

Looks like your code is modifying the array that is being iterated over. Try this and observe the logs.
$.each(chart.series, function(i,ser) {
console.log(i + " / " + chart.series.length);
ser.remove();
});
Observe how the current index i is increasing and your series length is decreasing after each iteration.
0 / 4
1 / 3
2 / 2 << chart.series[2] becomes undefined for chart.series.length=2
At the third iteration, i=2 & chart.series.length=2 => ser=undefined
Proper way to remove all series data from a highcharts chart? provides the right way of doing this. Always remove the series at index 0 if it exists.
while(chart.series.length > 0)
chart.series[0].remove(true);

Related

Is there a way to center label in nested pie chart without using magic numbers as radius for Amchart v4?

My problem: i would like to find an automatic way to center labels in donut chart cells. In my case each cells contains an array of complex objects, what i want is to show the number of those objects.
See:
Playing with radius, allowed me to find those values:
First layer: -28
Second layer: -20
Third layer: -10
Fourth layer: -8
I applied it as a quick fix but i don't like this solution, as it's fixed for 4 layers (what if I need to add an other layer ? etc....) and using "magic numbers" is unmaintenable...
Do you have a better solution ?
You can test it here: https://jsfiddle.net/aliz/gwz7om9e/
Line 40:
pieSeries.labels.template.radius = am4core.percent(positionRadiusInPie);
Note:
Using those attributes didn't work: "horizontalCenter", "VerticalCenter", "textAlign", "align".
EDIT: response to Bhavik Kalariya
This is what I get if I force one radius for all layers.
You can at least get it down to just using one base constant of your choosing by using an adapter approach on the label's radius to calculate the value you want for each series that is added to the chart using whatever formula you choose. Here's a basic formula that gave good results for me with your setup, where BASE_INNER_LABEL_RADIUS is set to -45.
pieSeries.labels.template.adapter.add('radius', function(radius, target) {
var chart = target.baseSprite; //chart stored in baseSprite of the AxisCircularLabel
var index = chart.series.indexOf(target.dataItem.component); //corresponding series stored in the dataItem's component
return am4core.percent(BASE_INNER_LABEL_RADIUS / (index + 2)); //Uses singular constant defined elsewhere, which is used in all series
});
This will adjust itself according to the number of series you add to the chart. You can make this as robust as you want by making further tweaks if you have fewer series and want to make the labels even more centered.
Updated fiddle
Edit
If you want to be even more generic, you can get at the slice sprite directly through the target.dataItem.sprites array (typically the first element, though you can loop through and look for an object whose className is "Slice" if you want to be super safe about it) and calculate your desired radius value using any of the numeric properties it has, such as innerRadius.
pieSeries.labels.template.adapter.add('radius', function(radius, target) {
var chart = target.baseSprite; //chart stored in baseSprite of the AxisCircularLabel
var index = chart.series.indexOf(target.dataItem.component); //corresponding series stored in the dataItem's component
return -(target.dataItem.sprites[0].innerRadius) / (index + 3);
});
You'll want to adjust this accordingly, of course.
Fiddle

Highcharts - Column chart with empty columns for date in x-axis

Is there any way to have a column or line chart which doesn't render empty column if I'm using the datetime type?
Because I want to display the last 7 days without the weekends, but this doesn't seems to be possible.
Example: http://jsfiddle.net/G5S9L/7/
Expected: don't display June 17th/18th
The second thing I tried was to use the category type. In this case the chart is just rendering these columns which are specified in the series data. But my chart consists of multiple series and each series will hava some gaps. Highchart doesn't match the category names and puts all y values after each other.
Exmaple: http://jsfiddle.net/G5S9L/6/
Expected: Thursday has 2 columns/bars as well as Monday
These samples are very simplified. I know I could generate a master list with all values of the x-axis and order each series according to the master list and fill the gaps with NULL values. But this a heavy overhead in generating data for my statistics. Because not all series have the same sources to determine the range of the x-axis.
With Highcharts? There's not simple way to achieve that.
However, using Highstock, simply use xAxis.ordinal = true and everything will work. See: http://jsfiddle.net/G5S9L/8/
For the second question, you can simply extend the Point class(wrap applyOptions method), and set the categories of xAxis.
(function (H) {
H.wrap(H.Point.prototype, 'applyOptions', function (applyOptions, options, x) {
var point = applyOptions.call(this, options, x),
series = point.series;
// check if
// 1. there is a name(category)
// 2. xAxis has the categories defined
// 3. x value equals xIncrement
if (point.name && series.xAxis.categories && point.x === series.xIncrement - 1) {
// find a matching category?
var idx = series.xAxis.categories.indexOf(point.name);
if (idx >= 0) {
point.x = idx;
}
}
return point;
});
})(Highcharts);
See fiddle: http://jsfiddle.net/G5S9L/17/

Highcharts buggy with more than 999 items in series data?

I created an example fiddle. It is using some techniques coming from Nicks answer on this Question.
When I tried to do it with my data model nothing happened. A debug session showed me that if I do:
var maxItems = 1000;
var chartData = new Array(maxItems);
for (var i = 0; i <= maxItems; i++) {
chartData[i] = { y: 3, x: 1380385867013, myData:'hello' };
}
Highcharts will not display anything. If I then change the value of maxItems to 999 it will work.
Another strange thing is, that when I use:
chartData[i] = [ 1380385867013, 3 ];
I can as much items as I want but I need the "myData" option to add tooltips there. What now?
Running your jsfiddle example with opened console log shows:
Highcharts error #12: www.highcharts.com/errors/12
Content of that link:
Highcharts Error #12
Highcharts expects point configuration to be numbers or arrays in turbo mode
This error occurs if the series.data option contains object configurations and the number of points exceeds the turboThreshold. It can be fixed by either setting the turboThreshold option to a higher value, or changing your point configurations to numbers or arrays. See turboThreshold.
Highcharts docs about turboThreshold:
turboThreshold: Number
When a series contains a data array that is longer than this, only one dimensional arrays of numbers, or two dimensional arrays with x and y values are allowed. Also, only the first point is tested, and the rest are assumed to be the same format. This saves expensive data checking and indexing in long series. Set it to 0 disable. Defaults to 1000.
So, users Mark and strikers are right.
Set the turboThreshold: 0 in plotOptions > series option. Like this :-
plotOptions: {
series: {
turboThreshold: 0} }

Get All Points for a particular Tick value

I am uisng tickPositioner to plot certain dates on X Axis.
xAxis: {
......
tickPositioner: function(min, max) {
// custom function which returns dates array.
return getDatesBetweenRange(min, max);
},
.....
}
using Highstock v1.2.5.
I also have Show/Hide series option in the Legend. It works fine no issues till here.
Now, When I hide any series from the chart.
I do not want to show those dates on x Axis who have no data as the series is hidden.
I was looking into source code at "getOffset:" method where Label is being created for each
Tick.
Is there any relation in API which returns all series points for this Tick ?
Or
Is there any relation in API that says that this tick pos (e.g. date) has no data visible ?
As I know, you can use a little different solution:
In tickPositioner you have access to all series for specific axis via this.series. Now, each of these series have xData which contains all x-values. All you need to do now is check if series is visible, and then compare your tick values (generated by getDatesBetweenrange()) with values in xData arrays - and return only these values which could be find there.

How to fill the Area between two series with multiples yAxis in HighChart?

The topic of filling area between series has been discussed quite a lot. I've seen some solutions using 'arearange' series ( adding dummy serie with area range to add the fill color ) or using 'stacked area' ( using dummy area serie with stacking: true, transparent under the actual series, then add another stacked area with the needed color ). An example can be seen here.
but my problem is quite specific : I need to fill the area between to series which don't share the same yAxis, thus I can't add a dummy serie since I can't figure which of the yAxis to use.
( The same problem occurs when series don't share the same xAxis reference values, as seen in this example )
For example, let's say I want to fill the area on this chart where the blue 'rainfall' line is under the green 'temperature' line : JSFiddle example
How can I do that ?
I've accepted Sebastian answer and I publish here its implementation :
I've created a function that generates a dummy series with the type 'arearange', using the data of the first series and a modified version of the data in the second series in order to map them on the main yAxis :
/**
*generate a dummy arearange series in order to fill area between our two series.
*/
function createRangeSerie(data1, min1, max1, data2, min2, max2)
{
//we will apply a linear transfomation to values from the second series
var b = min1-min2;
var a = (max1 - b) / max2;
var rangeData = [];
$.each(data1, function(index, value) {
var value2 = a*data2[index] + b;
rangeData.push([value, value2]);
});
//return the third series, used to fill the area between our 2 series.
return {
name: 'areaRangeSerie',
type: 'arearange',
data: rangeData
};
};
then I use it to add the new area in my chart as seen in this fiddle example :
http://jsfiddle.net/2me4Z/3/
Thanks again guys!
LIMITATIONS :
What I was afraid of occurs : in the case I want to fill only if the first curve is under the second (or vice versa). there is a problem on the adjacent points. As you can see in the updated JSfiddle. So this solution is still not perfect, I'll improve it.
LAST VERSION :
In my final version, I've added the intersection point to my arearange serie. Now the result is perfect. You can see the result an the algorithm here in this JSFiddle
as you can see, I4ve changed the xAxis so I've got values for computations instead of categories.
Edit : here is the function to find the intersection point from two lines ( each defined by two points so we need 4 arguments : line1Point1 line1Point2, line2Point1, line2Point2). I don't sue pixel coordinates but I compute intersection from my x and y values since the resulting point will be mapped by the highchart library the same way.
function intersectionPoint(l1Pt1, l1Pt2, l2Pt1, l2Pt2){
console.log(l1Pt1, l1Pt2, l2Pt1, l2Pt2);
//compute A B and C for the first line: AX + BY = C
A1 = l1Pt2.y-l1Pt1.y;
B1 = l1Pt1.x-l1Pt2.x;
C1 = A1*l1Pt1.x + B1 *l1Pt1.y;
//compute A B and C for the second line
A2 = l2Pt2.y - l2Pt1.y;
B2 = l2Pt1.x - l2Pt2.x;
C2 = A2*l2Pt1.x + B2*l2Pt1.y;
var delta = A1*B2 - A2*B1;
if(delta == 0) {
console.log("Lines are parallel");
return null;
}
else
{
return {
x: (B2*C1 - B1*C2)/delta,
y: (A1*C2 - A2*C1)/delta
};
}
};
I'm really expecting highchart to give a full official support for it since it will become again more complex when using logarithmic axis X(
You can use the same two linked axis (the same ranges /ticks) but with different data, and then use additional series with arearange type: http://www.highcharts.com/demo/arearange
I think your options are pretty much one of these two:
1) Normalize your data before sending it to the chart so that they can use the same axis.
2) Develop a complex script to determine where the series are in relation to each other and create your dummy series accordingly.
HOWEVER.
It's extremely important to consider the fact that with two series using two separate axes, measuring two different things on two different scales....
The interaction between the 2 lines is completely meaningless.
It is one of the major common pitfalls of data visualization to highlight the interaction between two such lines, but the interaction is entirely dependent on the mostly arbitrary scaling of the two completely different axis measurements.
FWIW.

Categories

Resources