D3.js polylinear quantize scale - javascript

I want this kind of map function:
for 0 <= x < 0.96, returns 'red';
for 0.96 <= x < 0.98, returns 'yellow';
for 0.98 <= x <= 1, returns 'green'.
I tried to use this but it doesnt work as I expected:
//<0.96: Red, 0.96 - 0.98: Yellow, 0.98-1: Green
var color = d3.scale.quantize()
.domain([0, 0.96, 0.98, 1])
.range(["red", "yellow", "green"]);
How to express that function in D3js? Thanks!

I found the solution. Actually I should not use quantize scale, quantile scale is the correct answer:
//<0.96: Red, 0.96 - 0.98: Yellow, 0.98-1: Green
var color = d3.scale.quantile()
.domain([0, 0.96, 0.98, 1])
.range(["red", "yellow", "green"]);
According to this page (https://github.com/mbostock/d3/issues/751): "the quantize scale does (perhaps improperly, and inconsistent with the documentation) allow the domain to be specified with more than two numbers; the intermediate numbers are ignored. This feature was added for parity with d3.scale.linear and other quantitative scales which allow polylinear domains. The quantize scale does not compute the extent of the input domain.".

Related

Personalized axis range

I have values going these kinds of range (400k to 600 million)
And I would like to create a comprehensive y-axis on a D3 plot.
If I use a log scale, all the variations in the huge numbers are erased, If I use a linearScale, all variations in the small numbers is also erased.
Therefore I thought of doing two-axis (one over the other like in the picture below) but I don't know if there is a simpler way.
Can I specify all the tick values to get an axis where all the variations would be visible?
Thank you.
Use a regular linear scale with more than two values in both the domain and range, thus creating a piecewise scale.
For instance:
const scale = d3.scaleLinear()
.domain([0, 5e7, 1e8, 6e8])
.range([h-10, h/2 + 10, h/2 - 10, 10]);
Here is the running code:
const svg = d3.select("svg");
const h = 600;
const scale = d3.scaleLinear()
.domain([0, 5e7, 1e8, 6e8])
.range([h - 10, h / 2 + 10, h / 2 - 10, 10]);
const axis = d3.axisLeft(scale).tickValues(scale.ticks().concat(d3.range(0, 5e7, 5e6)))(svg.append("g").attr("transform", "translate(100,0)"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="200", height="600"></svg>
Do not forget to make sure that the user understands the y axis is truncated, preferably with a clear annotation showing that.

create color scale between two colors

let colors = ["#5D2EE8", "#2F9EEE", "#2FC8EE", "#2DD91A", "#CBF22C",
"#F2CE2C", "#F06E1D", "#E61717", "#3C2EA8", "#7A2EA1"];
let colorRange = d3.scale.quantile()
.domain([0, 31])
.range(colors);
I want to replace this implementation with the creation of a color palette of x colors between two colors ?
For example I want to map my set of 90 numbers between 0 and 31 to 9 colors between yellow and red. Is this possible ?
I'm using v3
You can create a sequential scale with a custom interpolator, or a threshold scale with a custom array of colors.
However, since you want to conveniently use any two colors as the start and end colors, I reckon that the easiest solution is using two scales.
The first one maps all the numbers from 0 to 31 to 9 values only:
var scale = d3.scaleQuantize()
.range(d3.range(9))
.domain([0, 31]);
Then, another scale maps those 9 values to 9 colors between any two given colors. In your case, "yellow" and "red":
var colorScale = d3.scaleLinear()
.range(["yellow", "red"])
.domain([0, 9]);
I'm using v4 here, you can just change them to d3.scale.quantize() and d3.scale.linear().
Here is a demo (also using v4), I'm creating 150 divs, each one representing a number between 0 and 31. The output has only 9 colors:
var scale = d3.scaleQuantize()
.range(d3.range(9))
.domain([0, 31]);
var colorScale = d3.scaleLinear()
.range(["yellow", "red"])
.domain([0, 9]);
var divs = d3.select("body").selectAll(null)
.data(d3.range(0, 31.2, 0.2))
.enter()
.append("div")
.style("background-color", function(d) {
return colorScale(scale(d))
});
div {
margin: 2px;
width: 15px;
height: 15px;
display: inline-block;
border: 1px solid gray;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
If you don't want to use two scales, just use math before passing the number to the color scale for creating 9 bins.

Make circles not go outside of the chart bounds with D3

I am working on a chart looking like this now:
I use d3 scales and ranges to setup sizes and coordinates of circles, from JSON data.
All works fine but I need to make sure those circles that are close to extreme values don't overlap the sides of the chart (like orange circle on the top right and blue one on the bottom side), so I think I need to play with ranges and change coordinates in case they overlap or is there a better tried way to do this?
When drawing circles, in addition to the x and y scaling functions we also use an r scaling function:
var rScale = d3.scale.linear()
.domain([0, maxR])
.range([0, maxBubbleRadius]);
var xScale = d3.scale.linear()
.domain([minX, maxX])
.range([0, chartWidth]);
var yScale = d3.scale.linear()
.domain([minY, maxY])
.range([chartHeight, 0]);
where maxR is the largest r value in your dataset and maxBubbleRadius is however large you want the largest circle to be, when you plot it.
Using the x and y scaling functions it is easy to calculate where the centre of each circle will be plotted, we can then add on the (scaled) r value to see if the circle will spill over a chart boundary. With a scenario like the first chart below we can see that 4 of the circles spill over. The first step to remedy this is to find out how many vertical and horizontal units we spill over by and then increase the minimum and maximum x and y values to take this into account, before recalculating the xScale and yScale vars. If we were to then plot the chart again, the boundary would move out but there would probably still be some visible spillage (depending on actual values used); this is because the radius for a given circle is a fixed number of pixels and will therefore take up a different number of x and y units on the chart, from when we initially calculated how much it spilled over. We therefore need to take an iterative approach and keep applying the above logic until we get to where we want to be.
The code below shows how I iteratively achieve an acceptable scaling factor so that all the circles will plot without spilling. Note that I do this 10 times (as seen in the loop) - I've just found that this number works well for all the data that I've plotted so far. Ideally though, I should calculate a delta (the amount of spillage) and iterate until it is zero (this would also require overshooting on the first iteration, else we'd never reach our solution!).
updateXYScalesBasedOnBubbleEdges = function() {
var bubbleEdgePixels = [];
// find out where the edges of each bubble will be, in terms of pixels
for (var i = 0; i < dataLength; i++) {
var rPixels = rScale(_data[i].r),
rInTermsOfX = Math.abs(minX - xScale.invert(rPixels)),
rInTermsOfY = Math.abs(maxY - yScale.invert(rPixels));
var upperPixelsY = _data[i].y + rInTermsOfY;
var lowerPixelsY = _data[i].y - rInTermsOfY;
var upperPixelsX = _data[i].x + rInTermsOfX;
var lowerPixelsX = _data[i].x - rInTermsOfX;
bubbleEdgePixels.push({
highX: upperPixelsX,
highY: upperPixelsY,
lowX: lowerPixelsX,
lowY: lowerPixelsY
});
}
var minEdgeX = d3.min(bubbleEdgePixels, function(d) {
return d.lowX;
});
var maxEdgeX = d3.max(bubbleEdgePixels, function(d) {
return d.highX;
});
var minEdgeY = d3.min(bubbleEdgePixels, function(d) {
return d.lowY;
});
var maxEdgeY = d3.max(bubbleEdgePixels, function(d) {
return d.highY;
});
maxY = maxEdgeY;
minY = minEdgeY;
maxX = maxEdgeX;
minX = minEdgeX;
// redefine the X Y scaling functions, now that we have this new information
xScale = d3.scale.linear()
.domain([minX, maxX])
.range([0, chartWidth]);
yScale = d3.scale.linear()
.domain([minY, maxY])
.range([chartHeight, 0]);
};
// TODO: break if delta is small, rather than a specific number of interations
for (var scaleCount = 0; scaleCount < 10; scaleCount++) {
updateXYScalesBasedOnBubbleEdges();
}
}

Custom axis function in D3JS

I'm using D3JS and I want an axis in x with this kind of values : 125, 250, 500, 1000 ... until 8000. So multiply by 2 my values each time.
So I tried a Quantize Scales but axis do not support it.
How can I do this ? Can I do a custom mathematical function like y = mx + b (where m = 2 in my case and b = 0) and use it in axis?
Here you can see my code
Podelo
The linear scale is pretty flexible if you mass in multiple values for the range and domain to create a polylinear scale:
tickWidth = (ChartWidth - padding)/7
xScale = d3.scale.linear()
.domain([0, 125, 250, 500, 1000, 2000, 4000, 8000])
.range(d3.range(8).map(function(d){ return d*tickWidth; }));
http://jsfiddle.net/h2juD/6/

Determining a rule: relate integer value to color #RGB

I'd like to determine a background-color C depending on an integer X (number of positive votes)
OK, I could do
if(x==0) c = '#000';
else if(x > 0 && x < 5 ) c = '#001'
else if(X<=5 <= 20) c = '#002';
//and so on...
But, how can I do this to make it gradual? I mean from 0 to 500 votes -> 500 tones of blue colors? (if I'm not wrong its posible two digits for HEX (if not, 15*15 xD))
Any ideas?
Since you're just looking for ideas, here's one that nobody else will suggest. First, go look up "Catmull-Rom Spline" formulas. It boils down to a very simple matrix multiplication trick that gives you a formula for computing curves given a set of control points.
OK, so now that you know all about Catmull-Rom splines, you can write some code to do a curve in 3-space. Well, what's RGB if not a 3-dimensional space? So you pick a few good color control points (RGB colors), and then you use your handy Catmull-Rom implementation to generate a parametric curve through all those colors, with as many colors along the curve as you want.
The cool thing about the color progressions will be that they're really "smooth" across transitions.
I would probably use something like:
var blueness = x / 2;
if (blueness > 255) blueness = 255;
rgb(0, 0, blueness);
but you can use this sprintf implementation and use it like you would in any other language to convert to hex.
Include raphaeljs on your page and paste the functions estimateColorForValue() and toHex() somewhere in your code. estimateColorForValue(hue, value, darkestValue, brightestValue) computes a color for some value, interpolating the color by seeing where in the range [darkestValue-brightestValue] value is. hue is in the range [0-1]: 0.1 for orange-browny, 0.4 for green, 0.8 for pinkish, and many more colors in between. small changes in hue drastically change the visual color.
For example: estimateColorForValue(.1, 15, 1, 20), can be explained as, for data ranging 1 through 20, compute color for value 15, in the orangy space.
toHex(estimateColorForValue(.1, 15, 1, 20)) ==> "#cf8415"
Code:
<script src="https://raw.github.com/DmitryBaranovskiy/raphael/master/raphael.js"></script>
<script>
function toHex(hsb) {
return Raphael.hsb2rgb(hsb.h, hsb.s, hsb.b).hex;
}
function estimateColorForValue(hue, value, darkestValue, brightestValue) {
// Constants to determine saturation and brightness
var darkBrightness = 0.6;
var brightBrightness = 1;
var darkSaturation = 0.3;
var brightSaturation = 1;
// Compute saturation and brightness:
var gradient = (value - darkestValue) / (brightestValue - darkestValue);
var saturation = darkSaturation + gradient * (brightSaturation - darkSaturation);
var brightness = darkBrightness + gradient * (brightBrightness - darkBrightness);
return {h: hue, s:saturation, b:brightness};
}
</script>

Categories