How to create a diverging data legend on a choroplet map? - html

I'm trying to create a choroplet map with diverging data (going from -0.7 to 0.7) and can't find a way so that my legend show the correct colors. The problem is because when colors are all positive signs is always > and is sequential. Now because I have negative numbers, it doesn't work anymore. How can I correct this?
// get color depending on population differentiel value
function getColor(d) {
return d > 0.7 ? '#b2182b' :
d > 0.5 ? '#d6604d' :
d > 0.3 ? '#f4a582' :
d > 0.1 ? '#fddbc7' :
d < -0.7 ? '#2166ac' :
d < -0.5 ? '#4393c3' :
d < -0.3 ? '#92c5de' :
d < -0.1 ? '#d1e5f0' :
'#f7f7f7';
}
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [-0.7, -0.5, -0.3, -0.1, 0.1, 0.3, 0.5, 0.7],
labels = [],
from, to;
for (var i = 0; i < grades.length; i++) {
from = grades[i];
to = grades[i + 1];
labels.push(
'<i style="background:' + getColor(from + 1) + '"></i> ' +
from + (to ? ' à ' + to : '+'));
}
div.innerHTML = labels.join('<br>');
return div;
};

The problem is because when colors are all positive signs is always > and is sequential. Now because I have negative numbers, it doesn't work anymore.
No, it's because you're mixing > and < without any good reason, instead of following a regular pattern of decreasing numbers, e.g.:
function getColor(d) {
return d > 0.7 ? '#b2182b' :
d > 0.5 ? '#d6604d' :
d > 0.3 ? '#f4a582' :
d > 0.1 ? '#fddbc7' :
d > -0.1 ? '#d1e5f0' :
d > -0.3 ? '#92c5de' :
d > -0.5 ? '#4393c3' :
d > -0.7 ? '#2166ac' :
'#f7f7f7';
}
Now the stop points are aligned, so every stop point is the smallest value for the color range.
I'd go even further and store the stops and range colors in a data structure, correlating the smallest end of a range with the corresponding colour:
var stops = [
{ stop: 0.7, color: '#b2182b' },
{ stop: 0.5, color: '#d6604d' },
{ stop: 0.3, color: '#f4a582' },
{ stop: 0.1, color: '#fddbc7' },
{ stop: -0.1, color: '#d1e5f0' },
{ stop: -0.3, color: '#92c5de' },
{ stop: -0.5, color: '#4393c3' },
{ stop: -0.7, color: '#2166ac' },
{ stop: -Infinity, color: '#f7f7f7' },
];
And recreate the getColor() function by looping through that data structure:
function getColor(d) {
for (var i in stops) {
if (d > stops[i].stop) { return stops[i].color; }
}
}
Note that all numbers are greater than -Infinity, so the last entry in that data structure shall work as the default case.
And create a legend by iterating through the same data structure, carrying over the range's upper stop from the previous step (and initializing that at Infinity, as that's the implicit upper stop for the first range):
var rangeMax = 'Infinity';
for (var i in stops) {
var rangeMin = stops[i].stop.toString();
var rangeColour = stops[i].color;
labels.push(
'<i style="background:' + rangeColour + '"></i> ' +
rangeMin + ' à ' + rangeMax
);
rangeMax = stops[i].stop;
}

getColor(from + 1) is the culprit. Because of the "+ 1", you will always be off your scale.
Note that even after removing it, you will be missing the lowest legend (< 0.7). You can handle it in a similar way as your highest legend.

Related

How to dynamically change line color based on value being lower than previous in highcharts

I am trying to build a line chart using highchart.js that uses the color green and black. The data is pulled from an SQL database and if the value is higher than the previous value it is green. If the value is less than the previous value than the color is black.
I am new at this and have been searching and searching but the only things I find is using zones to change the colors.
Can anyone help me on this?
I created this example, it should help you :
series: [{
name: 'Random data',
colorByPoint: true,
data: (function() {
// generate an array of random data
var time = (new Date()).getTime(),
i,
yValue;
for (i = -19; i <= 0; i += 1) {
yValue = Math.random();
if (i > -19 && yValue > data[data.length - 1].y) { // Green point
zones.push({
color: "#5f9",
value: time + i * 1000,
});
} else if (i > -19 && yValue <= data[data.length - 1].y) { // black point
zones.push({
color: "#000",
value: time + i * 1000,
});
} else { // first point alway green
zones.push({
color: "#5f9",
value: time + i * 1000,
});
}
data.push({
x: time + i * 1000,
y: yValue
});
}
return data;
}()),
zoneAxis: "x",
zones: zones
}]
Fiddle

How to easily label geoJSON with Leaflet?

This map is of Population Totals in a neighborhood. The layer 'Population2000Layer' includes the attribute field 'Total'. Just need to reach in there and display the total at the polygon's center with some like
choroplethize(feature.properties.Total)
The goal is to use to the Total Population attribute to label each polygon:
function choroplethize(d) {
<!-- jenks breaks from 2010 were applied to 2000 as well-->
return d > 1175 ? '#045a8d' :
d > 1083 ? '#2b8cbe' :
d > 801 ? '#74a9cf' :
d > 711 ? '#bdc9e1' :
'#f1eef6';
}
// styling for the total Population layers
function styleTotalPop(feature) {
return {
weight: 3,
opacity: 0.25,
color: 'black',
dashArray: '3',
fillOpacity: 0.9,
fillColor: choroplethize(feature.properties.Total)
}
}
<!-- add blocks' Populations for 2000 and 2010 as GeoJSON layer -->
var Population2000Layer = L.geoJson(Population2000, {
style: styleTotalPop,
onEachFeature: geojsonPopup2000,
pointToLayer: function (feature, latlng) {
return L.marker(latlng);
}
});
In my case my JS doesn't make choroplethize categories, but I declared some cases with other syntax. I think this could be helpful for you.
These are the attributes:
var calzada = new L.geoJson(data, {
style: function (feature) {
var c;
switch (feature.properties.dpv_cal) {
case 'Natural':
c = '#ffffff';
break;
case 'Mejorada':
c = '#db6adb';
break;
case 'Pavimentada':
c = '#ff0127';
break;
default:
c = '#000000';
}
return {color: c, opacity: 1, weight: 2};
}
}).addTo(map);
This is just a part of my code, obviously, and worked for me. It is a line shape and refers to different routes' categories.

Connect Table Cells from Different Rows with Diagonal line

I'm looking to have diagonal lines connecting table cells from different rows. I can easily get a diagonal line to connect the corners of a single cell, but is there a way to cross cells?
For example, in the image below, I'd like to have diagonal lines connecting "1 and "3" to the "1 & 3" cell. Same thing with 2, 5, and 2 & 5.
Of course, this is just one variation. I would like to be able to connect any number of cells in any way. Maybe something like this...
Any ideas here?
Try this Demo : Demo
I added one more demo, which will take only the cell positions and based on that it will automatically find the positions and connect them accordingly.
It is better than the other answer, in which we need to provide the line start and end points.
HTML Code
<br>
<table border="1" cellpadding="5">
<tr>
<td>1</td>
<td>+</td>
<td>4</td>
<td>1</td>
<td>+</td>
<td>4</td>
</tr>
<tr>
<td></td>
<td>5</td>
<td></td>
<td></td>
<td>5</td>
<td></td>
</tr>
</table>
CSS Code
.line {
width : 2px;
background-color : red;
display : block;
position : absolute;
-webkit-transform-origin : 0 0;
transform-origin : 0 0;
}
.green {
background-color : green;
}
.blue {
background-color : blue;
}
JavaScript Code
$(function() {
connectCells($('table'), {x : 0, y : 0}, {x : 1, y : 1}, 'red');
connectCells($('table'), {x : 2, y : 0}, {x : 1, y : 1}, 'blue');
});
function connectCells(table, cell1, cell2, cls) {
var td1 = table.find('tr:eq(' + cell1.y + ') td:eq(' + cell1.x + ')');
var td2 = table.find('tr:eq(' + cell2.y + ') td:eq(' + cell2.x + ')');
var pos1 = $(td1).position();
var pos2 = $(td2).position();
drawLine({
x : pos1.left + $(td1).outerWidth()/2,
y : pos1.top + $(td1).outerHeight() - 5
}, {
x : pos2.left + $(td2).outerWidth()/2,
y : pos2.top + $(td2).outerHeight() - 5
}, cls);
}
function drawLine(tp1, tp2, cls) {
if(!cls) { cls = "" };
if(tp2.x < tp1.x && tp2.y < tp1.y) {
p1 = tp2;
p2 = tp1;
} else {
p1 = tp1;
p2 = tp2;
}
var ang = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI - 90;
var lin = $('<span></span>').addClass("line " + cls).appendTo('body');
$(lin).css({
'top' : p1.y,
'left' : p1.x,
'height' : Math.sqrt((p1.x - p2.x)* (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)),
'transform' : 'rotate(' + ang + 'deg)'
});
}
Try this : Demo
JavaScript Code
$(function() {
var p = { x : 200, y : 200 },
p1 = { x : 300, y : 150 },
p2 = { x : 200, y : 100 },
p3 = { x : 100, y : 150 };
drawLine(p, p1);
drawLine(p, p2, "green");
drawLine(p, p3, "blue");
});
function drawLine(tp1, tp2, cls) {
if(!cls) { cls = "" };
if(tp2.x < tp1.x && tp2.y < tp1.y) {
p1 = tp2;
p2 = tp1;
} else {
p1 = tp1;
p2 = tp2;
}
var ang = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI - 90;
var lin = $('<span></span>').addClass("line " + cls).appendTo('body');
$(lin).css({
'top' : p1.y,
'left' : p1.x,
'height' : Math.sqrt((p1.x - p2.x)* (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)),
'transform' : 'rotate(' + ang + 'deg)'
});
}
CSS Code
.line {
width : 2px;
background-color : red;
display : block;
position : absolute;
-webkit-transform-origin : 0 0;
transform-origin : 0 0;
}
.green {
background-color : green;
}
.blue {
background-color : blue;
}
It will create a new span and position it at the given co-ordinates using CSS3 transform.
You can find the start and end positions dynamically and pass them to draw lines with optional colors.
You can enhance this as per your needs.

Is the RegEx applied correct?

Trying to translate the following sql query to map reduce mongodb:
select
100.00 * sum(case
when p_type like 'PROMO%'
then l_extendedprice*(1-l_discount)
else 0
end) / sum(l_extendedprice * (1 - l_discount)) as promo_revenue
from
lineitem,
part
where
l_partkey = p_partkey
and l_shipdate >= date '1995-09-01'
and l_shipdate < date '1995-09-01' + interval '1' month;
I used in the collection above:
{
"_id" : ObjectId("511b7d1b3daee1b1446ecdfe"),
"l_linenumber" : 1,
"l_quantity" : 17,
"l_extendedprice" : 21168.23,
"l_discount" : 0.04,
"l_tax" : 0.02,
"l_shipdate" : ISODate("1996-03-13T03:00:00Z"),
"l_commitdate" : ISODate("1996-02-12T03:00:00Z"),
"l_receiptdate" : ISODate("1996-03-22T03:00:00Z"),
"l_comment" : "blithely regular ideas caj",
"partsupp" : {
"ps_availqty" : 6157,
"ps_supplycost" : 719.17,
"ps_comment" : "blithely ironic packages haggle quickly silent platelets",
"ps_partkey" : {
"p_partkey" : NumberLong(155190),
"p_name" : "slate lavender tan lime lawn",
"p_mfgr" : "Manufacturer#4",
"p_brand" : "Brand#44",
"p_type" : "PROMO BRUSHED NICKEL"
}
}
}
This mapreduce:
db.runCommand({
mapreduce: "lineitem",
query: {
l_shipdate: {'$gte': new Date("Sept 01, 1995"), '$lt': new Date("Oct 01, 1995")}
},
map: function() {
var data = {promo_revenue:0, divisor:0, dividendo: 0 };
var pattern = /PROMO$/;
var discount = 1 - this.l_discount;
var revenue = this.l_extendedprice * discount;
if(this.partsupp.ps_partkey.p_type.match(pattern)!= null){
data.divisor = revenue;
}else{
data.divisor = 0;
}
data.dividendo = revenue;
emit("PROMO_REVENUE", data);
},
reduce: function(key, values) {
var data = {promo_revenue:0, divisor:0, dividendo: 0 };
for (var i = 0; i < values.length; i++) {
data.divisor += values[i].divisor;
data.dividendo += values[i].dividendo;
}
var resultado = data.divisor / data.dividendo;
data.promo_revenue = 100.00 * resultado;
return data;
},
out: 'query014'
});
I retrieve this answer:
{ "_id" : "PROMO_REVENUE", "value" : { "promo_revenue" : 0, "divisor" : 0, "dividendo" : 2761949328.227077 } }
The problem:
The RegEx that i was applied, is it correct? It's strange the divisor be ever zero. And only if the divisor be ever zero the answer is correct or not?
Thanks
LIKE 'PROMO%' is not a regex, but % is a wildcard for "zero or more characters of anything." The regex equivalent would probably be /PROMO.*/. /PROMO$/ means "ends with PROMO."

Drawing multiple edges between two nodes with d3

I've been following Mike Bostock's code from this example to learn how to draw directed graphs in d3 and was wondering how I would structure the code so that I could add multiple edges between two nodes in the graph. For example, if the dataset in the example above were defined as
var links = [{source: "Microsoft", target: "Amazon", type: "licensing"},
{source: "Microsoft", target: "Amazon", type: "suit"},
{source: "Samsung", target: "Apple", type: "suit"},
{source: "Microsoft", target: "Amazon", type: "resolved"}];
and then run through the code, all I see is one line. All the paths are being drawn correctly in the html code, however they all have the same coordinates and orientation which causes the visual to look like 1 line. What kind of code restructuring would need to be done in this example to allow for the 3 edges to not be drawn on top of each other?
In fact, the original visualization is a prime example of one method to show multiple links between nodes, that is - using arcs rather than direct paths, so you can see both incoming and outgoing links.
This concept can be extended to show multiple of each of these types of links by changing the radius values of subsequent svg path(arc) elements representing the link. A basic example being
dr = 75/d.linknum;
Where d.linknum represents the number of the successive link. dr is later used as the rx and ry amounts for the arc being drawn.
Full implementation here: http://jsfiddle.net/7HZcR/3/
Here is the source for the answer above if anyone ever needs it :
var links = [{source: "Microsoft", target: "Amazon", type: "licensing"},
{source: "Microsoft", target: "Amazon", type: "suit"},
{source: "Samsung", target: "Apple", type: "suit"},
{source: "Microsoft", target: "Amazon", type: "resolved"}];
//sort links by source, then target
links.sort(function(a,b) {
if (a.source > b.source) {return 1;}
else if (a.source < b.source) {return -1;}
else {
if (a.target > b.target) {return 1;}
if (a.target < b.target) {return -1;}
else {return 0;}
}
});
//any links with duplicate source and target get an incremented 'linknum'
for (var i=0; i<links.length; i++) {
if (i != 0 &&
links[i].source == links[i-1].source &&
links[i].target == links[i-1].target) {
links[i].linknum = links[i-1].linknum + 1;
}
else {links[i].linknum = 1;};
};
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});
var w = 600,
h = 600;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([w, h])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
// Per-type markers, as they don't inherit styles.
svg.append("svg:defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("svg:g").selectAll("circle")
.data(force.nodes())
.enter().append("svg:circle")
.attr("r", 6)
.call(force.drag);
var text = svg.append("svg:g").selectAll("g")
.data(force.nodes())
.enter().append("svg:g");
// A copy of the text with a thick white stroke for legibility.
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.attr("class", "shadow")
.text(function(d) { return d.name; });
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = 75/d.linknum; //linknum is defined above
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
circle.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
marker#licensing {
fill: green;
}
path.link.licensing {
stroke: green;
}
path.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
}
text.shadow {
stroke: #fff;
stroke-width: 3px;
stroke-opacity: .8;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>
And for D3v4 see here : https://bl.ocks.org/mbostock/4600693
Thanks for the answers using linknum, it really worked. however the lines started overlapping after linkum > 10.
Here is a function to generate equidistance quadratic curves
// use it like 'M' + d.source.x + ',' + d.source.y + link_arc2(d) + d.target.x + ',' + d.target.y
function link_arc2(d) {
// draw line for 1st link
if (d.linknum == 1) {
return 'L';
}
else {
let sx = d.source.x;
let sy = d.source.y;
let tx = d.target.x;
let ty = d.target.y;
// distance b/w curve paths
let cd = 30;
// find middle of source and target
let cx = (sx + tx) / 2;
let cy = (sy + ty) / 2;
// find angle of line b/w source and target
var angle = Math.atan2(ty - sy, tx - sx);
// add radian equivalent of 90 degree
var c_angle = angle + 1.5708;
// draw odd and even curves either side of line
if (d.linknum & 1) {
return 'Q ' + (cx - ((d.linknum - 1) * cd * Math.cos(c_angle))) + ',' + (cy - ((d.linknum - 1) * cd * Math.sin(c_angle))) + ' ';
}
else {
return 'Q ' + (cx + (d.linknum * cd * Math.cos(c_angle))) + ',' + (cy + (d.linknum * cd * Math.sin(c_angle))) + ' ';
}
}
}