Accessing Nested Array in a D3 variable - javascript

This one has got to be easy but I can't for the life of me figure out why it's not working.
I've got some D3 code to plot some circles. I've nested arrays of six numbers inside a single variable (called 'dataset'). I'm trying to access those values to use a a y-value for the circle.
var width = 600;
var height = 400;
var dataset = [[16.58, 17.90, 17.11, 17.37, 13.68, 13.95], [20.26,1 3.40, 18.63, 19.28, 20,92, 18.95], [16.32, 23.16, 21.05, 28.16, 23.68, 23.42], [31.32, 30.80, 29.37, 28.16, 32.37, 27.63], [41.32, 39.74, 29.37, 35.00, 35.53, 30.00], [25.83, 38.27, 43.33, 45.83, 44.17, 41.25]];
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d, i) {
return i * 100 + 50;
})
.attr("cy", function(d, i) {
for (var j = 0; j<6; j++){
//console.log(i,j);
return d[i][j]; //THIS IS WHERE I'M FAILING
}
})
.attr("r", 15);
So the x values are just 100 px intervals (I basically want each set in its own column). The y value of each circle should then be the j'th term in the i'th array. So why doesn't d[i][j] return that?
I've got a console.log statement commented out. If I un-commennt that everything logs just as I would expect, but the double bracket notation is clearly not accessing the numbers. If I go straight to the console in the browser and type dataset[0][1], it returns '17.90', so why doesn't it work in this implementation?
So confused.
Thanks

Related

D3js legend color does not match to the map color javascript

I have a map already drawed. I would like to add a legend using d3.js. For example when filering by length, the map should show differents colors. Since a week, I couldn't achieve this task. My map color seem to be good but the legend does not match.
Could anybody help me with my draw link function ?
https://jsfiddle.net/aba2s/xbn9euh0/12/)
I think it's the error is about the legend function.
Here is the function that change my map color Roads.eachLayer(function (layer) {layer.setStyle({fillColor: colorscale(layer.feature.properties.length)})});
function drawLinkLegend(dataset, colorscale, min, max) {
// Show label
linkLabel.style.display = 'block'
var legendWidth = 100
legendMargin = 10
legendLength = document.getElementById('legend-links-container').offsetHeight - 2*legendMargin
legendIntervals = Object.keys(colorscale).length
legendScale = legendLength/legendIntervals
// Add legend
var legendSvg = d3.select('#legend-links-svg')
.append('g')
.attr("id", "linkLegendSvg");
var bars = legendSvg.selectAll(".bars")
//.data(d3.range(legendIntervals), function(d) { return d})
.data(dataset)
.enter().append("rect")
.attr("class", "bars")
.attr("x", 0)
.attr("y", function(d, i) { return legendMargin + legendScale * (legendIntervals - i-1); })
.attr("height", legendScale)
.attr("width", legendWidth-50)
.style("fill", function(d) { return colorscale(d) })
// create a scale and axis for the legend
var legendAxis = d3.scaleLinear()
.domain([min, max])
.range([legendLength, 0]);
legendSvg.append("g")
.attr("class", "legend axis")
.attr("transform", "translate(" + (legendWidth - 50) + ", " + legendMargin + ")")
.call(d3.axisRight().scale(legendAxis).ticks(10))
}
D3 expects your data array to represent the elements you are creating. It appears you are passing an array of all your features: but you want your scale to represent intervals. It looks like you have attempted this approach, but you haven't quite got it.
We want to access the minimum and maximum values that will be provided to the scale. To do so we can use scale.domain() which returns an array containing the extent of the domain, the min and max values.
We can then create a dataset that contains values between (and including) these two endpoints.
Lastly, we can calculate their required height based on how high the visual scale is supposed to be by dividing the height of the visual scale by the number of values/intervals.
Then we can supply this information to the enter/update/exit cycle. The enter/update/exit cycle expects one item in the data array for every element in the selection - hence why need to create a new dataset.
Something like the following shold work:
var dif = colorscale.domain()[1] - colorscale.domain()[0];
var intervals = d3.range(20).map(function(d,i) {
return dif * i / 20 + colorscale.domain()[0]
})
intervals.push(colorscale.domain()[1]);
var intervalHeight = legendLength / intervals.length;
var bars = legendSvg.selectAll(".bars")
.data(intervals)
.enter().append("rect")
.attr("class", "bars")
.attr("x", 0)
.attr("y", function(d, i) { return Math.round((intervals.length - 1 - i) * intervalHeight) + legendMargin; })
.attr("height", intervalHeight)
.attr("width", legendWidth-50)
.style("fill", function(d, i) { return colorscale(d) })
In troubleshooting your existing code, you can see you have too many elements in the DOM when representing the scale. Also, Object.keys(colorscale).length won't produce information useful for generating intervals - the keys of the scale are not dependent on the data.
eg

D3 -- Arcs for chord diagram not being displayed

I trying to understand how the D3 chord diagram works. My first step is to display the arcs for the diagram with following script. But for some reason, the arcs are not showing up.
See web page HERE
Can some one tell me what I am missing?
<body>
<script>
// Chart dimensions.
var width = 960,
height = 750,
innerRadius = Math.min(width, height) * .41,
outerRadius = innerRadius * 1.1;
//Create SVG element with chart dementions
var svg = d3. select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append ("g")
.attr("transform", "translate (" + width / 2 + "," + height / 2 + ")");
//------------Reformat Data ------------------------------------------
var matrix = []; // <- here is the data
d3.tsv('picData.tsv', function(err, data)
{
//console.log(data);
pictures = d3.keys(data[0]).slice(1);
//console.log(pictures);
data.forEach(function(row)
{
var mrow = [];
pictures.forEach(function(c)
{
mrow.push(Number(row[c]));
});
matrix.push(mrow);
//console.log(mrow);
});
//console.log('1st row: ' + matrix[0]);
//console.log(matrix);
});
//---------------- Define diagram layout ----------------------------
var chord = d3.layout.chord() //<-- produce a chord diagram from a matrix of input data
.matrix(matrix) //<-- data in matrix form
.padding(0.05)
.sortSubgroups(d3.descending);
var fill = d3.scale.category20(); //<-- https://github.com/mbostock/d3/wiki/API-Reference#d3scale-scales
//console.log(fill);
var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", "group");
//console.log(g);
// create arcs
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
//console.log(arc);
g.append("path")
.attr("d", arc)
.style("fill", function(d) { console.log(d.index); return fill(d.index);})
.style("stroke", function(d) { return fill(d.index); })
.attr("id", function(d, i) { return"group-" + d.index });;
g.append("svg:text")
.attr("x", 6)
.attr("class", "picture")
.attr("dy", 15)
.append("svg:textPath")
.attr("xlink:href", function(d) { return "#group-" + d.index; })
.text(function(d) { return pictures[d.index]; });
//console.log(g);
</script>
</body>
Your problem stems from the fact that d3.tsv is asynchronous:
Issues an HTTP GET request for the comma-separated values (CSV) file at the specified url... The request is processed asynchronously.
As a result, all of your code under "Define diagram layout" is being executed before any data is loaded. Otherwise, your code works fine (See image below). So just move all your code into your d3.tsv(...) call and you'll be all set.
Your script is running without errors, but no elements are being created from your data join. That's usually a sign that you are passing in a zero-length data array.
In fact, you're not passing in an array at all; you're passing a function object. When d3 looks up the array length of that object, it returns undefined, which gets coerced to the number zero, and so no groups and no chords are created.
Relevant part of your code:
var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", "group");
To actually get the array of chord group data objects, you need to call chord.groups(). Without the () at the end, chord.groups is just the name of the function as an object.
Edited to add:
Ooops, I hadn't even noticed that your drawing code wasn't included inside your d3.tsv callback function. Fix that, as described in mdml's answer, then fix the above.

Linear cx scaling with javascript objects in D3

I am attempting to plot a simple dataset consisting of an array of javascript objects. Here is the array in JSON format.
[{"key":"FITC","count":24},{"key":"PERCP","count":16},{"key":"PERCP-CY5.5","count":16},{"key":"APC-H7","count":1},{"key":"APC","count":23},{"key":"APC-CY7","count":15},{"key":"ALEXA700","count":4},{"key":"E660","count":1},{"key":"ALEXA647","count":17},{"key":"PE-CY5","count":4},{"key":"PE","count":38},{"key":"PE-CY7","count":18}]
Each object simply contains a String: "key", and a Integer: "count".
Now, I am plotting these in D3 as follows.
function key(d) {
return d.key;
}
function count(d) {
return parseInt(d.count);
}
var w = 1000,
h = 300,
//x = d3.scale.ordinal()
//.domain([count(lookup)]).rangePoints([0,w],1);
//y = d3.scale.ordinal()
//.domain(count(lookup)).rangePoints([0,h],2);
var svg = d3.select(".chart").append("svg")
.attr("width", w)
.attr("height", h);
var abs = svg.selectAll(".little")
.data(lookup)
.enter().append("circle")
.attr("cx", function(d,i){return ((i + 0.5)/lookup.length) * w;})
.attr("cy", h/2).attr("r", function(d){ return d.count * 1.5})
Here is what this looks like thus far.
What I am concerned about is how I am mapping my "cx" coordinates. Shouldn't the x() scaling function take care of this automatically, as opposed to scaling as I currently handle it? I've also tried .attr("cx", function(d,i){return x(i)}).
What I eventually want to do is label these circles with their appropriate "keys". Any help would be much appreciated.
Update:
I should mention that the following worked fine when I was dealing with an array of only the counts, as opposed to an array of objects:
x = d3.scale.ordinal().domain(nums).rangePoints([0, w], 1),
y = d3.scale.ordinal().domain(nums).rangePoints([0, h], 2);
Your code is doing what you want...I just added the text part. Here is the FIDDLE.
var txt = svg.selectAll(".txt")
.data(lookup)
.enter().append("text")
.attr("x", function (d, i) {
return ((i + 0.5) / lookup.length) * w;
})
.attr("y", h / 2)
.text(function(d) {return d.key;});
I commented out the scales, they were not being used...as already noted by you.

duplicating the whole svg element using d3.js

I am creating a rectangle using d3.js, inside that rectangle i am creating 10 smaller rectangles`.
i want to replicate whole thing into another svg element on mouse click.
jsfiddle link of the code : http://jsfiddle.net/nikunj2512/XK585/
Here is the code:
var svgContainer = d3.select("body").append("svg")
.attr("width", 200)
.attr("height", 200);
//Draw the Rectangle
var rectangle = svgContainer.append("rect")
.attr("x", 10)
.attr("y", 10)
.attr("fill", "red")
.attr("width", 200)
.attr("height", 200);
var bigRectContainer = d3.select('#bigRectContainer').append('svg')
.attr('width', 200)
.attr('height', 200);
var dim = 20;
var x = 0;
var y = 0;
for (i = 1; i < 11; i++) {
x = 10 + (i-1)*dim;
//alert(x);
y = 10;
svgContainer.append("rect")
.attr("x", x)
.attr("y", y)
.attr("width", 20)
.attr("height", 20)
.style("fill", "black");
}
var bigRectContainer = svgContainer.append("g");
svgContainer.selectAll("rect").on("click", function () {
var littleRect = d3.select(this);
console.log(littleRect)
var bigRect = bigRectContainer.selectAll("rect")
.data(littleRect)
.enter()
.append("rect");
});
Please tell me where i made the mistake...
I'm not entirely certain what you're trying to do with the code you've posted, but I thought that duplicating an entire SVG node was interesting. It turns out it's quite easy to do with selection#html - this doesn't work on the SVG node, but it does work on its container, so it's easy to clone the whole node:
function addAnother() {
var content = d3.select(this.parentNode).html();
var div = d3.select('body').append('div')
.html(content);
div.selectAll('svg').on('click', addAnother);
}
svg.on('click', addAnother);
See working fiddle here. Note that this only works if the SVG node is the only child of its parent - otherwise, you might need to wrap it somehow.
D3 doesn't provide cloning functionality, probably because of the native cloneNode method that already exists on DOM elements, including SVG nodes.
This method includes a boolean parameter to deep copy (i.e. copy all descendants) instead of just cloning the node it is called on. You would probably want to do something like bigRectContainer.node().cloneNode(true) to copy the entire DOM branch of rectangles.

Confusing d3.js code with random data that doesn't show and multiple SVGs that show before being defined

I'm using this tutorial to learn some basic d3.
I'm at the "binding data" section, which is so far proving quite confusing.
var dataset = [],
i = 0;
for(i=0; i<5; i++){
dataset.push(Math.round(Math.random()*100));
}
alert("Data: " + dataset)
var sampleSVG = d3.select("#viz")
.append("svg:svg")
.attr("width", 400)
.attr("height", 75);
sampleSVG.selectAll("rect")
.data(dataset)
.enter().append("svg:rect")
.style("stroke", "gray")
.style("fill", "white")
.attr("height", 40)
.attr("width", 75)
.attr("x", function(d, i){return i*80})
.attr("y", 20);
My questions are:
We have created a dataset of 5 random numbers. Why are these not reflected in the widths of the rectangles?
.append("svg:svg") doesn't refer to any rectangles, so how can we selectAll("rect") afterwards if they don't even exist?
In the anonymous function, what does d refer to?
In the anonymous function, what does i refer to? What is it multiplying by 80?
Does it automatically loop through all points in the dataset? In the final code chunk, there doesn't seem to be any iteration, so I'm guessing it just does this for every data element?
So confused!
Because the width of the rectangle is fixed at 75 and the random numbers aren't used.
append("svg:rect") creates the rectangles
d refers to the dataset, these would be the random numbers.
i is the index of the dataset item so 0 for the first item, 1 for the second and so on up to 4 as there are 5 items in the dataset
yes, you guessed right.

Categories

Resources