Highcharter tooltip not working with many series - r

I'm trying to create a line chart with many series (more than 50) using the highcharter package in R.
When I use fewer series (less than 40) my tooltip works correctly, but when the number of series increase, the tooltip stops displaying the category of the x-axis and starts simply counting from zero to the number of ticks. Here is the code to reproduce:
library(dplyr)
library(highcharter)
test1<-data.frame(Value=rnorm(300,0,1),Type=rep(c('A','B','C'),each=100),Period=rep(seq(as.Date('2020-01-01'),as.Date('2020-04-09'),by=1),3))
test2<-data.frame(Value=rnorm(500,0,1),Type=rep(as.character(1:50),each=10),Period=rep(seq(as.Date('2020-01-01'),as.Date('2020-01-10'),by=1),50))
category1<-unique(format(test1$Period,'%d/%b/%y'))
category2<-unique(format(test2$Period,'%d/%b/%y'))
hchart(test1,type='line',hcaes(x=as.character(Period),y=Value,group=Type)) %>%
hc_xAxis(type='category',categories=as.list(category1),title='',labels=list(rotation=-30)) %>%
hc_legend(align='left',maxHeight=100) %>%
hc_plotOptions(line=list(marker=list(enabled=F))) %>%
hc_tooltip(shared=T,
formatter=JS("function() {
var s = '';
$.each(this.points, function(i, point) {
if (point.y !== 0) {
s += '<br><b>'+ point.series.name +': </b>'+ Highcharts.numberFormat(point.y,2,',','.');
}
});
return this.x + s;
}"))
hchart(test2,type='line',hcaes(x=as.character(Period),y=Value,group=Type)) %>%
hc_xAxis(type='category',categories=as.list(category2),title='',labels=list(rotation=-30)) %>%
hc_legend(align='left',maxHeight=100) %>%
hc_plotOptions(line=list(marker=list(enabled=F))) %>%
hc_tooltip(shared=T,
formatter=JS("function() {
var s = '';
$.each(this.points, function(i, point) {
if (point.y !== 0) {
s += '<br><b>'+ point.series.name +': </b>'+ Highcharts.numberFormat(point.y,2,',','.');
}
});
return this.x + s;
}"))
And here are the outputs:
I don't know if this is a limitation because of the volume of information, but it is a strange behavior that I could not solve.

Related

Increase size of one of the series in R highchart

I'm trying to show a line and % changes in a single highchart plot, but the changes are very little and It can't be seen in the plot. I made a simplified code to show my problem:
a <- c(300,200, 400, 10, 40, 80)
b <- c(0.8, 2, -2, -1.5, -1.1, 2)
d<-cbind(a,b)
dt <- seq(as.Date("2018-01-01"), as.Date("2018-01-06"), by = "days")
ts <- xts(d, dt )
highchart(type="stock") %>%
hc_add_series(ts$a,
type = "line",
color="black") %>%
hc_add_series(ts$b,
type = "lollipop",
color="red")
I need to increase the size of "ts$b" in the plot, how can I do it? I also tried with two axis, but It seems doesn't solve the problem.
I see two solutions to achieve that.
The first you mentioned - using two yAxis and manipulating their height and top distance.
Example JS code:
yAxis: [{
height: '90%',
opposite: false
},
{
visible: false,
top: '83%',
height: '15%',
}
]
Demo:
https://jsfiddle.net/BlackLabel/0826r7sh/
Another way is using a modified logarithmic axis. Negative values can't be plotted on a log axis, because by nature, the axis will only show positive values. In that case you need to use a custom extension according to the following thread:
Highcharts negative logarithmic scale solution stopped working
(function(H) {
H.addEvent(H.Axis, 'afterInit', function() {
const logarithmic = this.logarithmic;
if (logarithmic && this.options.custom.allowNegativeLog) {
// Avoid errors on negative numbers on a log axis
this.positiveValuesOnly = false;
// Override the converter functions
logarithmic.log2lin = num => {
const isNegative = num < 0;
let adjustedNum = Math.abs(num);
if (adjustedNum < 10) {
adjustedNum += (10 - adjustedNum) / 10;
}
const result = Math.log(adjustedNum) / Math.LN10;
return isNegative ? -result : result;
};
logarithmic.lin2log = num => {
const isNegative = num < 0;
let result = Math.pow(10, Math.abs(num));
if (result < 10) {
result = (10 * (result - 1)) / (10 - 1);
}
return isNegative ? -result : result;
};
}
});
}(Highcharts));
.
yAxis: {
type: 'logarithmic',
custom: {
allowNegativeLog: true
}
},
Demo
https://jsfiddle.net/BlackLabel/nw6osucm/

R Markdown - Highcharter - Animate graph when visible, rather on page load?

I am writing a report in R Markdown, it contains multiple animated highcharts.
The animations work fine, however they all run when the html page loads (after knitting), instead of when the user scrolls to it, so essentially the animation is pointless as the user never sees it.
An example of an animated chart is at the bottom of this question.
Is there a way to make it animate when it appears? All the examples I have found use jsfiddle and I am using R Markdown.
Many thanks
library(dplyr)
library(stringr)
library(purrr)
n <- 5
set.seed(123)
df <- data.frame(x = seq_len(n) - 1) %>%
mutate(
y = 10 + x + 10 * sin(x),
y = round(y, 1),
z = (x*y) - median(x*y),
e = 10 * abs(rnorm(length(x))) + 2,
e = round(e, 1),
low = y - e,
high = y + e,
value = y,
name = sample(fruit[str_length(fruit) <= 5], size = n),
color = rep(colors, length.out = n),
segmentColor = rep(colors2, length.out = n)
)
hcs <- c("line") %>%
map(create_hc)
hcs
Ok, I worked out how to do it myself, going to post the answer here in case someone stumbles across this post in the future.
First of all, I found NOTHING on how to do this in R.
So, I decided to do this in JS, AFTER I had knitted the R Markdown document to HTML, as it wouldn't work in R Markdown.
Once it is a HTML file, open it in TextEdit or Notepad, and add the following code just before one of the charts:
<script>
(function (H) {
var pendingRenders = [];
// https://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport/7557433#7557433
function isElementInViewport(el) {
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (
window.innerHeight ||
document.documentElement.clientHeight
) &&
rect.right <= (
window.innerWidth ||
document.documentElement.clientWidth
)
);
}
H.wrap(H.Series.prototype, 'render', function deferRender(proceed) {
var series = this,
renderTo = this.chart.container.parentNode;
// It is appeared, render it
if (isElementInViewport(renderTo) || !series.options.animation) {
proceed.call(series);
// It is not appeared, halt renering until appear
} else {
pendingRenders.push({
element: renderTo,
appear: function () {
proceed.call(series);
}
});
}
});
function recalculate() {
pendingRenders.forEach(function (item) {
if (isElementInViewport(item.element)) {
item.appear();
H.erase(pendingRenders, item);
}
});
}
if (window.addEventListener) {
['DOMContentLoaded', 'load', 'scroll', 'resize']
.forEach(function (eventType) {
addEventListener(eventType, recalculate, false);
});
}
}(Highcharts));
</script>
The charts then animate when you scroll to them, rather than when you open the HTML file.
Note: The JSFIDDLE I got the code from was from here:
https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/studies/appear/

setOptions (numericSymbols) in rCharts

I am using rCharts to plot some large values and rather than a value of '1B' (for billion) on the axis label, I am getting '1G' (for Giga). I have seen that the way to change this in highCharts is via the numericSymbol option, however I have not been able to do incorporate this into R.
A minimal example is as follows
library(rCharts)
data <- data.frame(x = 1:40, y = 2^(1:40))
a <- rCharts::Highcharts$new()
a$series(data=toJSONArray2(data, json=F), type = 'scatter')
a$yAxis(type = 'logarithmic', labels =
list(useHTML = T,
formatter = "#! function()
{
if (this.value < 1e3) {
return this.value;
} else if (this.value < 1e6){
return this.value/1e3+'k';
} else if (this.value < 1e9){
return this.value/1e6+'M';
} else if (this.value < 1e12){
return this.value/1e9+'B';
} else if (this.value < 1e15){
return this.value/1e12+'T';
} else if (this.value >= 1e15){
return this.value/1e15+'P';
} else {return this.value};
} !#")
)
a
But I am still getting labels of '1G' on my y-axis for values of about one billion. Is there an error when I set the values of a$lang? Or is there some alternative way to get a 'B' instead of a 'G'?

How to add zoom effect into dc.geoChoroplethChart?

I started to use dc.js Library to create all kinds of graphs and I bumped into a problem when I was trying to create a Geo Choropleth map using dc.js and couldn't add the ability to zoom and move the map.
All the examples I saw were using d3 and svg.. but once I used those examples, I couldn't use the data of dc.dimention and all the crossfilter calculations.
for example my code is:
d3.json("world-countries.json", function (statesJson) {
geoChart.width(1000)
.height(600)
.dimension(countryDim)
.projection(d3.geo.mercator()
.scale((960 + 1) / 4 )
.translate([960 / 4, 960 / 4])
.precision(.1))
.group(countryGroup)
.colors(d3.scale.quantize().range(["#E2F2FF","#C4E4FF","#9ED2FF","#81C5FF","#6BBAFF","#51AEFF","#36A2FF","#1E96FF","#0089FF","#0061B5"]))
.colorDomain([0, 200])
.colorCalculator(function(d){ returnd ?geoChart.colors()(d) :'#ccc'; })
.overlayGeoJson(statesJson.features,"state",function(d){
return d.properties.name;
})
.title(function (d) {
return "State: " + d.key + (d.value ? d.value : 0) + "Impressions";
});
Which works nicely, but I want to add the zoom effect and to be able to move the map. how can I do that?!?!
thanks in advance!
So, the answer is:
var width = 960,
height = 400;
var projection = d3.geo.mercator()
.scale(200)
.translate([width/2, height]);
function zoomed() {
projection
.translate(d3.event.translate)
.scale(d3.event.scale);
geoChart.render();
}
var zoom = d3.behavior.zoom()
.translate(projection.translate())
.scale(projection.scale())
.scaleExtent([height/2, 8 * height])
.on("zoom", zoomed);
var svg = d3.select("#geo-chart")
.attr("width", width)
.attr("height", height)
.call(zoom);
geoChart
.projection(projection)
.width(1000)
.height(400)
.transitionDuration(1000)
.dimension(countryDim)
.group(ctrGroup)
.filterHandler(function(dimension, filter){
dimension.filter(function(d) {return geoChart.filter() != null ? d.indexOf
(geoChart.filter()) >= 0 : true;}); // perform filtering
return filter; // return the actual filter value
})
.colors(d3.scale.quantize().range(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF",
"#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"]))
.colorDomain([0, 200])
.colorCalculator(function (d) { return d ? geoChart.colors()(d) : '#ccc'; })
.overlayGeoJson(statesJson.features, "state", function (d) { return d.id; })
.title(function (d) {
return "State: " + d.key + " " + (d.value ? d.value : 0) + " Impressions";
});

d3 map - After using blur filter, zoom does not work properly

I am using the blur effect on the d3 map as given here: http://geoexamples.blogspot.in/2014/01/d3-map-styling-tutorial-ii-giving-style.html?
But after using this method (because of how the data is loaded..using datum) my zoom functionality behaves randomly. Irrespective of where I click it zooms to the same point. Also, the animations have become very slow after using the filter.
Is there any other way to achieve blur? Or a solution to this problem?
Any help?
Thanks.
This is the code for the world creation in case when filtering is required (use of datum as per the code on the above site).
d3.json("world-110m2.json", function(error, world) {
g.insert("path")
.datum(topojson.feature(world, world.objects.land))
.attr("d", path);
g.insert("path")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("d", path)
.append("path");
g.selectAll("path")
.on("click", click);})
This is the code used in case filtering is not required (No use of datum - maybe the datum is causing the issue)
d3.json("world-110m2.json", function(error,topology) {
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d",path)
.on("click", click);)}
This is the zoom function: got the code from here: http://bl.ocks.org/mbostock/2206590
function click(d) {
var x, y, k;
var centered;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
if (active === d) return reset();
g.selectAll(".active").classed("active", false);
d3.select(this).classed("active", active = d);
var b = path.bounds(d);
g.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
g.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
}
The blur filter consumes lots of resources, as indicated in the post. Speciallly if you combine it with other filters.
One solution would be using Canvas instead of SVG. Here you have some filters using the Canvas element. It should be possible to achieve the same result.
I can't find why the zoom stops working, but the performance is slower because you use all the data, so you are applying the filter to all the data instead of using only the part of the word you are showing, so you are using a much bigger image when you zoom.

Resources