unique id , id name for generated rectangles in D3 - javascript

The following code generates 11 squares using D3:
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.3.0/d3.js"></script>
<script type="text/javascript">
var width = 1000,
height = 250,
margin = 4,
nRect = 11,
rectWidth = (width - (nRect - 1) * margin) / nRect,
svg = d3.select('#chart').append('svg')
.attr('width', width)
.attr('height', height);
var data = d3.range(nRect),
posScale = d3.scaleLinear()
.domain(d3.extent(data))
.range([0, width - rectWidth]);
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', posScale)
.attr('width', rectWidth)
.attr('height', height);
</script>
My plan is to use those squares later as links, is there anyway I can put a unique ID or name on every square generated?

The simplest solution is using the rectangles' indices:
.attr("id", function(d,i){ return "rect" + i})
Have in mind that IDs cannot start by number. That's why I'm concatenating the string rect with each rectangle's index.
Here is a demo:
var width = 1000,
height = 250,
margin = 4,
nRect = 11,
rectWidth = (width - (nRect - 1) * margin) / nRect,
svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height);
var data = d3.range(nRect),
posScale = d3.scaleLinear()
.domain(d3.extent(data))
.range([0, width - rectWidth]);
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr("id", function(d,i){ return "rect" + i})
.attr('x', posScale)
.attr('width', rectWidth)
.attr('height', height);
<script src="https://d3js.org/d3.v4.min.js"></script>

Related

Plotting a Histogram in D3 without knowing number of bins

I'm trying to create a function that creates a histogram for a given array. Clearly, there's no data for the X-Axis and I have to choose the bins arbitrarily. What would be the best way to do so?
My code:
var width = 700, height = 500, pad = 30, barPadding = 1;
function plotHistogram(element, dataset) {
var svg = d3.select(element)
.append("svg")
.attr("width", width)
.attr("height", height)
var bars = svg.append("g")
// Container box
var rectangle = svg.append("rect")
.attr("height", height)
.attr("width", width)
.attr("stroke-width", 2).attr("stroke", "#ccc")
.attr("fill", "transparent")
var xScale = d3.scaleLinear()
// Using default domain (0 - 1)
.range([pad, width - pad * 2])
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) { return d; })])
.range([height - pad, pad])
var xAxis = d3.axisBottom(xScale)
var yAxis = d3.axisLeft(yScale)
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (height - pad) + ")")
.call(xAxis)
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + pad +", 0)")
.call(yAxis)
svg.selectAll("bars")
.data(dataset)
.enter()
.append("rect")
// Evenly spacing out bars
.attr("x", function(d, i) { return i * width/dataset.length; })
// Top of each bar as the top of svg. To remove inverted bar graph.
.attr("y", function(d) { return height - (d * 4); })
// To give padding between bars
.attr("width", width / dataset.length - barPadding)
// To make the bars taller
.attr("height", function(d) { return d * 4; })
.attr("fill", "teal");
}
// #normal is the id of the div element.
plotHistogram("#normal", [10, 20, 30, 40, 50, 60, 70, 80]);
Edit 1: I have decided to use the default bin size (0 - 1) for the xScale above. I'm facing problems creating the bars.
Generate the bins as in https://bl.ocks.org/mbostock/3048450
The array in your plotHistogram is like the array data in #mbostock's bl.ock...
HTH

Display multiple d3.js charts in a single html page

I have the the d3.js code which is pasted here.
I am trying to display more than one graphs in the same page. Though the d3.js code is same. Say one from data1.json and the other from data2.json. Following is the snippet which is bothering me.
<svg width="960" height="960"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg2 = d3.select("svg"),
margin = 20,
diameter = +svg2.attr("width"),
g = svg2.append("g").attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
As per different answers in SO here, here, here, here or here, the solution seems to be one of the following:
Use different variable name to hold svgs such as svg1, svg2.. etc..
which I have done.
Use a method as described here.
var chart1 = d3.select("#area1")
.append("svg")
Method two is not working for me, as it shows blank page.
How to resolve this. I am sure that I am not getting the syntax correctly.
There's no problem at all using multiple SVGs on the same page. Here's an example:
var svg1 = d3.select("#svg1");
svg1.append("circle")
.attr("cx",100)
.attr("cy", 100)
.attr("r", 90)
.attr("fill", "red");
var svg2 = d3.select("#svg2");
svg2.append("circle")
.attr("cx",100)
.attr("cy", 100)
.attr("r", 90)
.attr("fill", "blue");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="200" height="200" id="svg1"></svg>
<svg width="200" height="200" id="svg2"></svg>
There is no need for repeating all the code, as you're doing right now. Don't repeat yourself.
An easy alternative is wrapping all your D3 code in a function that has two parameters, selector and url:
function draw(selector, url){
//code here
};
Then, inside that function draw, you set the position of your SVG:
var svg = d3.select(selector).append("svg")...
And the URL you get the data:
d3.json(ulr, function(error, root) {...
After that, just call the draw function twice, with different arguments:
draw(selector1, url1);
draw(selector2, url2);
Here is a demo, read it carefully to see how it works:
draw("#svg1", "#data1");
draw("#svg2", "#data2");
function draw(selector, url){
var data = d3.csvParse(d3.select(url).text())
var width = 500,
height = 150;
var svg = d3.select(selector)
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scalePoint()
.domain(data.map(function(d) {
return d.name
}))
.range([50, width - 50])
.padding(0.5);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.value
}) * 1.1])
.range([height - 20, 6]);
var line = d3.line()
.x(function(d){ return xScale(d.name)})
.y(function(d){ return yScale(d.value)});
svg.append("path")
.attr("d", line(data))
.attr("stroke", "teal")
.attr("stroke-width", "2")
.attr("fill", "none");
var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale);
svg.append("g").attr("transform", "translate(0,130)")
.attr("class", "xAxis")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(50,0)")
.attr("class", "yAxis")
.call(yAxis);
}
pre {
display: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div>First SVG</div>
<div id="svg1"></div>
<div>Second SVG</div>
<div id="svg2"></div>
<pre id="data1">name,value
foo,8
bar,1
baz,7
foobar,9
foobaz,4</pre>
<pre id="data2">name,value
foo,1
bar,2
baz,3
foobar,9
foobaz,8</pre>
If the two charts use the same code, I think the most d3-like way to go about it would be
var width = 960,
height = 960,
margin = 30;
var svgs = d3.select('#area1')
.selectAll('svg')
.data([json1, json2])
.enter()
.append('svg')
.attr('width', width)
.attr('height', height)
svgs.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.each(function(d) {console.log(d)}) // will log json1, then json2
You'll then have json1 and json2 bound to each of the newly appended svgs, and all code that follows will be done to both.
var width = 200,
height = 100,
margin = 30;
var svgs = d3.select('#area1')
.selectAll('svg')
.data([{text:'thing1'}, {text:'thing2'}])
.enter()
.append('svg')
.attr('width', width)
.attr('height', height);
svgs.append("text")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.text(function(d) {return d.text});
<script src="https://d3js.org/d3.v4.js"></script>
<div id='area1'></div>

Linear scale not using range specified in d3

For some reason my graph is going into my padding, even though I've incorporated the padding into the linear scale of y values. Here's my code:
$(document).ready(function(){
//padding
var padding = {
top: 20,
right: 20,
bottom: 20,
left: 20,
};
//graph dimensions
var w = 1000
var h = 500
//append and assign variable reference to svg
var svg = d3.select('section').append('svg').attr('id', 'graph').attr('width', w).attr('height', h);
//get json data
d3.json('https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/GDP-data.json', function(error, data){
//error handling
if (error){
console.warn(error);
}
var dataset = data.data;
//minimum and maximum date data points
var minDate = new Date(dataset[0][0]);
var maxDate = new Date(dataset[274][0]);
//scale data to svg dimensions
var xScale = d3.time.scale().domain([minDate, maxDate]).range([padding.left, w - padding.right]);
var yScale = d3.scale.linear().domain([0, d3.max(dataset, function(d){return d[1];})]).range([h - padding.bottom, padding.top]);
console.log(yScale(0));
//make x and y axis
var xAxis = d3.svg.axis().orient('bottom').scale(xScale);
//generate graph
svg.selectAll('rect')
.data(dataset)
.enter()
.append('rect')
.attr('x', function(d, i){
return i * ((w - padding.left - padding.right) / dataset.length) + padding.left;
})
.attr('y', function(d){
return yScale(d[1]);
})
.attr('width', (w - padding.left - padding.right) / dataset.length)
.attr('height', function(d){return d[1]})
.attr('fill', '#4682B4');
//generate x and y axix
svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + (h - padding.bottom) + ')')
.call(xAxis);
});
});
Here's the page on Codepen.
Does anyone know why the graph is cutting into the x axis like that? How do I fix it so that the bars start padding.bottom pixels away from the bottom
I've found the answer, I changed height attribute of svg to:
.attr('height', function(d){return h - padding.bottom - yScale(d[1])});
and it worked

D3.js transition starts at height 0, needs to start at previous value

I have a bar chart displaying data on which you can filter through different years with the press of a button. I want the chart to transition from the current value to the new value, but now it starts at the bottom each time you press a button. How can I fix this?
Thanks!
var margin = {top: 20, right: 20, bottom: 30, left: 20},
height = 500 - margin.top - margin.bottom,
width = 600 - margin.left - margin.right
function bars(data) {
max = d3.max(data)
var yScale = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, height])
var xScale = d3.scale.ordinal()
.domain(d3.range(0, data.length))
.rangeBands([0, width], .2)
var myChart = d3.select("#chart")
var bars = myChart.selectAll("rect.bar")
.data(data)
//enter
bars.enter()
.append("svg:rect")
.attr("class", "bar")
.attr("fill", "#800")
//apply to everything (enter and update)
bars.style('fill', '#C64567')
.attr('width', xScale.rangeBand())
.attr('x', function(d,i){
return xScale(i);
})
.attr('height', 0)
.attr('y', height)
bars.transition()
.attr('height', function(d){
return yScale(d);
})
.attr('y', function(d){
return height - yScale(d);
})
.duration(1000)
.ease('elastic')
}
function init() {
//setup the svg
var svg = d3.select("#svg")
.style('background', '#000')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append("svg:g")
.attr("id", "chart")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
//UI
d3.select("#button1")
.on("click", function (d, i) {
bars(j1996);
})
d3.select("#button2")
.on("click", function (d, i) {
bars(j1997);
})
d3.select("#button3")
.on("click", function (d, i) {
bars(j1998);
})
//draw the bars
bars(j1996);
}
A fresh new look at it and I managed to find the solution. I removed the tag bars in this line:
//apply to everything (enter and update)
bars.style('fill', '#C64567')
So all the attributes are now set on the bar.enter() command, this way d3.js makes the transitions automatically from the last value

d3.js - Bar chart won't display with scale

I updated my code to use the scale method in D3. However, since then my bar chart won't display. What is the cause of this issue?
var dataset = [ ];
for (var i = 0; i < 14; i++) {var newNumber = Math.round(Math.random() * 70);
dataset.push(newNumber);}
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var widthScale = d3.scale.linear()
.domain([0,d3.max(dataset)])
.range([0,w]);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 3)
.attr("y", function (d,i) {return i* 36;})
.attr("width", function(d) {return widthScale;})
.attr("height", h / dataset.length - barPadding)
.attr("fill", function(d) {return "rgb(0, 0, " + (d * 10) + ")";});
A D3 scale is just a function translates values from a given input domain (your data values, 0 to max) to a specified output range (the width of your chart). Thus you have to apply the scale to your data, e.g. widthScale( d ). Right now you are assigning the widthScale function to the width attribute of your rect instead of the output value.
See the working fiddle: http://jsfiddle.net/S92u4/

Categories