D3.js: How to combine 2 datasets in order to create a map and show values on.mouseover? - json

I would like to combine two datasets on a map in D3.js.
For example:
1st dataset: spatial data in .json.
2nd dataset: Data to the areas in .csv
--> When you hover on the map a tooltip should show a sentences with some data from the 2nd dataset.
I am able to make the map and show a tooltip with data within the .json-file, but how do I insert the 2nd dataset?
A new function within my function that creates the map?
Do I have to take a completely new way?
Should I merge the .json-file with my 2nd dataset before using d3.js?
I appreciate any thoughts! :)

So, I think what you're asking is how to take spatial data from json and join it with some csv data that is loaded separately?
I did something similar with a choropleth map I was drawing and basically I just created a map of topology element ids to data objects and then I did a lookup using the topology element id to get whatever I wanted to associate with the actual drawn map element (I was using this method to set the color for the choropleth based on the fips country code).
So basically, draw the map so that you have an id associated with each map element that you want to be able to hover over. Then, in your mouseover/mouseout handlers, you will use that id to lookup the data you want to show in the tooltip and either use the svg title element or tipsy or manually draw an svg text element or whatever to show the tooltip.
Here's a couple useful references for drawing tooltips:
https://gist.github.com/biovisualize/1016860
http://jsfiddle.net/reblace/6FkBd/2/
From the fiddle:
function mouseover(d) {
d3.select(this).append("text")
.attr("class", "hover")
.attr('transform', function(d){
return 'translate(5, -10)';
})
.text(d.name + ": " + d.id);
}
// Toggle children on click.
function mouseout(d) {
d3.select(this).select("text.hover").remove();
}
Basically, it's appending an SVG text element and offsetting it from the position of the element being hovered over.
And here's a sample of how I look up data in an external map:
// Update the bound data
data.svg.selectAll("g.map path").transition().duration(750)
.style("fill", function(d) {
// Get the feature data from the mapData using the feature code
var val = mapData[d.properties.code];
// Return the colorScale value for the state's value
return (val !== undefined)? data.settings.colorScale(val) : undefined;
});
If your data is static, you can join it into your topojson file (if that's what you're using). https://github.com/mbostock/topojson/wiki/Command-Line-Reference
The client could change my data, so I kept it separate and redrew the map each time the data changed so that the colors would update. Since my data was topojson, I could access the feature id from the map data using d.properties.code (because I had joined the codes into the topojson file using the topojson tool I reference above... but you could use whatever unique id is in the spatial data file you have).

Related

Inserting tags by clicking on the objet

I have a question about the coordinates of the model.
Would it be possible to register a problem with a tag in the model, defining the exact location by clicking on the model?
enter image description here
PS.: on this example, only it is possible to use central coordinates of the object
To get the exact x,y,z value, use this 'ALT-key pivot point' technique, to get the surface point of a model, instead of it's centroid:
https://github.com/wallabyway/markupExt/issues/2
Second part:
Once you have the x,y,z value, you can replace 'centroid' position calulation, in this post:
https://forge.autodesk.com/blog/placing-custom-markup-dbid
// get the center of the dbId (based on its fragIds bounding boxes)
const pos = this.viewer.worldToClient(this.getModifiedWorldBoundingBox(id).center());
Does that help?

3ds Max TransNode not recognized as object

We're having some problems accessing the parent object (the TransNode) in our Forge viewer application. The TransNode is the object/node which contains all the materials used in the object.
Our research found that TransNodes are not recognized as objects by the Forge Viewer. We can still access its materials (Mat0, Mat1, Mat3 etc), but not the parent (the TransNode).
Would it be possible to define the TransNodes as objects in the API or is there any workaround we could use? Thanks a lot.
If in a 3ds Max scene, an object has applied a Multi-Material, upon translation, for each channel there will be created a subcomponent.
This becomes handy when you want to create selectable parts of a mesh, without breaking apart the mesh.
However, if you want to avoid this subcomponent selection and select the entire object upon subcomponent selection, there are 2 approaches:
At 3ds Max level, before translating the scene, replace the Multi-Material with a Basic/Single Material. To keep the texture/colour, just bake the MultiMat into a bitmap and use it as diffuse colour in the Basic Material.
At Forge Viewer level, you could tailor selection using algorithm like:
if I am selecting a child, select automatically the parent.
This is done by subscribing to selection event and at minimum, the code could look like this:
let viewer = viewerApp.getCurrentViewer();
let tree = viewer.model.getData().instanceTree;
viewer.addEventListener(Autodesk.Viewing.AGGREGATE_SELECTION_CHANGED_EVENT,
(event) => {
if (event.selections[] != undefined) {
let selectedNode = event.selections[0].dbIdArray[0];
console.log("selected a node with id = ", selectedNode);
if (tree.nodeAccess.getNumChildren(selectedNode) == 0) {
let parentId = tree.nodeAccess.getParentId(selectedNode);
console.log("parent of the selected node is ", parentId)
viewer.select(parentId);
}
}
})
Thus, any selection of a "leaf", will automatically select the parent of that "leaf" along with all that is under the parent node.
The second approach is derived from one of our blogpost discussing the idea of Controlling components selection in the Viewer.

Hover on <p> element to highlight geographic boundary, D3 Choropleth

I made a choropleth map of Chicago's 77 neighborhoods in D3.
The only challenge is, it's hard to know which neighborhood is which.
So, what I did was make divs with p elements (containing the neighborhood names) in the body of my HTML file and positioned them into a blank spot in my svg/canvas.
See a visual here.
What I'm trying to do is make it so when you hover over the name, the geographic boundary of the neighborhood highlights. Somehow I need to relate the geography to the text, but I have no idea how.
For a more robust solution, you would ideally want to add an id field to your data. This assumes that your data is some format (such as JSON). You may already have a unique identifier that you can use instead, but if not the following should work.
var i = 0;
for (var x in dataSet)
dataSet[x].id = ++i; //Or i++ for zero-based indexing
Now it depends on how you are generating the svg elements, but ideally you are using the enter function of d3. In that case, just create the result of enter as a variable and use it to append both the path (map portion) as well as the text.
var dataSelect = svg.selectAll(".item").data(data.items);
var dataEnter = dataSelect.enter();
dataEnter.append("path").....
dataEnter.append("text").....text(function(d){return d.label;}) //Using text because this is drawn inside the SVG.
With using the data and enter functions, the created objects automatically have the id data bound to them.
This makes it a simple case of text.id == path.id in your mouseover function.
svg.selectAll(".itemText").on("mouseover", function(textItem){
svg.selectAll(".item").each(function (cityItem){
if (cityItem.id == textItem.id)
d3.select(this).style("fill", "green");
else
d3.select(this).style("fill", function(d){return d.color;});
})
});
I've done this in a fiddle which you can see here
Note that this does not use p elements because ideally, if you're using SVG then you probably should use text elements. If you have to use p elements, then you can still use this general technique, but instead using p.text() as the matching factor on mouseover instead of id, assuming that the name is bound to your path data somewhere.

Why it does not showing the year value .in D3plus scatter?

I am using D3plus for data visualization . but in the x axis wrong data is showing instead of what i wrote in .x("year") to show .
http://jsfiddle.net/MituVinci/a77kz0dr/
enter code here
var visualization = d3plus.viz()
.container("#viz") // container DIV to hold the visualization
.data(sample_data) // data to use with the visualization
.type("scatter") // visualization type
.id("Reason") // key for which our data is unique on
.x("year") // key for x-axis
.y("Female") // key for y-axis
.draw()
I also want to resize the width and height of this and also want to show it using an external json file how can i do it ?
Since you have "Reason" as your .id() variable, D3plus is aggregating all data points that have the same "Reason". So the "x" position for "Family Feud" is 2010+2011+2012+2013+2014 or 10060, which is where all of your bubbles are located.
If you want to display each bubble individually, you could create a separate variable called "ReasonYear", concat the text of the Reason and Year fields together and then use .id("ReasonYear") for your visualization.
Use .width() and .height() to control the width and height respectively of your visualization.
Use .data() to load data from an external JSON file
Documentation can be found here: https://github.com/alexandersimoes/d3plus/wiki/Visualizations

FusionTablesLayer - Get Location column on click

I have a Google Maps with a FusionTablesLayer showing railways in Germany (the fusion table can be found here). The geometry column contains the LineString coordinates of each feature.
var railwaysLayer = new google.maps.FusionTablesLayer({
map: map,
query: {
select: 'geometry',
from: '1cVaJLbbxgBErP8PzYkHP-lpWMN8wUmINbdAJS1Y'
}
});
When a user clicks on a railway, i need to know which row (or feature actually) got clicked. This can be done in the click event handler like this:
google.maps.event.addListener(existingRailwaysLayer, 'click', function(e) {
console.log(e.row.geometry.value);
});
This works as expected, but here's the problem: This table got uploaded one month ago from a KML file as a test. I've imported the exact same KML file on a different account today (here), and the geometry column isn't being returned anymore in the event handler.
I checked all the settings and found out that in both tables, the geometry columns are of type Location. However: in the first table, the rows are NOT geocoded. File > Geocode > Begin geocoding effectively starts the process of geocoding each row, while in the second table it says: 'Column is fully geocoded'.
Since everything else is exactly the same, this could very well be the problem. I don't know how to 'un-geocode' the rows in the second table. Since the first one got uploaded a month ago, my guess is that Google now automatically geocodes the rows.
Is there any way to get the geometry column of the feature that got clicked?
Note: It's definitely possible, even when the geometrycolumn is geocoded. In the 'Map of geometry' tab of the second table, you can select the columns that are shown in the infoWindow when a feature is clicked. See here for a working demo.
rows with KML-data will not be included in rows (I'm not sure if this always has been).
I guess the KML-features have been ommitted to save bandwidth
What you can do:
Parse e.infoWindowHtml (as you have noticed it's possible to print the geometry-feature in the InfoWindow, e.infoWindowHtml contains the string used as content for the InfoWindow).
But the automatic InfoWindow-template only shows the coordinates, use a dynamic template, e.g.
{template .contents}
<div class='googft-info-window'>
<b>description:</b> {$data.formatted.description}<br>
<b>name:</b> {$data.formatted.name}<br>
<div style="width:0;height:0;overflow:hidden;">[[{$data.value.geometry}]]</div>
</div>
{/template}
(You may use this template with your table, I've created it, it has the templateId:6)
As you can see the geometry is embedded in a div with no size, yit will not be shown inside the InfoWindow.
The geometry is enclosed by square brackets, use RegExp to find it:
google.maps.event.addListener(railwaysLayer, 'click', function(e) {
var str = e.infoWindowHtml, geo;
if(geo=str.match(/\[\[([^\]]+)]]/)){
geo=geo[1].replace(/(>|<)/g,function(x){
return((x==='>')?'>':'<');
});
console.log(geo);
}
});
Demo:(using your table with my template) http://jsfiddle.net/doktormolle/9YdKW/