Plot part of the map underneath a surface - javascript

I would like to visualize my geographic data using plotly (javascript). I have plotted a surface and some datapoints above it as follows:
Here is my code to do it:
var x_completed = [];
for (var i=0;i < column_count;i++) {
x_completed.push(full_data_list[i].lng);
}
var y_completed = [];
for (var i=0;i < row_count;i++) {
y_completed.push(full_data_list[i*column_count].lat);
}
var z_completed = [];
for (var i = 0; i < row_count; i++) {
row = [];
for (var j = 0; j < column_count; j++) {
row.push(full_data_list[i*column_count+j].p_val);
}
z_completed.push(row);
}
x_org = [];
y_org = [];
z_org = [];
for (var i = org_data.length - 1; i >= 0; i--) {
dp = org_data[i];
x_org.push(dp.lng);
y_org.push(dp.lat);
z_org.push(dp.p_val);
}
var trace = {
x: x_org,
y: y_org,
z: z_org,
mode: 'markers',
name: 'Original Measurement',
marker: {
size: 5,
line: {
color: 'rgba(217, 217, 217, 0.14)',
width: 0.5},
opacity: 0.8},
type: 'scatter3d'
};
var data_z = {x: x_completed, y: y_completed, z: z_completed, type: 'surface', name: 'Inferred Data using Matrix Completition'};
var layout = {
scene: {
xaxis:{title: 'Latitude'},
yaxis:{title: 'Longitude'},
zaxis:{title: 'CO2'},
},
autosize: true,
margin: {
l: 0,
r: 0,
b: 50,
t: 50,
pad: 4
},
}
Plotly.newPlot('map', [data_z,trace],layout);
Now my problem is that I want to draw the corresponding geographic area underneath the surface so everyone can easily know where the data comes from. I have tried googling but I couldn't figure out how to include an 2D image (or part of the map) to a 3D environment. I have had a look at scattermapbox but it only allows 2D map. Could you please suggest me?
Many thanks.

Related

Stacked bar chart with (computed) average line in Plotly.js

I've got a (stacked) bar chart and I want an average line plotted on my chart.
Let's take this example:
var trace1 = {
x: ['giraffes', 'orangutans', 'monkeys'],
y: [20, 14, 23],
name: 'SF Zoo',
type: 'bar'
};
var trace2 = {
x: ['giraffes', 'orangutans', 'monkeys'],
y: [12, 18, 29],
name: 'LA Zoo',
type: 'bar'
};
var data = [trace1, trace2];
var layout = {barmode: 'stack'};
Plotly.newPlot('myDiv', data, layout, {showSendToCloud:true});
Result:
Expected output:
I've found a similar question, but in that case it was pretty easy to add a line with a 'fixed' value. In this case I've got a stacked bar chart nicolaskruchten/pivottable, so the user can easily drag and drop columns. That makes computing the average harder.
I can loop through all results and compute the average value, but since Plotly is very powerful and has something like aggregate functions, I feel like there should be a better way.
How can I add a (computed) average line to my (stacked) bar chart?
Plotly.js not provided any direct options for drawing average line.
But you can do this simple way.
//Find average value for Y
function getAverageY() {
allYValues = trace1.y.map(function (num, idx) {
return num + trace2.y[idx];
});
if (allYValues.length) {
sum = allYValues.reduce(function (a, b) {
return a + b;
});
avg = sum / allYValues.length;
}
return avg;
}
//Create average line in shape
var layout = {
barmode: 'stack',
shapes: [{
type: 'line',
xref: 'paper',
x0: 0,
y0: getAverageY(),
x1: 1,
y1: getAverageY(),
line: {
color: 'green',
width: 2,
dash: 'dot'
}
}]
};
Updated:
You need to update your graph after loading this drawing a average
line for any numbers of trace.
//Check graph is loaded
if (document.getElementById('myDiv')) {
//draw average line
drawAvgLine(document.getElementById('myDiv'))
}
function drawAvgLine(graph) {
var graphData = graph.data; //Loaded traces
//making new layout
var newLayout = {
barmode: 'stack',
shapes: [{
type: 'line',
xref: 'paper',
x0: 0,
y0: getAverageY(graphData),
x1: 1,
y1: getAverageY(graphData),
line: {
color: 'green',
width: 2,
dash: 'dot'
}
}]
};
//Update plot pass existing data
Plotly.update('myDiv', graphData, newLayout)
}
//Calculate avg value
function getAverageY(graphData) {
var total = [],
undefined;
for (var i = 0, n = graphData.length; i < n; i++) {
var arg = graphData[i].y
for (var j = 0, n1 = arg.length; j < n1; j++) {
total[j] = (total[j] == undefined ? 0 : total[j]) + arg[j];
}
}
return total.reduce(function (a, b) {
return a + b;
}) / total.length;
}

By modifying arr[1][0] multiple cells are modified

I'm trying to make a match-3 game (candy crush like). I have an object level which has tiles property which is a 2d array. After I do some manipulations I want to change the type of a specific element to -1 using this simple line (I'll be using for, but for now I've made it simple for demonstrative purposes)
level.tiles[1][0].type = -1;
Here is the code
var level = {
x: 250, // X position
y: 113, // Y position
columns: 8, // Number of tile columns
rows: 8, // Number of tile rows
tilewidth: 40, // Visual width of a tile
tileheight: 40, // Visual height of a tile
tiles: [], // The two-dimensional tile array
selectedtile: {selected: false, column: 0, row: 0}
};
var tileTypes = [
{
type: "red",
colors: [255, 128, 128]
},
{
type: "green",
colors: [128, 255, 128]
},
{
type: "darkBlue",
colors: [128, 128, 255]
},
{
type: "yellow",
colors: [255, 255, 128]
}
];
function createLevel() {
for (var i = 0; i < level.columns; i++) {
level.tiles[i] = [];
}
for (var i = 0; i < level.columns; i++) {
for (var j = 0; j < level.rows; j++) {
level.tiles[i][j] = getRandomTile();
}
}
}
function getRandomTile() {
return tileTypes[Math.floor(Math.random() * tileTypes.length)];
}
createLevel();
level.tiles[1][0].type = -1;
Unfortunately not only tiles[1][0] is modified, but multiple cells. The interesting part is that every time random cells are affected
This occurs because getRandomTile() returns a reference to a tile type, not a copy of it.
I.e. to simplify this case:
var a = {x: 1};
var b = [a, a, a, a];
b[0].x = 2;
console.log(a, b);
will output
{x: 2} [{x: 2}, {x: 2}, {x: 2}, {x: 2}]
If you want the tiles to be modifiable, have getRandomTile return a copy – a shallow copy in this case, so colors is still a reference, not a copy – of the randomly chosen tile type.
function getRandomTile() {
const tileType = tileTypes[Math.floor(Math.random() * tileTypes.length)];
// Idiom for shallow copy, i.e. assign all properties of tileType
// into a new, unique object.
return Object.assign({}, tileType);
}
The problem is you modify the type object, instead of linking to another type. A solution would be to clone it when creating the tiles:
function getRandomTile() {
var srcType = tileTypes[Math.floor(Math.random() * tileTypes.length)];
return {type:srcType.type, colors:srcType.color};
}
Another one (depending on your goal) would be to have Tile objects, each one having a reference to a Type object (not just an integer). At this point some classes might be helpful:
class TileType {
constructor(colors){
this.colors = colors;
}
}
let tileTypes = [...]
class Tile {
constructor(){
this.type = tileTypes[Math.random()*tileTypes.length|0];
}
setNewType(type){
this.type = type;
}
}
etc.
This is caused by getRandomTile which returns the same reference of the object defined in tileTypes if the index passed in is the same. You can print tileTypes to help your understand what happens.

D3 GeoJson Choropleth and different data files loading depending on onClick Button

I want to create a choropleth showing data of production of different animals. Depending on an onClick-Event Button I want to load different files of data to create the colorpleth. I have 4 different JSON-files for sheep, pig, chick and cows. First I'm loading the map data in europe.json and then the data from chick.json/cow.json/sheep.json/pig.json. In the different for-loops I'm summing up numbers to get the total for all years per country.
I tried different approaches to create 4 buttons and depending on the clicks to load one of the 4 JSON data files and read all stackoverflow questions with similar questions but my project is a bit different because of the choropleth.
the JSONs with data look like this:
[{
"2005": 0,
"2006": 0,
"2007": 0,
"2008": 0,
"2009": 0,
"2010": 0,
"2011": 0,
"2012": 0,
"2013": 0,
"2014": 0,
"2015": 3.26,
"2016": 2.27,
"geo": "AL",
"fleischart": "sheep"
},
{
"2005": 7.15,
"2006": 0,
"2007": 0,
"2008": 8.22,
"2009": 7.87,
"2010": 8.03,
"2011": 8.35,
"2012": 8.37,
"2013": 8.45,
"2014": 7.98,
"2015": 7.77,
"2016": 7.3,
"geo": "AT",
"fleischart": "sheep"
}, { ....
different objects for different countries.
and my code looks like this:
<div id="container">
</div>
<script type="text/javascript">
var w = 800;
var h = 600;
var projection = d3.geo.mercator()
.center([ 13, 52 ])
.translate([ w/2, h/2 ])
.scale([ w/1.5 ]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#container")
.append("svg")
.attr("width", w)
.attr("height", h);
d3.json("europe.json", function (json) {
console.log(json);
d3.json("chick.json", function (error, data) {
if (error) {
console.log(error);
} else {
console.log(data);
dataset = data;
}
var mengeprojahrproland = new Array();
var mengedurchschnittproland = new Array;
for (var n = 0; n < 36; n++) {
var landName = Object(data[n])["geo"];
var total = 0;
mengeprojahrproland.push(Object.values(data[n]));
for (var q = 0; q < 12; q++) {
total += Object(mengeprojahrproland[n])[q];
}
mengedurchschnittproland.push(parseFloat(total).toFixed(2));
var landmengeobjekt = [{Land: 0, Anzahl: 0}];
var laender = [];
var laendermenge = [];
for (var m = 0; m < 36; m++) {
var landName = Object(data[m])["geo"];
var durchschnittmengeprojahr = mengedurchschnittproland[m];
laender.push(landName);
laendermenge.push(durchschnittmengeprojahr);
landmengeobjekt.push({Land: landName, Anzahl: durchschnittmengeprojahr});
}
}
for (var a = 0; a < laender.length; a++) {
var dataState = laender[a];
var dataValue = parseFloat(laendermenge[a]);
var color = d3.scale.threshold()
.domain([
1, 2,
5, 10, 25, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 6000, 7000, 10000, 11000, 15000, 17500, 18000, 19000, 22000
])
.range([
"#2438FF", "#2A36F8", "#3035F1", "#3633EA", "#3C32E3", "#4230DC", "#482FD5", "#4E2ECE", "#542CC7", "#5A2BC0", "#6029B9", "#6628B2", "#6D27AB", "#7325A4", "#79249D", "#7F2296", "#85218F", "#8B1F88", "#911E82", "#971D7B", "#9D1B74", "#A31A6D", "#A91866", "#AF175F", "#B61658", "#BC1451", "#C2134A", "#C81143", "#CE103C", "#D40E35", "#DA0D2E", "#E00C27", "#E60A20", "#EC0919", "#F20712", "#F8060B", "#FF0505"
]);
for (var j = 0; j < json.features.length; j++) {
var jsonState = json.features[j].properties.wb_a2;
if (jsonState == dataState) {
json.features[j].properties.value = dataValue;
break;
}
}
}
svg.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.style("fill", function (d) {
var value = d.properties.value;
if (value) {
return color(value);
} else {
return "darkgray";
}
});
});
});
this is the current choropleth for the chick.json data: insahne.com/colorpleth/indexchick.html
how can I achieve to load different files depending on user's click?

get real 2D vertex coordinates of a div after CSS 3D transformations with Javascript

I've been trying to figure this out for a couple of days now but I can't seem to get it right.
Basically, I have some divs whose parent has a CSS perspective and rotateX 3D transformations applied and I need to get the actual on-screen coordinates of those divs.
Here's a jsfiddle with an example of what I mean (albeit not working properly).
https://jsfiddle.net/6ev6d06z/3/
As you can see, the vertexes are off (thanks to the transformations of its parents)
I've tried using
getBoundingClientRect()
but that doesn't seem to be taking the 3D transforms into consideration.
I don't know if there's an already established method to get what I need but otherwise I guess there must be a way of calculating the coordinates using the matrix3D.
Any help is appreciated.
As it is there is not a builtin way to get the actual 2d coordinates of each vertex of the transformed element. In the case of all the APIs (such as getBoundingClientRect), they return a bounding rectangle of the transformed element represented as a 2point rectangle [(top,left), (bottom,right)].
That being said, you can absolutely get the actual coordinates with a little bit of effort and matrix math. The easiest thing to do would be to use a premade matrix library to do the math (I've head good things about math.js but have not used it), although it is certainly doable yourself.
In pseudo-code for what you will need to do:
Get the untransformed bounds of the transformed parent element in the document coordinate system.
Get the untransformed bounds of the target element in the document coordinate system.
Compute the target's untransformed bounds relative to the parent's untransformed bounds.
a. Subtract the top/left offset of (1) from the bounds of (2).
Get the css transform of the parent element.
Get the transform-origin of the parent element (defaults to (50%, 50%)).
Get the actual applied transform (-origin * css transform * origin)
Multiply the four vertices from (3) by the computed transform from (6).
Perform the homogeneous divide (divide x, y, z by the w component) to apply perspective.
Transform the projected vertices back into the document coordinate system.
Fun!
And then for fun in real code: https://jsfiddle.net/cLnmgvb3/1/
$(".target").on('click', function(){
$(".vertex").remove();
// Note: The 'parentOrigin' and 'rect' are computed relative to their offsetParent rather than in doc
// coordinates. You would need to change how these offsets are computed to make this work in a
// more complicated page. In particular, if txParent becomes the offsetParent of 'this', then the
// origin will be wrong.
// (1) Get the untransformed bounds of the parent element. Here we only care about the relative offset
// of the parent element to its offsetParent rather than it's full bounding box. This is the origin
// that the target elements are relative to.
var txParent = document.getElementById('transformed');
var parentOrigin = [ txParent.offsetLeft, txParent.offsetTop, 0, 0 ];
console.log('Parent Origin: ', parentOrigin);
// (2) Get the untransformed bounding box of the target elements. This will be the box that is transformed.
var rect = { left: this.offsetLeft, top: this.offsetTop, right: this.offsetLeft + this.offsetWidth, bottom: this.offsetTop + this.offsetHeight };
// Create the vertices in the coordinate system of their offsetParent - in this case <body>.
var vertices =
[
[ rect.left, rect.top, 0, 1 ],
[ rect.right, rect.bottom, 0, 1 ],
[ rect.right, rect.top, 0, 1 ],
[ rect.left, rect.bottom, 0, 1 ]
];
console.log('Original: ', vertices);
// (3) Transform the vertices to be relative to transformed parent (the element with
// the CSS transform on it).
var relVertices = [ [], [], [], [] ];
for (var i = 0; i < 4; ++i)
{
relVertices[i][0] = vertices[i][0] - parentOrigin[0];
relVertices[i][1] = vertices[i][1] - parentOrigin[1];
relVertices[i][2] = vertices[i][2];
relVertices[i][3] = vertices[i][3];
}
// (4) Get the CSS transform from the transformed parent
var tx = getTransform(txParent);
console.log('Transform: ', tx);
// (5) Get the CSS transform origin from the transformed parent - default is '50% 50%'
var txOrigin = getTransformOrigin(txParent);
console.log('Transform Origin: ', txOrigin);
// (6) Compute the full transform that is applied to the transformed parent (-origin * tx * origin)
var fullTx = computeTransformMatrix(tx, txOrigin);
console.log('Full Transform: ', fullTx);
// (7) Transform the vertices from the target element's bounding box by the full transform
var txVertices = [ ];
for (var i = 0; i < 4; ++i)
{
txVertices[i] = transformVertex(fullTx, relVertices[i]);
}
console.log('Transformed: ', txVertices);
// (8) Perform the homogeneous divide to apply perspective to the points (divide x,y,z by the w component).
var projectedVertices = [ ];
for (var i = 0; i < 4; ++i)
{
projectedVertices[i] = projectVertex(txVertices[i]);
}
console.log('Projected: ', projectedVertices);
// (9) After the transformed vertices have been computed, transform them back into the coordinate
// system of the offsetParent.
var finalVertices = [ [], [], [], [] ];
for (var i = 0; i < 4; ++i)
{
finalVertices[i][0] = projectedVertices[i][0] + parentOrigin[0];
finalVertices[i][1] = projectedVertices[i][1] + parentOrigin[1];
finalVertices[i][2] = projectedVertices[i][2];
finalVertices[i][3] = projectedVertices[i][3];
}
// (10) And then add the vertex elements in the 'offsetParent' coordinate system (in this case again
// it is <body>).
for (var i = 0; i < 4; ++i)
{
$("<div></div>").addClass("vertex")
.css('position', 'absolute')
.css('left', finalVertices[i][0])
.css('top', finalVertices[i][1])
.appendTo('body');
}
});
function printMatrix(mat)
{
var str = '';
for (var i = 0; i < 4; ++i)
{
for (var j = 0; j < 4; ++j)
{
str += (' ' + mat[i][j]);
}
str += '\r\n';
}
console.log(str);
}
function getTransform(ele)
{
var st = window.getComputedStyle(ele, null);
var tr = st.getPropertyValue("-webkit-transform") ||
st.getPropertyValue("-moz-transform") ||
st.getPropertyValue("-ms-transform") ||
st.getPropertyValue("-o-transform") ||
st.getPropertyValue("transform");
var values = tr.split('(')[1],
values = values.split(')')[0],
values = values.split(',');
var mat = [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1] ];
if (values.length === 16)
{
for (var i = 0; i < 4; ++i)
{
for (var j = 0; j < 4; ++j)
{
mat[j][i] = +values[i * 4 + j];
}
}
}
else
{
for (var i = 0; i < 3; ++i)
{
for (var j = 0; j < 2; ++j)
{
mat[j][i] = +values[i * 2 + j];
}
}
}
return mat;
}
function getTransformOrigin(ele)
{
var st = window.getComputedStyle(ele, null);
var tr = st.getPropertyValue("-webkit-transform-origin") ||
st.getPropertyValue("-moz-transform-origin") ||
st.getPropertyValue("-ms-transform-origin") ||
st.getPropertyValue("-o-transform-origin") ||
st.getPropertyValue("transform-origin");
var values = tr.split(' ');
var out = [ 0, 0, 0, 1 ];
for (var i = 0; i < values.length; ++i)
{
out[i] = parseInt(values[i]);
}
return out;
}
function createTranslateMatrix(x, y, z)
{
var out =
[
[1, 0, 0, x],
[0, 1, 0, y],
[0, 0, 1, z],
[0, 0, 0, 1]
];
return out;
}
function multiply(pre, post)
{
var out = [ [], [], [], [] ];
for (var i = 0; i < 4; ++i)
{
for (var j = 0; j < 4; ++j)
{
var sum = 0;
for (var k = 0; k < 4; ++k)
{
sum += (pre[k][i] * post[j][k]);
}
out[j][i] = sum;
}
}
return out;
}
function computeTransformMatrix(tx, origin)
{
var out;
var preMul = createTranslateMatrix(-origin[0], -origin[1], -origin[2]);
var postMul = createTranslateMatrix(origin[0], origin[1], origin[2]);
var temp1 = multiply(preMul, tx);
out = multiply(temp1, postMul);
return out;
}
function transformVertex(mat, vert)
{
var out = [ ];
for (var i = 0; i < 4; ++i)
{
var sum = 0;
for (var j = 0; j < 4; ++j)
{
sum += +mat[i][j] * vert[j];
}
out[i] = sum;
}
return out;
}
function projectVertex(vert)
{
var out = [ ];
for (var i = 0; i < 4; ++i)
{
out[i] = vert[i] / vert[3];
}
return out;
}
Note: The accepted answer is not cross browser compatible. This has to do with the stupidly diverse ways browsers calculate offset properties.
I changed the answer above to use
var rect=this.getBoundingClientRect()
and the results is more cross-browser compatible.
https://jsfiddle.net/2znLxda2/

Simple variable issue

I am trying to change my variables inside my items variable and not sure how to do it.
var items = [
{name: '#builder', x: 0, y:15},
{name: '#image', x: 0, y:15},
{name: '#button', x: -100, y: -55}
];
//I only want to change builder and image but not button...How to do this?
if(i>5){
items = [
{name: '#builder', x: 50, y:105},
{name: '#image', x: 110, y:115}
];
}
Thanks for the help.
Have you considered using an object hash table instead?
var items = {
'#builder' : { x: 0, y:15},
'#image': { x: 0, y:15},
'#button': {x: -100, y: -55}
};
Then you can change them like so:
items['#builder'].x = 50;
If it's not an option, that's cool, but it seems like if you want to look things up by name, this might be a better route for you.
Outside of that, you'd have to loop through each record to find the name you wanted and set the values appropriately.
var items = [
{name: '#builder', x: 0, y:15},
{name: '#image', x: 0, y:15},
{name: '#button', x: -100, y: -55}
];
function updateValues(name, x, y) {
for(var i = 0; i < items.length; i++) {
var item = items[i];
if(item.name == name) {
item.x = x;
item.y = y;
return;
}
}
}
updateValues('#builder', 50, 105);
If you know the position, then you could do:
if (i > 5) {
items[0].x = 50;
items[0].y = 105;
items[1].x = 110;
items[1].y = 115;
}

Categories