Changing JSON files in d3js force visualizaition - json

Ok I have found a couple of resources that have tried to load two datasources using d3js. Unfortunately they don't seem to solve the problem I having.
Essentially what I am trying to do is create a force layout with nodes and links and have the ability to change datasources via a selection from a drop down menu.
Here are the basics...I retrieve all nodes and links from and external file using d3.json. Everything works perfectly and I have even animated the visualization to cycle through the different dates for each node. My problem is, now I am trying to expand on the functionality of this visualization...I want to be able to select a .json file from a dropdown menu and then reload the visualization with the new data. Basically providing the user to view several different visualizations of completely different data.
All the concepts appear to be working except for when I redraw the visualization using the new data file, some nodes appear and float off on their own not linking to the the original node. I forgot the first 7 nodes I have hard coded their x, y coordinates to be in the middle of the visualization and every node after that uses the force functionality to place themselves depending on what they are linked to.
Here is some code
$("#customer").change(function(){ // this is the dropdown menu
$("#customer option:selected").each(function () {
var line = d3.selectAll("line").remove(); // remove all links on screen
var g = d3.selectAll("g").remove(); // remove all nodes on screen
dataFile = this.innerHTML; // changes variable used in d3.json
change(); // calls function wrapper that contains d3.json
});
function change(){
d3.json(dataFile, function(fileData){
rtm = new Object(); // initialize dictionary object
dates = []; // initialize dates[] array to contain all dates in data
var node = {}; // node object
// traverse through data contained in file
for(var i = 0; i < fileData.nodes.length; i++){
node = fileData.nodes[i]; // assign current node to node object
node.date = new Date(node.date);
rtm[node.id] = node; // add node object to dictionary object
// check if date exists in dateArray
if(findDate(node.date, dates)){
dates.push(node.date); // add dates that don't exist
}
}
getNodes(fileData.nodes[0].date); // finds the nodes to be visualized and updates the array passed to force.nodes()
getLinks(fileData.nodes[0].date); // finds the nodes to be visualized and updates the array passed to force.links()
start(); // initiates visualization after d3.json() finishes
});
}
// function controls visualization. Uses d3.js framework to control visualization
// must be called from within d3.json for first initialization or nothing will render
function start(){
// stop force functionality to allow for removal and adding of new objects
force.stop();
// remove all elements inside visualization
var line = d3.selectAll("line").remove(); // remove all links on screen
var g = d3.selectAll("g").remove(); // remove all nodes on screen
// create any links needed, dependent on links contained links[] array
// selects links already created or adds new links.
link = svg.selectAll("line.link")
.data(force.links(), function(d){ return d.id;}) // 2nd parameter can be added to use as a comaparison for pairing existing links with identical same data
link.enter().append("line") // new data will be bound to a new line. 2nd parameter in .data() allows for element reuse
// instead of creation, otherwise data and line elements are matched on index. If those
// two don't match then a new element is created.
.attr("class", "link") // assigns .link class to link element to allow for css styling
//.style("stroke-opacity", .5)
.style("stroke-width", 8)
.style("stroke", function(d){
return d.target.id > 7 ? (d.target.fraud ? "Red" : "#35586C") : "Grey";
});
link.exit().remove(); // removes any links removed from links[] array
// create any nodes needed, dependent on data contained in .data()
// selects nodes already created or adds new nodes. matches nodes to data
node = svg.selectAll(".node")
.data(force.nodes(), function(d){ return d.id;}) // 2nd parameter can be added to use as a comaparison for pairing existing node with identical same data
node.enter().append("g") // new data will be bound to a new node. 2nd parameter in .data() allows for element reuse
// instead of creation, otherwise data and node elements are matched on index. If those
// two don't match, a new element is created.
.attr("class", "node") // assigns .node class to node element to allow for css styling
.call(force.drag); // allows for nodes to be dragged and moved. x,y coordinates will adjust to movement
//.transition().duration(10000).delay(10000); // transitions any new nodes into the visualization
node.exit().remove(); // removes any nodes removed from nodes[] array
node.append("image")
.attr("xlink:href", function(d){
// add path for image of each node
var path = "images/";
if(!d.fraud){ // check if fraudulent node
if(d.name == "Record"){
path += "record-G.gif";
return path;
}
if(d.id <= 7){
path += "Record/";
}else {
path += "Linked/";
}
if(d.name == "SSN"){
path += "SSN.png";
}else if(d.name == "Name"){
path += "Name.png";
}else if(d.name == "Address"){
path += "Address.png";
}else if(d.name == "Phone"){
path += "Phone.png";
}else if(d.name == "DOB"){
path += "DOB.png";
}else if(d.name == "Email"){
path += "Email.png";
}else if(d.name == "Record"){
path += "record-G.gif";
}else if(d.name == "App"){
path += "App.png";
}else {
path += "nan.gif";
}
}else {
path += "FraudApp.png"
}
return path;
})
.attr("class", "image")
.attr("x", function(d){ return d.name == "Record" ? -30 : -20})
.attr("y", function(d){ return d.name == "Record" ? -30 : -20})
.attr("height", function(d){ return d.name == "Record" ? 60 : 40})
.attr("width", function(d){ return d.name == "Record" ? 60 : 40});
force.start(); // restart force functionality
}
I have no idea if any of this makes sense. A quick synopsis: I want to provide functionality to change .json files and redraw(start over) the visualization with the data contained in the that .json file.

Related

Populate FeatureCollection with values from bands of each individual image in an image collection in Google Earth Engine

In Google Earth Engine, I have loaded in a Featurecollection as a JSON which contains a few polygons. I would like to add columns to this FeatureCollection which gives me the mean values of two bands for each polygon and from each of the multiple images contained within the Image Collection.
Here is the code I have so far.
//Polygons
var polygons = ee.FeatureCollection('ft:1_z8-9NMZnJie34pXG6l-3StxlcwSKSTJFfVbrdBA');
Map.addLayer(polygons);
//Date of interest
var start = ee.Date('2008-01-01');
var finish = ee.Date('2010-12-31');
//IMPORT Landsat IMAGEs
var Landsat = ee.ImageCollection('LANDSAT/LT05/C01/T1') //Landsat images
.filterBounds(polygons)
.filterDate(start,finish)
.select('B4','B3');
//Add ImageCollection to Map
Map.addLayer(Landsat);
//Map the function over the collection and display the result
print(Landsat);
// Empty Collection to fill
var ft = ee.FeatureCollection(ee.List([]))
var fill = function(img, ini) {
// type cast
var inift = ee.FeatureCollection(ini)
// gets the values for the points in the current img
var mean = img.reduceRegions({
collection:polygons,
reducer: ee.Reducer.mean(),
});
// Print the first feature, to illustrate the result.
print(ee.Feature(mean.first()).select(img.bandNames()));
// writes the mean in each feature
var ft2 = polygons.map(function(f){return f.set("mean", mean)})
// merges the FeatureCollections
return inift.merge(ft2)
// gets the date of the img
var date = img.date().format()
// writes the date in each feature
var ft3 = polygons.map(function(f){return f.set("date", date)})
// merges the FeatureCollections
return inift.merge(ft3)
}
// Iterates over the ImageCollection
var newft = ee.FeatureCollection(Landsat.iterate(fill, ft))
// Export
Export.table.toDrive(newft,
"anyDescription",
"anyFolder",
"test")
In the console I get an error message
Element (Error)
Failed to decode JSON.
Error: Field 'value' of object '{"type":"ArgumentRef","value":null}' is missing or null.
Object: {"type":"ArgumentRef","value":null}.
In my csv file which is generated I get a new column called mean but this is populated with and no actual values.
There is no reason to use iterate() here. What you can do is a nested map(). Over polygons and then over images. You can flatten the resulting list of lists to turn it into a single list like this:
// compute mean band values by mapping over polygons and then over images
var results = polygons.map(function(f) {
return images.map(function(i) {
var mean = i.reduceRegion({
geometry: f.geometry(),
reducer: ee.Reducer.mean(),
});
return f.setMulti(mean).set({date: i.date()})
})
})
// flatten
results = results.flatten()
Script: https://code.earthengine.google.com/b65a731c78f78a6f9e08300dcf552dff
The same approach can be used with reduceRegions() as well, mapping over images and then over regions. However, you will have to map over the resulting features to set dates.
images.filterBounds(f) can be probably also added if your features cover a larger area.
PS: your table is not shared

ActionSctipt 3.0 Error:

I'm having real trouble trying to access the symbol dynamically, I have 9 buttons that all call this method, and they pass in their location (tl, t, tr, etc.) I've tried this method before on another program and it works without a problem, but in this program it fails.
I am attempting to access a symbol call s_tl (example location), but all I'm getting is undefined (see results).
function turn(btn : String):Function {
return function(e:MouseEvent) {
var players_turn : int;
var chosen : String = "s_" + btn;
trace(this);
trace(this[chosen]);
trace(chosen);
trace(this[chosen]);
// if crosses turn 0 else 1
if (s_c.currentFrame == 1) {
players_turn = 0;
} else {
players_turn = 1;
}
// check who's turn it is if it's been pressed before
if (players_turn == 0 && this[chosen].visible == false) {
this[chosen].gotoAndStop(1);
this[chosen].visible = true;
} else {
this[chosen].gotoAndStop(2);
this[chosen].visible = true;
}
};
}
Results:
[object global]
undefined
s_br
undefined
TypeError: Error #1010: A term is undefined and has no properties.
at MethodInfo-6()
Your problem is the bad code style. You define unnamed unbind function inside function turn() and that's where the root of your problem is. Unbind function exist, as your trace shows, in global addressing context and, unlike function turn(), is not bind to any specific display object. Your buttons probably exist on the same addressing context with turn(). Argument btn is available inside unnamed function because ECMA standard instructs so (if function A creates function B then local variables, including arguments, of A are available as local variables in B), but it is a very very very bad practice that makes code messy and induce headaches.
Please explain what you tried to achieve with that code so we could untangle it and rewrite in not-so-twisted way.
Okey, I basically figured you're doing Tic Tac Toe. Now, guideline. A cell must contain 3 frames: 1st frame for the button graphics, 2nd and 3rd for X and O. Name them your way: s_1, s_2, etc.
for (var i:int = 1; i < 10; i++)
{
var aCell:MovieClip = getChildByName("s_" + i) as MovieClip;
aCell.addEventListener(MouseEvent.CLICK, onTic);
}
function onTic(e:MouseEvent):void
{
var playersTurn:int = s_c.currentFrame;
var aCell:MovieClip = e.currentTarget as MovieClip;
trace(aCell.name);
// Now, the magic.
aCell.gotoAndStop(playersTurn + 1);
aCell.removeEventListener(MouseEvent.CLICK, onTic);
}

Update d3 graph from dynamic JSON (onStateChange)

I am working with the Vaadin framework (https://vaadin.com/home) integrating d3.js to my java code (analogous to http://www.rapidpm.org/2013/10/using-javascript-libraries-d3-in-vaadin.html). Basically, this Javascript code is bound to a Java class and reacts to a change of state in said Java class.
==================================================================================
So, when the state of the java diagram-class changes, this.conStateChange-method is called: javascript fetches a new String from the java class (var string = this.getState().string;), parses it to JSON and renders a graph.
A button click will trigger the state change by changing the String in the java class (!), thereby triggering the described method and rendering the new graph from the new JSON-parsed-String.
...THEORETICALLY... however, in reality, if I click that button once the graph disappears. Only if I click the button again, the new graph is rendered.
/*not so interesting code
var diagramElement = this.getElement();
var width = 960, height = 500;
var force = d3.layout.force().size([ width, height ])
.charge(-400)
.linkDistance(40)
.on("tick", tick);
var drag = force.drag()
.on("dragstart", dragstart);
var svg = d3.select(diagramElement)
.append("svg")
.attr("width", width)
.attr("height", height);
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");*/
/*interesting code*/
this.onStateChange = function() {
link.remove(); //delete old graph
node.remove(); //delete old graph
var string = this.getState().string;
graph = JSON.parse(string);
force.nodes(graph.nodes).links(graph.links).start();
link = link.data(graph.links)
.enter().append("line")
.attr("class", "link");
node = node.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 12)
.on("dblclick", dblclick)
.call(drag);
};
So whats wrong with this (indeed not very elegant) code, and how can I achieve that a single click will [1] remove the graph displayed and [2] render a new graph?
Thanks in advance...
I'm not sure if you have found a solution and I'm still a noob in d3 but I would like to suggest as this solution helped me. However, my force layout derived its data from a csv and not JSON not sure if it makes much difference.
I included " graph = { "nodes": [], "links": [] }; " in the "this.onStateChange = function() {" as this will help fill up the arrays with new data. Hope it helps!

Trouble Binding JSON subarrays in d3.js

I guess anyone who monitors the d3 questions is getting used to seeing me this week (this is my first time using d3) so here goes another question. I am fetching data via php/ajax but I have placed a sample as hard code below. I wish to bind the fineBinSummary data to a bar chart but can't seem to figure out how. When I inspect the html it makes it as far as setting up the svg and then stops when I start to try to bind data to it.
I successfully bound the detail data (code not shown, just the JSON) but can't get the fineBinSummary data to bind in the same manner. I also will need to extract the length of the fineBinSummary array (you can see that I've just made it the width / 5 but there won't always be 5 bars in the chart. Any assistance is much appreciated.
(document).ready(function() {
var data = [
{"Id":"93439440411",
"detail":[{"fineBin":"UNCLASSIFIED","x":"-142000.0","y":"-21000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-142000.0","y":"16000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-141000.0","y":"-15000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-141000.0","y":"24000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-141000.0","y":"27000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-140000.0","y":"-15000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"}],
"fineBinSummary":[{"fineBin":"UNCLASSIFIED","count":8212},{"fineBin":"SMALL_PARTICLE","count":104},{"fineBin":"RESIDUE","count":68},
{"fineBin":"FALSE","count":16},{"fineBin":"258","count":12}]},
{"Id":"93439440419",
"detail":[{"fineBin":"UNCLASSIFIED","x":"-142839.40900000001","y":"20448.394","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-142546.65599999999","y":"26731.720000000001","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-142499.136","y":"-24443.516","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-142267.68799999999","y":"32411.870999999999","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"},
{"fineBin":"UNCLASSIFIED","x":"-142000.0","y":"-33000.0","imageId":null,"serverId":null,"fileSpec":null,"color":"HotPink"}],
"fineBinSummary":[{"fineBin":"UNCLASSIFIED","count":8212},{"fineBin":"SMALL_PARTICLE","count":104},{"fineBin":"RESIDUE","count":68},
{"fineBin":"FALSE","count":16},{"fineBin":"258","count":12}]}]
//Constants
var squareSide = 400;
var height = squareSide / 2.0;
var barWidth = squareSide / 5;
//begin building bootstrap courousel stack
var slides = d3.select(".carousel-inner").selectAll('div')
.data(data)
.enter()
.append("div")
.attr("class","item")
.append("div")
.attr("class","container-fluid")
.append("div")
.attr("class","row");
//Make the first slide the active slide
d3.selectAll(".item").classed('active', function(d, i) { return i == 0; });
//Build the image side of the slide
var imageSide = slides.append("div")
.attr("class","col-lg-6 wafer-and-pareto");
//build final location for div that will hold the histogram
var paretoBox = imageSide.append("div")
.attr("class","row")
.append("div")
.attr("class","col-lg-12")
.append("div")
.attr("class","pareto-box")
.append("svg")
.attr("class","chart")
.attr("width",squareSide)
.attr("height",squareSide / 2.0);
Seems to be working up to this point based on reviewing the html but does not successfully execute the code below:
var bar = paretoBox.selectAll("g")
.data(function(d) {return data.fineBinSummary})
.enter()
.append("g")
.attr("transform",function(d,i) {
return "translate(" + i * barWidth + ",0)";
});
bar.append("rect")
.attr("y",function(d) {
return y(d.count);
})
.attr("height",function(d) {
return height - y(d.count)
})
.attr("width",barWidth - 1);
var y = d3.scale.linear()
.range([height,0])
.domain([0,d3.max(data.fineBinSummary,function(d) {
return d.count;
})]);
})
There are just two small problems with your code. First, in your nested selection, you are referencing the original data instead of the part bound to the current element. Instead of
var bar = paretoBox.selectAll("g")
.data(function(d) {return data.fineBinSummary})
use
var bar = paretoBox.selectAll("g")
.data(function(d) {return d.fineBinSummary})
Second, the max element for your scale is determined incorrectly -- data doesn't have a fineBinSummary member. The code should look like this:
var y = d3.scale.linear()
.range([height,0])
.domain([0, d3.max(data, function(d) {
return d3.max(d.fineBinSummary, function(e) { return e.count; });
})]);

Sankey diagram transition

I would like to know if there is an easy way to modify the Sankey diagram example so that there is smooth transition to new data. For example, imagine I have different datafiles (energy1.json, energy2.json ...) how could d3 plot a Sankey diagram for the first dataset, then waits and later on rearanges the boxes disposition to represent the second dataset?
This is possible. Here's one approach using a csv file. Working sankey here: https://www.betterment.com/resources/investment-strategy/portfolio-management/portfolio-diversification/
Define a global array outside of your d3.csv call.
var portfolioValues = [];
When parsing the csv to create the node/link structure, push values to your global array.
d3.csv("etf-geo.csv", function(error, data) {
graph = {"nodes" : [], "links" : []};
data.forEach(function (d, i) {
var item = { source: d.source, target: d.target, values: [] };
for (var j=0; j < 101; j++) {
item.values.push(d['value'+j.toString()]);
}
portfolioValues.push(item);
graph.nodes.push({ "name": d.source });
graph.nodes.push({ "name": d.target });
graph.links.push({
source: portfolioValues[i].source,
target: portfolioValues[i].target,
value: portfolioValues[i].values[startingAllocation]
});
});
//this handy little function returns only the distinct / unique nodes
graph.nodes = d3.keys(
d3.nest()
.key(function (d) { return d.name; })
.map(graph.nodes)
);
// it appears d3 with force layout wants a numeric source and target
// so loop through each link replacing the text with its index from node
graph.links.forEach(function (d, i) {
graph.links[i].source = graph.nodes.indexOf(graph.links[i].source);
graph.links[i].target = graph.nodes.indexOf(graph.links[i].target);
portfolioValues[i].source = graph.links[i].source;
portfolioValues[i].target = graph.links[i].target;
});
// now loop through each nodes to make nodes an array of objects
// rather than an array of strings
graph.nodes.forEach(function (d, i) {
graph.nodes[i] = { "name": d };
});
// construct sankey
sankey
.nodes(graph.nodes)
.links(graph.links)
.layout();
Listen for a change and pass user input to your update function.
$(".sankey-slider").bind("slider:changed", function (event, data) {
slideValue = data.value;
updateData(parseInt(slideValue));
});
Create a temporary array and retrieve the correct values from the global array. Call the sankey functions to recalculate the layout.
var newLinks = [];
portfolioValues.forEach(function(p, i) {
newLinks.push({
source: p.source,
target: p.target,
value: p.values[allocation]
});
});
graph.links = newLinks;
sankey
.nodes(graph.nodes)
.links(graph.links)
.size([width, height])
.layout();
Select each element that needs to be changed and pass the new data values.
d3.selectAll(".link")
.data(graph.links)
.attr("d", path)
.attr("id", function(d,i){
d.id = i;
return "link-"+i;
})
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
d3.selectAll(".node").attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
d3.selectAll("rect")
.attr("height", function(d) { return d.dy; })
.on("mouseover",highlight_node_links)
.on("mouseout",onNodeMouseout);
Working sankey here:
https://www.betterment.com/resources/investment-strategy/portfolio-management/portfolio-diversification/
Since the automatic positioning of nodes includes a part which tries to minimize link distance in a connected graph which is an np optimization problem, any kind of optimizer can potentially jump from one minimum to another leading to a jump in layout. So a guaranteed smooth transition wont be possible.
The closest possible solution would probably be to linearly interpolate between the two input data sets and thereby generate a series of graphs which (depending on the data) more or less smoothly transition from one two the other.
Hope this helps.