Related
I have x amounts of arrays containing speed values.
The values are in m/s and I don't know how many there are and how large they are. I need to create a function to decide on what color to use.
My idea was to find the max and min value of the array (the speeds), and since I know the values are all dividable with 0.25 I wanted to count the possible amount of "steps" by doing var steps = (max - min) / 0.25
Since I have an RGB spectrum I thought I could somehow calculate what value to use, but I simply can't wrap my head around what to do.
What I want to be able to do was to have slower speeds be red'ish, medium speeds to be green'ish and fast speeds to be blue'ish.
An example could be that I have an array:
speeds = [0.5, 0.5, 0.75, 1.25, 0.50, 0.75, 1.00, 4.00, 4.50, 8.00, 7.50, 8.50, 6.50, 6.00, 5.00, 5.25, 4.75, 4.00, 3.25, 2.50, 1.25, 0.00]
Now, for each value I have I want to calculate a color where the largest values will be more intense the larger they are (in the blue spectrum - something like (0, 0, 255)) while the smaller values will be more intense (in the red spectrum - (255, 0, 0)) the lower they are. And for the middle values I thought they could more intense in the green color (0, 255, 0) if it is absolutely in the middle, and then adding either a little bit of red or blue based on which side they are leaning towards.
I have tried to look for a plugin that could do this for me but I am unable to find such and I have also tried googling for a way to do this, but without any luck.
You could calculate the color of the two areas and use the three colors for generating the gradient.
function getColor(v, min, max) {
function getC(f, l, r) {
return {
r: Math.floor((1 - f) * l.r + f * r.r),
g: Math.floor((1 - f) * l.g + f * r.g),
b: Math.floor((1 - f) * l.b + f * r.b),
};
}
var left = { r: 255, g: 0, b: 0 },
middle = { r: 0, g: 255, b: 0 },
right = { r: 0, g: 0, b: 255 },
mid = (max - min) / 2;
return v < min + mid ?
getC((v - min) / mid, left, middle) :
getC((v - min - mid) / mid, middle, right);
}
var speeds = [0.5, 0.5, 0.75, 1.25, 0.50, 0.75, 1.00, 4.00, 4.50, 8.00, 7.50, 8.50, 6.50, 6.00, 5.00, 5.25, 4.75, 4.00, 3.25, 2.50, 1.25, 0.00],
min = Math.min(...speeds),
max = Math.max(...speeds);
speeds.forEach(function (a) {
var color = getColor(a, min, max);
document.body.innerHTML += '<span style="color: #fff; background-color: rgb(' + color.r + ',' + color.g + ',' + color.b + ');">' + a + '</span> ';
});
Suppose min is the minimum speed and max is the maximum speed, then all the speeds are between min and max:
min |---------------------------| max
speeds
You have to partition this interval into two smaller intervals, like this:
|-------------|--------------|
min mid max
You can assign to min full Red, to mid full Green, and to max full Blue:
R G B
|-------------|--------------|
min mid max
Now you have to compute for each speed value its color. Suppose that s is the value of one of your speeds:
r = g = b = 0;
if (s <= mid) {
r = 255 - (s - min) / (mid - min) * 255; // r is 255 when s = min and 0 when s = mid
g = 255 - (mid - s) / (mid - min) * 255; // g is 255 when s = mid and 0 when s = min
} else {
b = 255 - (s - mid) / (max - mid) * 255;
g = 255 - (max - s) / (max - mid) * 255;
}
Given your array of speeds, you can do the following:
var speeds = [0.5, 0.5, 0.75, 1.25, 0.50, 0.75, 1.00, 4.00, 4.50, 8.00, 7.50, 8.50, 6.50, 6.00, 5.00, 5.25, 4.75, 4.00, 3.25, 2.50, 1.25, 0.00]
var max = Math.max(...speeds);
var min = Math.min(...speeds);
var mid = (max - min) / 2;
var colors = speeds.map((s) => {
var r, g, b;
r = g = b = 0;
if (s <= mid) {
r = 255 - (s - min) / (mid - min) * 255;
g = 255 - (mid - s) / (mid - min) * 255;
} else {
b = 255 - (s - mid) / (max - mid) * 255;
g = 255 - (max - s) / (max - mid) * 255;
}
return [r, g, b];
});
console.log(colors);
The array colors will contain an [r, g, b] list for each speed in speeds.
Another option would be to simple calculate the hsl value, since you already know the exact colors you are dealing with. Converting from hsl to rgb should not be to hard, there are plenty of libraries out there that do that very well.
Here is an example.
var speeds = [0.5, 0.5, 0.75, 1.25, 0.50, 0.75, 1.00, 4.00, 4.50, 8.00, 7.50, 8.50, 6.50, 6.00, 5.00, 5.25, 4.75, 4.00, 3.25, 2.50, 1.25, 0.00];
var fragment = document.createDocumentFragment();
var list = document.querySelector('ul');
var speedsMin = Math.min(...speeds);
var speedsMax = Math.max(...speeds);
var hslMin = 0;
var hslMax = 240;
var hslValues = speeds.map(function(value) {
return {
h: Math.ceil( ( (value - speedsMin) / (speedsMax - speedsMin) ) * (hslMax - hslMin) + hslMin ),
s: 100,
l: 50
}
})
hslValues.forEach(function(value) {
var item = document.createElement('li');
var color = 'hsl(' + value.h + ',' + value.s + '%,' + value.l + '%)';
item.style.backgroundColor = color;
fragment.appendChild(item)
})
list.appendChild(fragment)
ul {
list-style-type: none
margin: 0;
padding: 0;
}
ul li {
width: 20px;
height: 20px;
border-radius: 10px;
display: inline-block;
margin: 0 4px
}
<ul></ul>
var min...,max...;
var mid=(min+max)/2;
speeds.foreach(function(x,idx){
var r,g,b;
if(x<mid){
b=0;
g=255*(x-min)/(mid-min);
r=255-g;
}else{
r=0;
g=255*(max-x)/(max-mid);
b=255-g;
}
// do something with r-g-b here.
});
The idea is something like this, but after writing I have a hard time bending my brain to verify it. I think it is a correct red->green->blue 2-segment gradient now.
Above 2-3 gradient-segments, I would just really create a palette.
var r=[];g=[];b=[];
// black-red
for(var i=0;i<256;i++){
r.push(i);
g.push(0);
b.push(0);
}
// red-green
for(var i=1;i<256;i++){
r.push(255-i);
g.push(i);
b.push(0);
}
// green-blue
for(var i=1;i<256;i++){
r.push(0);
g.push(255-i);
b.push(i);
}
// blue-white
for(var i=1;i<256;i++){
r.push(i);
g.push(i);
b.push(255);
}
Then you have a palette of 1021 elements, index is x*r.length/(max-min)|0.
A more JavaScript-ish iteration of the latter:
var colors=[];
// black-red
for(var i=0;i<256;i++)colors.push({r:i,g:0,b:0}); // black-red
for(var i=1;i<256;i++)colors.push({r:255-i,g:i,b:0}); // red-green
for(var i=1;i<256;i++)colors.push({r:0,g:255-i,b:i}); // green-blue
for(var i=1;i<256;i++)colors.push({r:i,g:i,b:255}); // blue-white
speeds.foreacy(function(x,idx){
var color=colors[x*colors.length/(max-min)|0]; // r-g-b fields with values 0-255
...
}
If you aren't opposed to using a library you could check out D3.js, specifically the utility to create custom scales. An example of this can be found here
Example
You would need to set up your color scale using your speeds array as a domain and the colors as the output range:
let colors = d3.scale.linear().domain([0, Math.max(...speeds)])
.interpolate(d3.interpolateHcl)
.range([d3.rgb('#FF0000'), d3.rgb('#0000FF')]);
colors is now a function that, given an index as input, will output a color. After that set up, loop through the speeds array to get the corresponding color:
for (let i = 0; i < speeds.length; i++) {
// colors(i)
}
I would sort this array and after this divide your array into 3 smaller arrays. After this operation you should normalize your arrays. You can do it by multiply each element in the list using formula:
(xi - min)/(max - min).
You have to normalize your new arrays not the old one. For first array (the smallest value) you can count red intensive by k=(max-xi)*255.0 . This colors will be (k, 0, 0) . The array with the biggest speeds you colorize by formula: k=xi*255.0 [colors will be (0,0,k)]. The formula for mid values depends on your choice. (Higher speed = more green or Higher speed = less speed).
I'm sorry for complicated description. Good luck.
I have a Dataset (~ 100mb) and want to get a better understanding of the data by first visualizing the amount of different JSON values.
I started by drawing an arc with ctx.arc(); and increasing the radius for each occurrence of a value.
switch(data[i].value) {
case "X":
ctx.beginPath();
ctx.arc(x, 100, i+1, 0, 2*Math.PI);
ctx.fillStyle = "MidnightBlue";
ctx.fill();
break;
}
The arc is drawn but way to big and goes beyond my viewport. So it seems that a) I'm making a mistake or b) there are just to much occurrences of the value, which cause the circle to become gigantic. How could I counter that problem?
Visualizing large values
There are two ways to visualize data that has large values.
You have given no clue as to the structure of the data so mostly I am just guessing about the data.
Scale and translate.
If the distribution of values is roughly linear you can scale and move the values to fit within the needed range.
To do this you go thought all the data points one at a time and find the minimum value and max value.
var min = Infinity; // set the start min and max
var max = -Infinity;
for (var i = 0; i < data.length; i++){
if (data[i].value === "X") {
// I dont know where you got the x from so leave that to you
// x is the value needed to graph
min = Math.min(min, x);
max = Math.max(max, x);
}
}
After you have check each value and have a min and max you need to workout how big you want to display the info.
const displayMaxRadius = Math.min(canvas.width, canvas.height) / 2;
const displayMinRadius = 10;
Then to display each value you use the min and max to scale to a normalized range, bringing each value to be within 0 to 1 inclusive. The scale to fit te display min and max
for (var i = 0; i < data.length; i ++) {
if (data[i].value === "X") {
// I dont know where you got the x from so leave that to you
// x is the value needed to graph
var norm = (x - min) / (max - min); // normalize the value
var displaySize = norm * (displayMaxRadius - displayMinRadius) + displayMinRadius;
ctx.beginPath();
ctx.arc(displaySize , 100, i + 1, 0, 2 * Math.PI);
ctx.fillStyle = "MidnightBlue";
ctx.fill();
Logarithmic data
Sometimes the range of values is spread unevenly over a very large range, with clumps of data at some ranges. Using the above method will work but for most of the data it will be scaled such that the individual differences are lost due to the large range of values.
To deal with that you create a logarithmic graph, simple find the root of the values before you find the min max range. You can use the square root or to any other value.
Use Math.pow(x,1/r) where r is to what root you want r = 2 is square root, r = 3 is cubic root, and so on
var root = 2; // sqrt root
var min = Infinity; // set the start min and max
var max = -Infinity;
for (var i = 0; i < data.length; i++) {
if (data[i].value === "X") {
// I dont know where you got the x from so leave that to you
// x is the value needed to graph
var rval = Math.pow(x, root);
min = Math.min(min, rval);
max = Math.max(max, rval);
}
}
for (var i = 0; i < data.length; i++) {
if (data[i].value === "X") {
// I dont know where you got the x from so leave that to you
// x is the value needed to graph
var rval = Math.pow(x, root);
var norm = (rval - min) / (max - min); // normalize the value
var displaySize = norm * (displayMaxRadius - displayMinRadius) + displayMinRadius;
ctx.beginPath();
ctx.arc(displaySize , 100, i + 1, 0, 2*Math.PI);
ctx.fillStyle = "MidnightBlue";
ctx.fill();
I sort of found an answer to my question myself. What I could do is create a grid/rectangle with modulo.
var x = (i % 115) * 1;
var y = Math.floor(i / 115) * 1;
ctx.fillStyle = "MidnightBlue";
ctx.fillRect(x, y, 1, 1);
As you can see I have key/value pairs of the states in the US. To visualize the occurrence of each state in the dataset I want to draw a grid with modulo.
The number 115 is the root of 13450. But 13450 is the amount of (for example) all farms in the US. Now I want to visualize just the farms in PA … How could I do that?
So, I have a code that is rolling a random number from 1024 to 4096 and changing backgroundPosition to rolled number. (jsfiddle)
function slide()
{
var x = 1;
var y = 30;
var z = Math.floor((Math.random() * 4096) + 1024); // background offset
var zz = Math.floor((Math.random() * 14) + 0); // number
var clr = setInterval(function()
{
if(x >= z) x = 1;
document.getElementById("slide").style.backgroundPosition = x+"px 0px";
x+=y;
y-=0.1;
if (y<=0)
{
clearInterval(clr);
document.getElementById('rolledNumber').innerHTML = z;
}
}
,10);
}
"z" is a random number between 1024 and 4096, how can I check what number on image it is? I tried if (z >= 2285 && z <= 2210){var zz = 4;}, where "zz" is a number on image, but it's not working.
Hope you guys can understand it.
You have a few problems here:
1) if (z >= 2285 && z <= 2210)
is impossible. There is no number that satisfies both "larger than 2284" AND "smaller than 2211".
2) "z" is a random number between 1024 and 4096
var z = Math.floor((Math.random() * 4096) + 1024); will actually create a random number between 1024 and 5120. Imagine the case where Math.random() returns 1. Your result would be 1 * 4096 + 1024, or 5120.
3) Your background image is repeating - if you stick to one set of 15 numbers, you could access the number by mapping the pixel offset to an array.. something like this:
var boxWidth = 150;
var boxes = [1, 14, 2, 13, 3, 12, 4, 0, 11, 5, 10, 6, 9, 7, 8];
function getNumber (offset) {
var boxNumber = Math.floor(offset / boxWidth);
return boxes[boxNumber];
}
4) No one knows the application of this logic other than you, please reword your question such that it actually makes any sense and act like you've tried to find the problem yourself.
If anyone is interested - I figured out how to do this. I'm checking "x" value, and if it's between x and y, that means rolled number is z. Fragment of code:
if (x >= 410 && x <= 485){var rolledNumber = 1;}
if (x >= 335 && x <= 410){var rolledNumber = 14;}
if (x >= 260 && x <= 335){var rolledNumber = 2;}
I have 10 sets of data coming in, and I need to set a radius for the first one to be the largest and then decrease by 10%.
my index comes in like: 1, 2, 3, 4, 5....
I am taking the index and dividing it by 1
var radiusTest = 1 / (index);
circleRadius = radiusTest * 100
Then I am multiplying it by 100, it is close to what I need but it does not degrade as nicely as I would like. Having each radius decrease by 10% is what I am looking for, not sure the best way of setting it up?
I think you are going for something like this:
var radiusTest = 1 - index * .1;
circleRadius = radiusTest * 100
So you have a value starting at 100% and decreasing in equal amounts over 10 iterations, or by 10%. Here is a way to implement this in JavaScript.
var steps = 10,
step,
stepSize = 1/steps,
radii = [];
for(step = 0; step <= steps; step++) {
radii[step] = (1-(stepSize * step)) * 100;
}
The array radii should contain:
[100, 90, 80, 70, 60, 50, 40,30,20, 10, 0]
You can mess with the constants to see different steps and step sizes.
When drawing a linechart with gRaphael using milliseconds along the x-axis I commonly get inconsistencies in the placement of the data points. Most commonly the initial data points are to the left of the y-axis (as seen in the fiddle below), sometimes the last data-point will be beyond the right side of the view-box/past the termination of the x-axis.
Does anyone know:
1) Why this occurs,
2) How to prevent it, &/or
3) How to check for it (I can use transform to move the lines/points if I know when it has happened/by how much).
my code:
var r = Raphael("holder"),
txtattr = { font: "12px sans-serif" };
var r2 = Raphael("holder2"),
txtattr2 = { font: "12px sans-serif" };
var x = [], y = [], y2 = [], y3 = [];
for (var i = 0; i < 1e6; i++) {
x[i] = i * 10;
y[i] = (y[i - 1] || 0) + (Math.random() * 7) - 3;
}
var demoX = [[1, 2, 3, 4, 5, 6, 7],[3.5, 4.5, 5.5, 6.5, 7, 8]];
var demoY = [[12, 32, 23, 15, 17, 27, 22], [10, 20, 30, 25, 15, 28]];
var xVals = [1288885800000, 1289929440000, 1290094500000, 1290439560000, 1300721700000, 1359499228000, 1359499308000, 1359499372000];
var yVals = [80, 76, 70, 74, 74, 78, 77, 72];
var xVals2 = [1288885800000, 1289929440000];
var yVals2 = [80, 76];
var lines = r.linechart(10, 10, 300, 220, xVals, yVals, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
this.tags = r.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
}
}, function () {
this.tags && this.tags.remove();
});
lines.symbols.attr({ r: 3 });
var lines2 = r2.linechart(10, 10, 300, 220, xVals2, yVals2, { nostroke: false, axis: "0 0 1 1", symbol: "circle", smooth: true })
.hoverColumn(function () {
this.tags = r2.set();
for (var i = 0, ii = this.y.length; i < ii; i++) {
this.tags.push(r.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{ fill: "#fff" }, { fill: this.symbols[i].attr("fill") }]));
}
}, function () {
this.tags && this.tags.remove();
});
lines2.symbols.attr({ r: 3 });
I do have to use gRaphael and the x-axis has to be in milliseconds (it is labeled later w/customized date strings)
Primary example fiddle: http://jsfiddle.net/kcar/aNJxf/
Secondary example fiddle (4th example on page frequently shows both axis errors):
http://jsfiddle.net/kcar/saBnT/
root cause is the snapEnds function (line 718 g.raphael.js), the rounding it does, while fine in some cases, is adding or subtracting years from/to the date in other cases.
Haven't stepped all the way through after this point, but since the datapoints are misplaced every time the rounding gets crazy and not when it doesn't, I'm going to go ahead and assume this is causing issues with calculating the chart columns, also before being sent to snapEnds the values are spot on just to confirm its not just receiving miscalculated data.
code of that function from g.raphael.js
snapEnds: function(from, to, steps) {
var f = from,
t = to;
if (f == t) {
return {from: f, to: t, power: 0};
}
function round(a) {
return Math.abs(a - .5) < .25 ? ~~(a) + .5 : Math.round(a);
}
var d = (t - f) / steps,
r = ~~(d),
R = r,
i = 0;
if (r) {
while (R) {
i--;
R = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
}
i ++;
} else {
if(d == 0 || !isFinite(d)) {
i = 1;
} else {
while (!r) {
i = i || 1;
r = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
i++;
}
}
i && i--;
}
t = round(to * Math.pow(10, i)) / Math.pow(10, i);
if (t < to) {
t = round((to + .5) * Math.pow(10, i)) / Math.pow(10, i);
}
f = round((from - (i > 0 ? 0 : .5)) * Math.pow(10, i)) / Math.pow(10, i);
return { from: f, to: t, power: i };
},
removed the rounding nonsense from snapEnds and no more issues, not noticed any downside from either axis or any other area of the chart. If you see one I'd love to hear it though.
code of that function from g.raphael.js now:
snapEnds: function(from, to, steps) {
return {from: from, to: to, power: 0};
},
Hi if you comment this:
if (valuesy[i].length > width - 2 * gutter) {
valuesy[i] = shrink(valuesy[i], width - 2 * gutter);
len = width - 2 * gutter;
}
if (valuesx[i] && valuesx[i].length > width - 2 * gutter) {
valuesx[i] = shrink(valuesx[i], width - 2 * gutter);
}
in g.line.js, It seems to solve the problem, and it also solves a similar problem with the values in the y axis.
Upgrading from v0.50 to v0.51 fixed the issue for me.
Still not sure why it occurs, adding in a transparent set was not a desirable option.
The simplest way to check for if the datapoints were rendered outside of the graph seems to be getting a bounding box for the axis set and a bounding box for the datapoints and checking the difference between the x and x2 values.
If anyone can help me with scaling the datapoint set, or figure out how to make this not happen at all, I will still happily appreciate/up vote answers
//assuming datapoints is the Raphael Set for the datapoints, axes is the
//Raphael Set for the axis, and datalines is the Raphael Set for the
//datapoint lines
var pointsBBox = datapoints.getBBox();
var axesBBox = axes.getBBox();
var xGapLeft = Math.ceil(axesBBox.x - pointsBBox.x);
//rounding up to integer to simplify, and the extra boost from y-axis doesn't
//hurt, <1 is a negligible distance in transform
var xGapRight = Math.ceil(axesBBox.x2 - pointsBBox.x2);
var xGap = 0;
if(xGapLeft > 0){
datapoints.transform('t' +xGapLeft +',0');
datalines.transform('t' +xGapLeft +',0');
xGap = xGapLeft;
}else if (xGapRight < 0) { //using else if because if it is a scale issue it will
//be too far right & too far left, meaning both are true and using transform will
//just shift it right then left and you are worse off than before, using
//set.transform(scale) works great on dataline but when using on datapoints scales
// symbol radius not placement
if (xGapLeft < 0 && xGapRight < xGapLeft) { xGapRight = xGapLeft; }
//in this case the initial point is right of y-axis, the end point is right of
//x-axis termination, and the difference between last point/axis is greater than
//difference between first point/axis
datapoints.transform('t' +xGapRight +',0');
datalines.transform('t' +xGapRight +',0');
xGap = xGapRight;
}
rehookHoverOverEvent(xGap); //there are so many ways to do this just leaving it
//here as a call to do so, if you don't the hover tags will be over the original
//datapoints instead of the new location, at least they were in my case.