Use different styles for each line in billboarder - javascript

I have a table with weekly data that I am comparing against a yearly target. I used to add the yearly target like this:
bb_y_grid(show = TRUE,
lines = list(list(value = 99, text = "Goal")))
This was a nice and simple solution, however the goals have shifted in 2022 so now I want to show 99 for x = 2021 and 98 for x = 2022. I could not find an option to limit the gridline to only show for a range of x.
My next idea was to add a line to my data. So I have 1 line for the real data and 1 line for the budget. This looks as follows:
However, I don't want to show the points for the budget line, only for the real data. My code is as follows:
billboarder() %>%
bb_linechart(dt.Cycle(), mapping = bbaes(YearWeekCiclo, Perc, Type),
show_point = T, type = "line") %>%
bb_x_axis(type = "category", tick = list(rotate = 90, width = 100), height = 90,
label = list(text = "Year-Week-Cycle", position = "outer-right")) %>%
bb_y_axis(label = list(text = "Percentage", position = "outer-top"), max = 100) %>%
bb_y_grid(show = TRUE) %>%
bb_legend(show = FALSE)
My question: How can I add a second line to my graph with it's own styling
Note: If you are only familiar with using billboard.js in JavaScript but know a solution I can try to translate that back to R language myself if needed

Related

Skip overlapping tick labels in Plotly Javascript

We are using scatter plots in Plotly.JS to display 2D graph data over a large X range, so we use logarithmic scaling. Zooming and panning works very well, except for one small issue: the X tick labels are confusing because Plotly uses single-digit labels for minor (non-powers of 10) ticks:
I can use tickFormat: '0.1s' to show real numbers (which is what users want) instead of single digits, but then there are cases where these labels can overlap:
I can also add dtick: 'D2' which only displays subticks at positions 2 and 5 and not all digits, but this is then fixed and doesn't adjust to scaling any more.
Ideally, I could specify subtick-label digits where to skip the label (but not the vertical line) completely, without having to resort to tickmode: array and having to specify all tick labels manually, and still benefit from automatic tick adjustment depending on scaling.
For example, if all subtick digits are displayed, I would say I'd like to have tick labels at positions 1, 2, 3, 5, 7, the result would look like this:
The other display modes (digits 2 & 5 only, or just the power of 10) would not change.
Is there a way to do this? If so, how? I'm not afraid of patching Plotly if required, but right now I don't know where to start looking.
Usually I solve this by rotating the labels by some 35-45 degrees. That way they are all there and still readable.
https://plotly.com/javascript/reference/#layout-xaxis-tickangle
tickangle
Parent: layout.xaxis
Type: angle
Default: "auto"
Sets the angle of the tick labels with respect to the horizontal. For example, a `tickangle` of -90 draws the tick labels vertically.
OK, so I looked deeply into the Plotly configuration options and there are options that are semi-automatically modified 'live' depending on zoom levels, but the conditions when those modifications happen are hardcoded.
So here's a patch (for Plotly v1.36, because that's what we currently use). I modified some of the hardcoded conditions when to change from 1,10,100,... to 1,2,5,10,20,50,100,... to 1,2,3,4,5,... labeling on logarithmic axes and removed the tick labels at 4, 6, 8 and 9 for the last case to avoid text overlapping.
For my application, this now works well and I could not find any more overlapping of tick labels. Also, I got rid of the single digits between powers of ten which confused some users.
--- a/plotly-latest.v1.36.js 2021-05-24 21:45:28.000000000 +0100
+++ b/plotly-latest.v1.36.js 2022-02-02 10:21:08.000000000 +0100
## -127302,13 +127302,13 ##
if(!nt) {
if(ax.type === 'category') {
minPx = ax.tickfont ? (ax.tickfont.size || 12) * 1.2 : 15;
nt = ax._length / minPx;
}
else {
- minPx = ax._id.charAt(0) === 'y' ? 40 : 80;
+ minPx = ax._id.charAt(0) === 'y' ? 40 : 100;
nt = Lib.constrain(ax._length / minPx, 4, 9) + 1;
}
// radial axes span half their domain,
// multiply nticks value by two to get correct number of auto ticks.
if(ax._name === 'radialaxis') nt *= 2;
## -127395,14 +127395,21 ##
// Start with it cleared and mark that we're in calcTicks (ie calculating a
// whole string of these so we should care what the previous date head was!)
ax._prevDateHead = '';
ax._inCalcTicks = true;
var ticksOut = new Array(vals.length);
- for(var i = 0; i < vals.length; i++) ticksOut[i] = axes.tickText(ax, vals[i]);
+ // if scaling == log, skip some intermediate tick labels to avoid overlapping text
+ var skipTexts = /^[4689]/;
+ var text;
+ for(var i = 0; i < vals.length; i++) {
+ text = axes.tickText(ax, vals[i]);
+ if(ax.type == "log" && ax.dtick == "D1" && text.text.match(skipTexts)) text.text = "";
+ ticksOut[i] = text;
+ }
ax._inCalcTicks = false;
return ticksOut;
};
function arrayTicks(ax) {
## -127535,18 +127542,20 ##
// ticks on a linear scale, labeled fully
roughDTick = Math.abs(Math.pow(10, rng[1]) -
Math.pow(10, rng[0])) / nt;
base = getBase(10);
ax.dtick = 'L' + roundDTick(roughDTick, base, roundBase10);
+ ax.tickformat = '';
}
else {
// include intermediates between powers of 10,
// labeled with small digits
// ax.dtick = "D2" (show 2 and 5) or "D1" (show all digits)
- ax.dtick = (roughDTick > 0.3) ? 'D2' : 'D1';
+ ax.dtick = (roughDTick > 0.4) ? 'D2' : 'D1';
+ ax.tickformat = '0.1s';
+ ax.hoverformat = '0.2s'; // Workaround to fix hoverinfo label formatting
}
}
else if(ax.type === 'category') {
ax.tick0 = 0;
ax.dtick = Math.ceil(Math.max(roughDTick, 1));
}
The patch is hereby released as Public Domain to make it as easy as possible to use this for direct integration into Plotly (maybe as a "D3" formatting option?).

How to adjust node size based on value in diagonalNetwork of networkD3

I am new to using the networkd3 package and I am trying to understand how to adjust the layout manually. As an example I have the following layout
library(networkD3)
library(tidyverse)
library(data.tree)
library(htmlwidgets)
set.seed(1)
example = data.frame(lvl1 = rep("A", 5), lvl2 = sample(c("D" ,"B", "C"), 5, replace = T), value = sample(c(1,2,3), 5, replace = T))
> example
lvl1 lvl2 value
1 A D 3
2 A C 3
3 A D 2
4 A B 2
5 A D 3
Data_tree <- example %>%
unite(col="pathString",lvl1,lvl2,sep="-",remove=FALSE, na.rm = T) %>%
as.Node(pathDelimiter = "-")
diagonalNetwork(ToListExplicit(Data_tree, unname = TRUE ),
fontSize = 9,
linkColour = "#ccc",
nodeColour = "#fff",
nodeStroke = "orange",
textColour = "#000000")
What I would like to do is to somehow map the value column into the size of the node. Similarly to how aesthetic mappings work in ggplot2. So ideally for each level I would like the nodesize to be proportional to the total value for this breakdown.
So for example lvl1 has only one possible value A so the node should get 100% of the chosen nodesize. On the other hand, lvl2 has 3 values, so for D I would like it to have (3+2+3)/13 % of the full node size, C should have 3/13 % of the nodesize and B should have 2/13 % of the nodesize.
This is just an example and I can do any manipulation needed, but is this even possible for these types of charts?
This is not an intended use of diagonalNetwork, so expect that there will be complications (e.g. when you hover over the nodes with the mouse cursor, the default behavior will reset the node size back to the default size), however...
# add a value to the root node
data_list <- ToListExplicit(Data_tree, unname = TRUE)
data_list$value <- 5
# save the output of diagonalNetwork as an object
d3net <- diagonalNetwork(data_list,
fontSize = 9,
linkColour = "#ccc",
nodeColour = "#fff",
nodeStroke = "orange",
textColour = "#000000")
# write some JavaScript to set the circles' radii to the data's value
node_size_js <-
'
function(el) {
d3.select(el)
.selectAll(".node")
.selectAll("circle")
.attr("r", d => d.data.value * 5);
}
'
# use onRender to run the JS when it loads
htmlwidgets::onRender(d3net, node_size_js)

How to add missing values to two associated arrays? (JavaScript)

I have two arrays:
category.data.xAxis // containing: ["0006", "0007", "0009", "0011", "0301"]
category.data.yAxis // containing: [6.31412, 42.4245, 533.2234, 2345.5413, 3215.24]
How do I take a max length, say DATAPOINT_LENGTH_STANDARD = 540 and fill in every missing number-based string on the xAxis array? so that:
The resulting xAxis array will read from "0000" to "0540" (or whatever the standard length is)
The associated yAxis indexes will remain connected to the original xAxis datapoints (i.e. "0006" to 6.31412)
Every newly created xAxis datapoint has an associated yAxis value of 0 (so the newly created "0000" xAxis entry will contain 0 in the yAxis at index 0 for both)
The xAxis value strings can be assumed to already be sorted from lowest to highest.
EDIT
The clarity of my question was an effort to help the community with finding a more straight-forward answer, but I suppose the exactness gave the appearance of a homework question. In response to the comments, my original attempt, which does not properly manipulate the x-axis and maintain proper indexing:
let tempArray = categoryObject.data.xAxis;
let min = Math.min.apply(null, tempArray);
let max = Math.max.apply(null, tempArray);
while (min <= max) {
if (tempArray.indexOf(min.toString()) === -1) {
tempArray.push(min.toString());
categoryObject.data.yAxis.push(0);
}
min++;
}
console.log(tempArray);
console.log(categoryObject.data.yAxis);
let xAxis = ["0006", "0007", "0009", "0011", "0301"]
let yAxis = [6.31412, 42.4245, 533.2234, 2345.5413, 3215.24]
const DATAPOINT_LENGTH_STANDARD = 540
// This assumes at least one data point
let paddedLength = xAxis[0].length
// Creates a new array whose first index is 0, last index is
// DATAPOINT_LENGTH_STANDARD, filled with 0s.
let yAxis_new = new Array(DATAPOINT_LENGTH_STANDARD + 1).fill(0)
// Copy each known data point into the new array at the given index.
// The + before x parses the zero-padded string into an actual number.
xAxis.forEach((x, i) => yAxis_new[+x] = yAxis[i])
// Replace the given array with the new array.
yAxis = yAxis_new
// Store the padded version of the index at each index.
for (let i = 0; i <= DATAPOINT_LENGTH_STANDARD; ++i) {
xAxis[i] = ('' + i).padStart(paddedLength, '0')
}
console.log(xAxis)
console.log(yAxis)

D3 js what kind of scale to use for unbalanced data

I have to render few circles on which "r" attr depends on the input data. The problem with input data is one of the value is so huge compared to rest of the data points that when i use scaleLinear | Pow | Log with range 2 - 35 ; all my circles have radius between 2 - 3 and only that few data points are radius 35. so what kind of scale can i use for such data for better visual ?
You could maybe use d3.scaleQuantize.
Here is the example provided by the documentation:
var width = d3.scaleQuantize()
.domain([10, 100])
.range([1, 2, 4]);
width(20); // 1
width(50); // 2
width(80); // 4
Or you could write your own classification function. Something like this (with 3 classes here):
if input data < x => r = 2
else if x <= input data < y => r = 8
else (input data >=y) => r = 14

Where is this fill function defined?

In the example, http://mbostock.github.com/d3/ex/bubble.html:
In line 27:
.style("fill", function(d) { return fill(d.packageName); });
Where is fill() defined? I didn't find it in d3.js either. And even if the package name is not a color, some random color is being assigned. How so?
Take a look at the very top of the snippet you linked:
var r = 960,
format = d3.format(",d"),
fill = d3.scale.category20c();
That third line is where fill is being defined. You can find the doc for category20c here:
https://github.com/mbostock/d3/wiki/Ordinal-Scales#wiki-category20
The source code is on lines 2997- on d3.v2.js:
d3.scale.category20c = function() {
return d3.scale.ordinal().range(d3_category20c);
};
Which is calling:
// lines 2894-2896
d3.scale.ordinal = function() {
return d3_scale_ordinal([], {t: "range", x: []});
};
Which then calls
// lines 2903-2905
function scale(x) {
return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length];
}
The parameter passed in, x is set to d3_category20c which assigns colors from the list of 20 available colors seen below:
["#3182bd", "#6baed6", "#9ecae1",
"#c6dbef", "#e6550d", "#fd8d3c",
"#fdae6b", "#fdd0a2", "#31a354",
"#74c476", "#a1d99b", "#c7e9c0",
"#756bb1", "#9e9ac8", "#bcbddc",
"#dadaeb", "#636363", "#969696",
"#bdbdbd", "#d9d9d9"]
Stepping through the code I see the colors assigned like this (based on the category passed in):
cluster = 0 #3182bd
graph = 1 #6baed6
optimization = 2 #9ecae1
animate = 3 #c6dbef
interpolate = 4 #e6550d
converters = 5 #fd8d3c
data = 6 #fdae6b
display = 7 #fdd0a2
flex = 8 #31a354
physics = 9 #74c476
query = 10 #a1d99b
methods = 11 #c7e9c0
scale = 12 #756bb1
util = 13 #9e9ac8
heap = 14 #bcbddc
math = 15 #dadaeb
palette = 16 #636363
axis = 17 #969696
controls = 18 #bdbdbd
render = 19 #d9d9d9
events = 0 #3182bd
legend = 1 #6baed6
etc...
Note, it's using the mod operator so it can keep assigning colors if the number of passed in categories exceeds the range of 20 colors.
Note, the colors for d3_category20c are defined in category.js, lines 48-54.
As already mentioned, this code:
var r = 960,
format = d3.format(",d"),
fill = d3.scale.category20c();
defines fill(). I just want to add following handy combination of documentation and reference card:

Categories