D3.js: set axis position based on data - javascript

I'm trying to draw multiple graphs in one svg image, based on chromosomal data. The purpose is to draw 1 graph per chromosome. I want to transpose the axis (and the data) based on the chromosome number in de data.
Data entries look like this (in JSON):
[{
"chrom": 1,
"pos": 2000000,
"ratio": 0.0253,
"average": 0.0408,
"stdev": 0.0257,
"z-score": - 0.6021
}, {
"chrom": 1,
"pos": 2250000,
"ratio": 0.0304,
"average": 0.0452,
"stdev": 0.0245,
"z-score": - 0.6021
}, {
"chrom": 1,
"pos": 2500000,
"ratio": 0.0357,
"average": 0.0498,
"stdev": 0.024,
"z-score": - 0.5885
}, {
"chrom": 1,
"pos": 2750000,
"ratio": 0.0381,
"average": 0.0522,
"stdev": 0.0228,
"z-score": - 0.6146
},
{etc..}
Currently my code looks like this:
d3.json("data.json", function(error, data) {
if (error) throw error;
x.domain(d3.extent(data, function(d) { return d.pos; }));
y.domain(d3.extent(data, function(d) { return d['ratio']; }));
svg.append("g")
.attr("class", "x axis")
.data(data)
//.attr("transform", "translate(0," + graph_height + ")")
.attr('transform', function(d) {
if (d.chrom > Math.ceil(chrnumber / 2)) {
console.log('translate('+ graph_width + graph_margin.center + ',' + graph_height + d.chrom * (graph_height + graph_margin.top) + ')');
return 'translate('+ graph_width + graph_margin.center + ',' + graph_height + d.chrom * (graph_height + graph_margin.top) + ')';
}else {
console.log('translate(0,' + graph_height + d.chrom * (graph_height + graph_margin.top) + ')');
return 'translate(0,' + graph_height + d.chrom * (graph_height + graph_margin.top) + ')';
}
})
.call(xAxis);
});
But this yields no errors, nor any output. I suppose there is some kind of error in the code above, because the svg image isn't generated on the page.
Does anyone have an idea where I went wrong?

The lines here:
svg.append("g")
.attr("class", "x axis")
.data(data)
are not enough to create one axis per chromosome. One way to do it (might not be the simplest, but a very "d3-friendly" one) is to create an array representing your set of chromosomes.
var chromList = d3.set( //data structure removing duplicates
data.map(function(d) { // create a list from data by ...
return d.chrom // ...keeping only the chrom field
}).values(); // export the set as an array
//chromList=[1,2,3 ..], according to the values of chrom seen in your data.
Now you bind this list to your axis, in order to create one axis per element:
svg.append("g") //holder of all x axis
.selectAll(".axis") //creates an empty selection at first, but it will be filled next.
.data(chromList) //bind the list of chrom ids to the selection
.enter() //the selection was empty, so each chromosome is "entering"
.append("g") //for each chromosome, insert a g element: this is the corresponding axis
.attr("class", "x axis")
.attr('transform', function(d) {
//use d here as your chromosome identifier
});

Related

d3.js: How to get event information for drag

I am using this example to implement dragging on a graph.
The most relevant part:
/// IMPLEMENT DRAG BEHAVIOR
drag = d3.drag().on("drag", dragged)
function dragged(event,d) {
d3.select(this).attr("transform", 'translate(' + event.x + ',' + 0 + ')')
}
for (line of quantile_horizontal_lines) {
line.call(drag)
}
The function dragged expects an event. But the object passed into dragged is just the coordinates of my line, with nothing about the event. Of course, it has no attribute x, so the code doesn't work.
An event object is supposed to look like this:
I can't figure out what I'm doing differently from the example.
My full code:
/// BASIC LINE GRAPH SETUP
// 2. Use the margin convention practice
var margin = {top: 50, right: 50, bottom: 50, left: 50}
, width = window.innerWidth - margin.left - margin.right // Use the window's width
, height = window.innerHeight - margin.top - margin.bottom; // Use the window's height
// 8. An array of objects of length N. Each object has key -> value pair, the key being "y" and the value is a random number
var dataset = data
// The number of datapoints
var n = data.length
// 5. X scale will use the index of our data
var xScale = d3.scaleLinear()
.domain([metadata.xmin, metadata.xmax]) // input
.range([0, width]); // output
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([metadata.ymin, metadata.ymax]) // input
.range([height, 0]); // output
// 7. d3's line generator
var line = d3.line()
.x(function(d) { return xScale(d.x); }) // set the x values for the line generator
.y(function(d) { return yScale(d.y); }) // set the y values for the line generator
// 1. Add the SVG to the graph div and employ #2
var svg = d3.select("#graph").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// 3. Call the x axis in a group tag
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
// 9. Append the path, bind the data, and call the line generator
plane = svg.append("g").attr('class','plane')
plane.append("path")
.datum(dataset) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
d3.select('.line') // move this to a CSS file later
.attr('fill','none')
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
/// ADD HORIZONTAL/VERTICAL LINES
plane.on('click',onclick)
onclick = function (event){
x = xScale.invert(event.layerX - margin.left);
y = yScale.invert(event.layerY - margin.right);
console.log(x,y)
}
quantile_horizontal_lines = new Array()
function drawQuantileLines(quantiles) {
console.log("running drawQuantileLines")
for (let i = 0; i < quantiles.length; i++) {
quantile = quantiles[i]
quantile_horizontal_line_0 = {'x': quantile.x, 'y': metadata.ymin}
quantile_horizontal_line_1 = {'x': quantile.x, 'y': quantile.y}
quantile_horizontal_lines.push(
plane.append("path")
.datum([quantile_horizontal_line_0, quantile_horizontal_line_1])
.attr('d', line)
.attr('class', 'line')
.attr('stroke', 'red'))
}
}
drawQuantileLines(quantiles)
/// IMPLEMENT DRAG BEHAVIOR
drag = d3.drag().on("drag", dragged)
function dragged(event,d) {
d3.select(this).attr("transform", 'translate(' + event.x + ',' + 0 + ')')
}
for (line of quantile_horizontal_lines) {
line.call(drag)
}
data, metadata, and quantiles are JSON objects generated from Python using json.dumps(). I doubt the JSONs are invalid in some way; I am able to draw the lines fine, the problem is with the dragging.
The example you are basing your code off of is d3v6. The canonical examples are generally updated fairly consitently with each version. You are using d3v4.
Versions prior to d3v6 used a different signature for functions passed to .on(). In d3v6, the functions take the form of function(event,d) prior to this these functions took the form:
function(d,i,nodes) {
console.log(d3.event) // event information
}
Where d is the bound datum, i is the index, and nodes is the group of nodes in the selection. So you should be able to use:
function dragged(d) {
d3.select(this).attr("transform", 'translate(' + d3.event.x + ',' + 0 + ')')
}
This change is the most notable change in d3v6.

apply sum function to parse sum of a column from csv into d3.js-chart

I have the following dataset in csv format:
Month,S40201,S40202,S40203
JAN,79,0,70
FEB,58,26,70
MAR,48,47,46
APR,64,98,77
MAY,79,71,64
JUN,86,103,116
JUL,95,75,95
AUG,0,40,3,5
SEP,60,82,79
OCT,98,101,79
NOV,60,81,75
DEC,7,30,46
The D3.js bar chart should display the sum of each column "S40201", "S40202", "S40203" as bar with the corresponding label on the X-axis. Label should be the column name (first row).
<script>
// Set the margins
var margin = {top: 60, right: 100, bottom: 20, left: 80},
width = 850 - margin.left - margin.right,
height = 370 - margin.top - margin.bottom;
// Parse the month variable
var parseMonth = d3.timeParse("%b");
var formatMonth = d3.timeFormat("%b");
// Set the ranges
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1)
var y = d3.scaleLinear().range([height, 0]);
// Create the svg canvas in the "graph" div
var svg = d3.select("#graph")
.append("svg")
.style("width", width + margin.left + margin.right + "px")
.style("height", height + margin.top + margin.bottom + "px")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform","translate(" + margin.left + "," + margin.top + ")")
.attr("class", "svg");
// Import the CSV data
d3.csv("data.csv", function(error, data) {
if (error) throw error;
// Format the data
data.forEach(function(d) {
d.Month = parseMonth(d.Month);
d.S40201 = +d.S40201;
d.S40202 = +d.S40202;
d.S40203 = +d.S40203;
});
var nest = d3.nest()
.key(function(d){
return d.S40201,d.S40202,d.S40203;
})
.sortKeys(d3.ascending)
.rollup(function(leaves){
return d3.sum(leaves, function(d) {return (d.S40201,d.S40203,d.S40203)});
})
.entries(data)
console.log(nest)
// Scale the range of the data
x.domain(nest.map(function(d) { return d.key; }));
y.domain([0, d3.max(nest, function(d) { return d.value; })]);
// Set up the x axis
var xaxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.attr("class", "x axis")
.call(d3.axisBottom(x)
//.ticks(d3.timeMonth)
.tickSize(0, 0)
//.tickFormat(d3.timeFormat("%B"))
.tickSizeInner(0)
.tickPadding(10));
// Add the Y Axis
var yaxis = svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y)
.ticks(5)
.tickSizeInner(0)
.tickPadding(6)
.tickSize(0, 0));
// yaxis.select(".domain").style("display","none")
// Add a label to the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - 60)
.attr("x", 0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Annual Sales")
.attr("class", "y axis label");
// Draw the bars
svg.selectAll(".rect")
.data(nest)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.value); });
})
</script>
With just one column it works fine, but If I want to add more than one column it doesn´t work correctly.
Welcome to StackOverflow. The problem you face is your nested data is not the way you like. If you console log your nested data the way you have it, the key and the value are both the same and not the headers.
Instead if you manually summarize the data the way you like it would be easier. For example:
var nest = [];//create empty array
var keys = Object.keys(data[0]); //get the headers for your data
//for each header push the sum as an object
keys.forEach(function (d, i) {
if (i == 0) return;//ignore first column as that is month
//get the sumfrom your data for all the values of this key i.e. d
var sum = d3.sum (data, function(e){ return e[d] });
//create an object with this key value pair
var obj = {
key: d, //column name
value: sum //sum for the column
}
nest.push(obj); //push this as an object in the nest array
})
Here is a block with the correct code showing the headers as the labels on the x-axis and the sum values.
https://bl.ocks.org/akulmehta/724d63f0108304ede84a14fc145aad28
Please feel free to comment if you need more explanation /guidance and please remember to mark it as an answer if this answers your question.
Update 1- For selected columns only
Based on the comments below, if you want selected columns only simply replace the var keys = Object.keys(data[0]); with an array of headers like var keys = ['S40201','S40202','S40203'] and also remove the line if (i == 0) return;//ignore first column as that is month as we do not have the month's column anymore. Final code for this part should look like this:
var nest = [];//create empty array
var keys = ['S40201','S40202','S40203']; //the headers for your data
//for each header push the sum as an object
keys.forEach(function (d, i) {
//get the sumfrom your data for all the values of this key i.e. d
var sum = d3.sum (data, function(e){ return e[d] });
//create an object with this key value pair
var obj = {
key: d, //column name
value: sum //sum for the column
}
nest.push(obj); //push this as an object in the nest array
})
And here is the block to demonstrate it works. Notice the CSV file has an additional column but it the graph ignores this. https://bl.ocks.org/akulmehta/724d63f0108304ede84a14fc145aad28

D3 transformation not applied to multiple <g> elements

Do you have any idea why the below code doesn't work? I'm trying to create 3 groups with different y coordinate for each. But as soon as I do it like that the transformation is not applied at all and all the <g>s are overlapping at 0,0.
If I change the function to explicit x,y coordinates in transformation it works correctly.
var dataset = [{
data: 100
}, {
data: 200
}, {
data: 300
}];
var groups = svg.selectAll("g")
.data(dataset)
.enter()
.append("g")
.attr("transform", "translate(0" + function(d,i) {return i * 100} + ")");
You have to return the whole translate value in a function:
.attr("transform", function (d, i){
return "translate(0," + (i * 100) + ")";
});

Reduce number of plots in D3 Scatterplot Matrix

I would like to reduce the number of plots in a D3 scatterplot matrix to just 4. I am using https://bl.ocks.org/mbostock/4063663 as my initial template. I have my data set up in http://plnkr.co/edit/lySDnd58vUlelRKmk20S?p=preview. Ideally I want only the plots on the top, setting up only one Y-Axis Label (Homicides per 1000) with the x-axis changing to each subsequent plot. Something similar to this quick mockup in paint. https://postimg.org/image/7638jvln3/
I was sure it would have been controlled somewhere in here, but I realized I was a bit lost. If anyone is able to help me set this up, it will be greatly appreaciated!
svg.selectAll(".x.axis")
.data(traits)
.enter().append("g")
.attr("class", "x axis")
.attr("transform", function(d, i) { return "translate(" + (n - i - 1) * size + ",0)"; })
.each(function(d) { x.domain(domainByTrait[d]); d3.select(this).call(xAxis); });
svg.selectAll(".y.axis")
.data(traits)
.enter().append("g")
.attr("class", "y axis")
.attr("transform", function(d, i) { return "translate(0," + i * size + ")"; })
.each(function(d) { y.domain(domainByTrait[d]);d3.select(this).call(yAxis); });
var cell = svg.selectAll(".cell")
.data(cross(traits, traits))
.enter().append("g")
.attr("class", "cell")
.attr("transform", function(d) { return "translate(" + (n - d.i - 1) * size + "," + d.j * size + ")"; })
.each(plot);
One last question, is it possible to get a y-axis title to appear. When I add the code below, everything rescaled and the data disappeared.
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font-size", "12px")
.text("Homicides per 1,000");
It is indeed possible using the same code. A little strange, but possible:
// keep similar calucations
var domainByTrait = {},
traits = d3.keys(data[0]).filter(function(d) { return d !== "name"; }),
n = traits.length - 1;
traits.forEach(function(trait) {
domainByTrait[trait] = d3.extent(data, function(d) { return d[trait]; });
});
// reset traits to ones you care about
var traits = ["% Holding Only HS Diploma", "Unemployement Rate", "Median Household Value ($10k)", "% African American"];
// chane xAxis ticks to 1, not n
xAxis.tickSize(size * 1);
yAxis.tickSize(-size * n);
...
// modify y axis to only Homicides
svg.selectAll(".y.axis")
.data(["Homicides per 1000 people"])
.enter().append("g")
...
// modify cross-product
var cell = svg.selectAll(".cell")
.data(cross(traits, ["Homicides per 1000 people"]))
.enter().append("g")
...
// remove filter on text titltes
cell
//.filter(function(d) { return d.i === d.j; })
.append("text")
...
Update plunker.

I am trying to visualize my json object with D3. I want date to be the x axis and y to be sales. number values stored a string

I have a json object that I am trying to visualize with D3.js. I want the x axis to represent the date in the json object which is stored as a string and the y axis to represent sales projections which is also a number in a string i.e "85,000.00"
example of my json object:
[{"Num":78689,"Client":"Health Services" ,"TotalEstSales":"85,000,000.00","Date ":"2/15/2015","RFP Receipt Date":null,"Exp. Proposal Due Date":"3/6/2015","Proposal Submission Date":null,"estAwardDate":"4/15/2015","Procurement Type":"New - Incumbent","Bid Type":"Standalone Contract"}]
and my d3 code:
// Various accessors that specify the four dimensions of data to visualize.
function x(d) { return d.date; }
function y(d) { return d.TotalEstSales; }
function radius(d) { return parseFloat(d.TotalEstSales);}
function color(d) { return d.region; }
function key(d) { return d.Title;}
// Chart dimensions.
var margin = {top: 19.5, right: 19.5, bottom: 19.5, left: 39.5},
width = 960 - margin.right,
height = 500 - margin.top - margin.bottom;
// Various scales. These domains make assumptions of data, naturally.
var xScale = d3.scale.log().domain([300, 1e5]).range([0, width]),
yScale = d3.scale.linear().domain([10000, 85000000]).range([height, 0]),
radiusScale = d3.scale.sqrt().domain([0, 5e8]).range([0, 40]),
colorScale = d3.scale.category10();
// The x & y axes.
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).ticks(12, d3.format(",d")),
yAxis = d3.svg.axis().scale(yScale).orient("left");
// Create the SVG container and set the origin.
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Add the x-axis.
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the y-axis.
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// Add an x-axis label.
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width)
.attr("y", height - 6)
.text("Data of RFP");
// Add a y-axis label.
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 6)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("Award amount");
// Add the year label; the value is set on transition.
var label = svg.append("text")
.attr("class", "year label")
.attr("text-anchor", "end")
.attr("y", height - 24)
.attr("x", width)
.text(2015);
// Load the data.
d3.json("rfpdata.json", function(data) {
// A bisector since many nation's data is sparsely-defined.
// var bisect = d3.bisector(function(d) { return d[0]; });
// Add a dot per nation. Initialize the data at 1800, and set the colors.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.style("fill", function(d) { return colorScale(color(d)); })
.call(position)
.sort(order);
// Add a title.
dot.append("title")
.text(function(d) { return d.Client; })
// Positions the dots based on data.
function position(dot) {
dot .attr("cx", function(d) { return xScale(x(d)); })
// .attr("cy", function(d) { return yScale(y(d)); })
.attr("r", function(d) { return radiusScale(radius(d)); });
}
// Defines a sort order so that the smallest dots are drawn on top.
function order(a, b) {
return radius(b) - radius(a);
}
// After the transition finishes, you can mouseover to change the year.
function enableInteraction() {
var yearScale = d3.scale.linear()
.domain([1800, 2009])
.range([box.x + 10, box.x + box.width - 10])
.clamp(true);
// Cancel the current transition, if any.
function mouseover() {
label.classed("active", true);
}
function mouseout() {
label.classed("active", false);
}
function mousemove() {
displayYear(yearScale.invert(d3.mouse(this)[0]));
}
}
// this is the function needed to bring in data
// Interpolates the dataset for the given (fractional) year.
function interpolateData(date) {
return data.map(function(d) {
return {
title: d.Title,
client: d.Client,
sales: parseFloat(d.TotalEstSales),
sales: interpolateValues(d.TotalEstSales, date),
};
});
}
// Finds (and possibly interpolates) the value for the specified year.
function interpolateValues(values, date) {
var i = bisect.left(values, date, 0, values.length - 1),
a = values[i];
if (i > 0) {
var b = values[i - 1],
t = (date - a[0]) / (b[0] - a[0]);
return a[1] * (1 - t) + b[1] * t;
}
return a[1];
}
});
I am not sure what I am doing wrong but the data is not displaying? Am i properly parsing the date string? This was a graph available on the d3 site. I want a bubble graph where the radius changes depending on the size of the sale and the date is on the x axis.
#all Update:
I was able to make the proper adjustment for date on the xaxis here:
var xAxis = d3.svg.axis().orient("bottom").scale(xScale).tickFormat(d3.time.format("%m/%d")),
yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(23, d3.format(" ,d"));
d3.time.format was what I was looking for. Once data was loaded I needed to parse the date:
month = data.Date;
parseDate = d3.time.format("%m/%d/%Y").parse;
data.forEach(function(d) {
d.Date = parseDate(d.Date);
});
// update Dates here when new report comes in monthly
xScale.domain([parseDate("1/1/2015"),parseDate("6/1/2015")]);
obviously, using "Date" as a name column in the excel file was not idea for "Date" in js(because it is an oject).

Categories

Resources