Trouble Binding JSON subarrays in d3.js - json

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; });
})]);

Related

why no sound after I add MediaElement Source to the music? Web Audio API

I'm trying to use the Web Audio API as I'm looking to make some visualizations based on the music that's playing. However all examples I have found, it's like the audio is muted. It plays the file but there's no sound.
I know that it entails CORS, and I tried to fix by adding "audio.crossOrigin = 'anonymous'". But it doesn't work. Thanks in advance.
This is the link to codepen: http://codepen.io/lianliu/pen/xwPXvO/?editors=101
$(document).ready(function() {
var audio = new Audio("http://w-labs.at/experiments/audioviz/GYAKO.mp3");
audio.crossOrigin = "anonymous";
var audioCtx = new(window.AudioContext || window.webkitAudioContext)();
//var audioElement = document.getElementById('myAudio');
var audioSrc = audioCtx.createMediaElementSource(audio);
var analyser = audioCtx.createAnalyser();
// Bind our analyser to the media element source.
audioSrc.connect(analyser);
audioSrc.connect(audioCtx.destination);
audio.play();
audio.volume = 0.5;
//var frequencyData = new Uint8Array(analyser.frequencyBinCount);
var frequencyData = new Uint8Array(200);
var svgHeight = '300';
var svgWidth = '1200';
var barPadding = '1';
function createSvg(parent, height, width) {
return d3.select(parent).append('svg').attr('height', height).attr('width', width);
}
var svg = createSvg('body', svgHeight, svgWidth);
// Create our initial D3 chart.
svg.selectAll('rect')
.data(frequencyData)
.enter()
.append('rect')
.attr('x', function(d, i) {
return i * (svgWidth / frequencyData.length);
})
.attr('width', svgWidth / frequencyData.length - barPadding);
// Continuously loop and update chart with frequency data.
function renderChart() {
requestAnimationFrame(renderChart);
// Copy frequency data to frequencyData array.
analyser.getByteFrequencyData(frequencyData);
// Update d3 chart with new data.
svg.selectAll('rect')
.data(frequencyData)
.attr('y', function(d) {
return svgHeight - d;
})
.attr('height', function(d) {
return d;
})
.attr('fill', function(d) {
return 'rgb(0, 0, ' + d + ')';
});
}
// Run the loop
renderChart();
});
I know that it entails CORS, and I tried to fix by adding
"audio.crossOrigin = 'anonymous'". But it doesn't work.
You're on the right track. You need to make sure your server serve the mp3 with the Access-Control-Allow-Origin header set. Set it to the specific domain you're making the request from, or to "*" to access it from anywhere.
How you set the header depends on the server software you're running. Here's a list of different approaches: http://enable-cors.org/server.html
Edit:
Try changing the URL to a mp3 that is served with the header set, like
http://chrome-jam-static.commondatastorage.googleapis.com/sounds/soundlogo_jam.mp3
and you'll see that your code works just fine.

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!

D3 add graticule and geojson

When adding D3 graticule first and then calling d3.json , the very first feature within json doesn't show on the map. If I add d3.graticule within d3.json after appending paths, graticule will be drawn on top of the features, which is not what I wanted. Here is my JavaScript file:
$(document).ready(function (){
$('.aToolTip').tooltip();
$('.aPopOver').popover();
// D3 Mapping Starts Here
var h = $('#myMap').height();
var w = $('#myMap').width();
console.log("h = " + h , ", w = " + w);
$('svg').attr('width', w)
.attr('height', h);
var aSVG = d3.select("#aSVGElement");
var aProjection = d3.geo.mercator()
.rotate([-135, 24])
.scale(600)
.translate([w/2, h/2]);
var path = d3.geo.path()
.projection(aProjection);
var graticule = d3.geo.graticule()
.step([10, 10]);
aSVG.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
d3.json("js/Australia.json", function(error, json){
aSVG.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path);
}); // end d3.json
});// end document ready
The first feature doesn't show up if you're drawing the grid first because you're selecting paths, of which the grid is one. This means that the first data element (i.e. the first feature) is matched to the existing path and not part of the .enter() selection.
To fix this, simply assign a class to your feature paths and select accordingly:
aSVG.selectAll("path.feature")
.data(json.features)
.enter()
.append("path")
.attr("class", "feature")
.attr("d", path);

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.

Is d3 edge labeling possible?

Is it possible to have edge labels in a graph using d3? I've looked over the examples and some of the documentation, and I can see where node labeling is possible, but edge labeling is not explicitly mentioned anywhere (that I can find).
Following other peoples examples I
add a path alongside every edge,
add text to the edges
bind that text to a textpath which is referencing the path along the edge
This example is using the above ideas: http://bl.ocks.org/jhb/5955887
The short answer is "yes", but there's no explicit support for it. You'll have to determine the position of the label yourself. One way of doing this would be to attach the label to the start node and translate it by half the distance to the target node, plus some offset to prevent it overlapping the line. For more elaborate edges (e.g. curves) it would be more difficult.
I tried the text on a path option, but it is quite involved and didn't result in the appearance I wanted. Unfortunately, I didn't check it into the local git repository. This comes from my Eclipse history (thanks Eclipse developers). You'll have to change this code snippet to your own data structure, but I'll hope it is of some help. In particular, notice the adding of the id value to the lines selection and then reusing it in the percentages selection by using the xlink:href attribute. Also, I appended a tspan in the textpath to move the text down by a couple of pixels so it appears over the path instead of on it.
function drawLines(links) {
var diagonal = d3.svg.diagonal();
var format = d3.format(".1%");
var linkKey = function(l) {return l.target.key; };
var lines = linesGroup.selectAll("path").data(links, linkKey);
lines.enter()
.append("path")
.on("mouseover", select)
.on("mouseout", unselect)
.attr("d", diagonal)
.attr("id", function (l) { return "interaction-path-target-" + l.target.key; })
.style("stroke-width", 0.000001);
lines.exit()
.transition().duration(500)
.style("stroke-width", 0.000001)
.remove();
lines.transition()
.delay( function(d, i) { return i * 100; })
.duration(500)
.style("stroke-width", function(d) { return d.weight == 0 ? 0.000001 : d.weight / 1000; })
.attr("d", diagonal);
var percentages = linesGroup.selectAll("text").data(links, linkKey);
percentages.enter()
.append("text")
.attr("opacity", 1)
.append("svg:textPath")
.attr("startOffset", "70%")
.attr("xlink:href",
function(l) {
return "#interaction-path-target-" + l.target.key;
})
.append("svg:tspan")
.attr("dy", 3)
.attr("class", "percentageText")
percentages.exit()
.transition().duration(500)
.attr("opacity", 0)
.remove();
percentages
.transition()
.delay( function(d, i) { return i * 100; })
.duration(500)
.attr("opacity", 1);
percentages.select(".percentageText").text(function(d) {
var newvalue = d.weight ?
d.incomming ? percentageIn(d.weight) : percentageOut(d.weight) : 0;
return format(newvalue);
});
}