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
Related
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?).
Every solution I've found uses the following object.
function converter(num) {
var romanKeys =
{M:1000,CM:900,D:500,CD:400,C:100,XC:90,L:50,XL:40,X:10,IX:9,V:5,IV:4,I:1}
When attempting the problem myself, I wasn't too sure which roman numerals were redundant when constructing the object. Procedurally, how do we arrive to this object? e.g How do I know that
"VI: 6" is unnecessary but "IV: 4" is?
When a symbol appears after a larger (or equal) symbol it is added
Example: VI = V + I = 5 + 1 = 6
Example: LXX = L + X + X = 50 + 10 + 10 = 70
But if the symbol appears before a larger symbol it is subtracted
Example: IV = V − I = 5 − 1 = 4
Example: IX = X − I = 10 − 1 = 9
I can be placed before V (5) and X (10) to make 4 and 9.
X can be placed before L (50) and C (100) to make 40 and 90.
C can be placed before D (500) and M (1000) to make 400 and 900.
When you are scanning a roman number you are looking from left to right at each symbol and if it appears before a larger symbol, you take them together, do the substraction and add it to the result, then move to the symbol after them. Otherwise you take a single symbol and add its value to the result and move to the next symbol.
For example for XIV:
1) result = 0
2) X < I => result += 10 (result = 10)
3) I < V => result += (5-1) (result = 14)
Note that if you are using that mapping, you only need the combinations where the second symbol is greater than the first one for which the substraction rule applies, as noted above (CM, CD, XC, XL, IX, IV).
Having something like XI in that mapping would give you a wrong result. For XIV you will have XI (11) + V (5) = 16, not X (10) + IV (4) = 14.
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)
I got this as an assignment, but I'm stuck on implementing the area portion. The assignment is already turned in (finished about 80%). I still want to know how to implement this, though. I am stuck on line 84-86. Here is the prompt.
Prompt: Calculate the area of irregularly shaped polygons using JS
INPUT: a nested array of 3-6 coordinates represented as [x,y] (clockwise order)
OUTPUT: area calculated to 2 significant figures
Pseudocode:
loop the input and check for error cases:
a. array length < 3 or array length > 6
b. array is not empty
c. numbers within -10 and 10 range
loop to each inner array, and multiply x and y in the formula below:
sum_x_to_y = (X0 *Y1) + (X1* Y2)...X(n-1)* Yn
sum_y_to_x = (Y0 * X1) + (Y1-X2)...Y(n-1)* Xn
ex:
(0, -10) (7,-10) (0,-8) (0,-10)
| x | y |
| 0 |-10 |
| 7 |-10 |
| 0 |-8 |
| 0 |-10 |
sum_x_to_y = 0*-10 + 7*-8 + 0*-10 = -56
sum_y_to_x = -10*7 + -10*0 + -8*0 = -70
area = (sum_y_to_x - sum_x_to_y) / (2.00)
ex: area = -70 -(-56) = 57/2 = 7
return area.toPrecision(2) to have one sig fig
function PaddockArea(array_coords) {
var sum_x_to_y = 0;
var sum_y_to_x = 0;
var arr_size = array_coords.length;
if (arr_size === 0) {
//check for empty array
console.log("Invalid input. Coordinates cannot be empty.");
}
if (arr_size < 3 || arr_size > 7) {
//check input outside of 3-6 range
console.log("Input out of range.");
}
for (var i = 0; i < arr_size; i++) {
for (var j = 0; j < array_coords[i].length; j++) {
//test for inner coordinates -10 to 10 range
if (array_coords[i][j] < -10 || array_coords[i][j] > 10) {
console.log("Coordinates outside of -10 to 10 range.");
}
// I NEED TO IMPLEMENT TO calc for AREA here
sum_x_to_y += array_coords[i][j] * array_coords[j][i];
sum_y_to_x += array_coords[j][i] * array_coords[i][j];
var area = (sum_y_to_x - sum_x_to_y) / 2;
console.log(area.toPrecision(2) + "acres");
}
}
}
If you're just using Simpson's rule to calculate area, the following function will do the job. Just make sure the polygon is closed. If not, just repeat the first coordinate pair at the end.
This function uses a single array of values, assuming they are in pairs (even indexes are x, odd are y). It can be converted to using an array of arrays containing coordinate pairs.
The function doesn't do any out of bounds or other tests on the input values.
function areaFromCoords(coordArray) {
var x = coordArray,
a = 0;
// Must have even number of elements
if (x.length % 2) return;
// Process pairs, increment by 2 and stop at length - 2
for (var i=0, iLen=x.length-2; i<iLen; i+=2) {
a += x[i]*x[i+3] - x[i+2]*x[i+1];
}
return Math.abs(a/2);
}
console.log('Area: ' + areaFromCoords([1,1,3,1,3,3,1,3,1,1])); // 4
console.log('Area: ' + areaFromCoords([0,-10, 7,-10, 0,-8, 0,-10,])); // 7
Because you haven't posted actual code, I haven't input any of your examples. The sequence:
[[1,0],[1,1],[0,0],[0,1]]
is not a polygon, it's a Z shaped line, and even if converted to a unit polygon can't resolve to "7 acres" unless the units are not standard (e.g. 1 = 184 feet approximately).
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: