Unable to get scaleLinear() to populate values into SVG Rectangle's width - d3.js

Please look at this index.html with simple d3.js scaling:
<body>
<script>
var canvasWidth = 750;
var canvasHeight = 600;
// Setup scales
d3.json("tt.json",
function(data)
{
var widthScale = d3.scaleLinear()
.domain([0, 3])
.range(0, canvasWidth);
var colorScale = d3.scaleLinear()
.domain([0, 10])
.range(["red", "blue"]);
var canvas = d3.select("body")
.append("svg")
.attr("width", canvasWidth)
.attr("height", canvasHeight)
canvas.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width", function(d) { return widthScale(d.t_count); })
.attr("height", 30)
.attr("y", function(d, i) { return i * 50; })
.attr("fill", "blue")
canvas.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("fill", "black")
.attr("y", function(d, i) { return (i * 50 + 22); })
.text(function(d) { return d.ttext; })
}) // of function(data)
</script>
</body>
The .json file on the server looks like this:
[{"ttext":"Architect","t_count":"1"},
{"ttext":"Entertainment","t_count":"2"},
{"ttext":"Food","t_count":"2"},
{"ttext":"Gujarati","t_count":"1"},
{"ttext":"Laundry","t_count":"1"},
{"ttext":"Milk","t_count":"2"},
{"ttext":"Rajasthani","t_count":"1"}]
The output is just this:
(nothing but the ttext attribute from the json file.)
A hardcoded factor i.e., return (d.t_count * 30); does plot the rectangle. But when the scaling is introduced, the text is all that is output.
Here's the SVG DOM that is generated (strangely missing width in the rect):
Struggling with this for a day almost. Can anyone review and please help?

In a scale, both domain and range have to be arrays.
So, change to this:
var widthScale = d3.scaleLinear()
.domain([0, 3])
.range([0, canvasWidth]);//range as an array

Related

Bar height taking up entire height of viewBox responsive SVG

I'm still fairly new to D3.js. Right now, I'm working on making a responsive bar chart. I'm using the viewbox to make it responsive in a div and using the DOM to set the height and width to the offsetWidth/Height of the div.
But the bars seem to be incorrect in height when set to the yScale.
Here's a screenshot of how they end up appearing:
I'm also being thrown this error in the console:
"d3.min.js:2 Error: attribute height: A negative value is not valid. ("-78.69462281846592")"
I believe the error has to do with this part of the code where I set the height attr to the height subtracted by the yScale data value:
var bars = svg.selectAll('.bar')
.data(data)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', function(d) {
return xScale(d.Year);
})
.attr('y', function(d) {
return yScale(d.Total);
})
.attr('width', xScale.bandwidth())
.attr('height', function(d) {
return height - yScale(d.Total);
});
Here's the entire function that's drawing the bar chart:
<style>
#barchart {
width: 75vw;
height: 50vh;
}
</style>
function draw(data) {
var width = document.getElementById('barchart').offsetWidth,
height = document.getElementById('barchart').offsetHeight;
var svg = d3.select('#barchart')
.append('svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('viewBox', '0 0 ' + width + ' ' + height)
.append('g');
var xScale = d3.scaleBand()
.range([0, width])
.padding(0.4);
var yScale = d3.scaleLinear()
.range([height, 0]);
xScale.domain(data.map(function(d) {
return d.Year;
}));
yScale.domain(data.map(function(d) {
return d.Total;
}));
var x_xaxis = svg.append('g')
.attr('transform', 'translate(' + 0 + ',' + 370 + ')')
.call(d3.axisBottom(xScale));
var y_axis = svg.append('g')
.attr('transform', 'translate(' + 20 + ',' + 0 + ')')
.call(d3.axisLeft(yScale)
.ticks(100));
var bars = svg.selectAll('.bar')
.data(data)
.enter()
.append('rect')
.attr('class', 'bar')
.attr('x', function(d) {
return xScale(d.Year);
})
.attr('y', function(d) {
return yScale(d.Total);
})
.attr('width', xScale.bandwidth())
.attr('height', function(d) {
return height - yScale(d.Total);
});
}
Without seeing your data it's tough to give a full answer but I'm certain the problem is that your yScale domain is being set incorrectly. Its linear so it needs an array of [minValue, maxValue], you are giving it an array of all data values. Try setting it with extent:
yScale.domain(d3.extent(data, function(d) {
return d.Total;
}));
Looking through this you want to start at zero, so:
yScale.domain([0, d3.max(data, function(d) {
return d.Total;
})]);
Also, you need to define the margins a bit more concretely. Take a look at these modifications.

Adding axes eats up my data

I'm following the D3 tutorial but adding the axis makes half of my data disappear and I don't understand why. I thought that maybe the axis is taking up the space that's meant for the data so I added an extra 10px to the transform property, but it doesn't make any difference.
var GIST = "https://gist.githubusercontent.com/charisseysabel/f8f48fbf11b8a1b0d62cbe2d6bdc2aa6/raw/2ead1537adb822fbd59a666afd5334d525480a13/nano-2017.tsv"
var width = 1000,
height = 550,
margin = {top: 20, right: 30, bottom: 30, left: 4};
var y = d3.scaleLinear()
.range([height, 0]);
var yScale = d3.scaleLinear()
.range([height, 0]);
var xScale = d3.scaleLinear()
.range([0, width]);
var xAxis = d3.axisLeft(yScale);
var yAxis = d3.axisBottom(xScale);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", height);
chart.append("g")
.attr("transform", "translate(10, 0)")
.call(xAxis);
chart.append("g")
.attr("transform", "translate(0, 540)")
.call(yAxis);
d3.tsv(GIST, type, function(error, data) {
y.domain([0, d3.max(data, function(d) { return d.value; })]);
var barWidth = width / data.length;
var bar = chart.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(" + ((i * barWidth) + 10) + ",0)"; }
);
bar.append("rect")
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.attr("width", barWidth - 1);
bar.append("text")
.attr("x", (barWidth / 2) - 2)
.attr("y", function(d) { return y(d.value) + 3; })
.attr("dy", ".75em")
.text(function(d) { return d.value; });
});
function type(d) {
d.value = +d.value;
return d;
}
When you do this...
var bar = chart.selectAll("g").etc...
... you're selecting group elements that already exist in the SVG, which are the axes, and binding your data to them.
There are two easy solutions:
Move your code that creates the axes to the bottom of the d3.tsv, that is, after you have appended the bars.
Select something that doesn't exist, like
var bar = chart.selectAll(null).etc. To read more about the logic behind selectAll(null), have a look at my answer here.

D3.js pie chart to show the percentage of sales in each quarter

Using d3.js I want to make a chart showing each item as pie chart displaying the quarterly sale of tickets for different programs
here is the sample picture of chart I want to make,each segment in the chart represents ticket for a program and its percentage of sales in each quarter. Now using this link enter link description here
I have made a graph but which is not exactly the one I needed.Is there any charts available in d3.js to show a graph as I mentioned in the picture or we need to customize it to get a graph like that.
Is there any charts available in d3.js to show a graph as I mentioned
in the picture or we need to customize it to get a graph like that?
No there isn't a ready made solution, d3 as the comment on the question notes is a collection of methods for manipulating the DOM, this allows a great deal of flexibility in creating custom visualizations (users aren't as limited as with many ready-made solutions that only allow defined modifications). Consequently, yes, you can make a chart like that in d3 taking elements and ideas from both scatter plot and pie chart implementations with d3 to make your chart.
This answer shows one approach that could be used in creating such a graph. Ideally it can provide ideas in crafting your own visualization that meets your need.
First, you need a mechanism to make variable sized pie charts and to place them - arguably this is the hardest part (after that you just have a scatter plot that's easier to manipulate). This requires some thought as to data structure, I've used a structure such as:
var data = [
{x:100,y:100,radius:20,slices:[1,5]},
{x:150,y:180,radius:10,slices:[1,2,3,4]},
You could add other properties as needed, all that this does is specify an x and y coordinate for the pie chart center, a radius for the pie chart, and the values of the wedges for each pie chart.
With that, you can append a group element (g) to your svg, one for each pie chart (or item in the data array) using a standard enter cycle in d3, positioning the groups as we go:
var pies = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });
Because the data array used to append the wedges themselves will only include the wedge values, we can save the radius property as a property of the group and access that when appending the wedges:
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
.attr("fill",function(d,i){
return color[i];
});
A basic example might look like this:
var data = [
{x:100,y:100,radius:20,slices:[1,5]},
{x:150,y:180,radius:10,slices:[1,2,3,4]},
{x:180,y:130,radius:30,slices:[1,2,3,4,5,6,7]},
{x:50,y:50,radius:15,slices:[5,3]},
{x:50,y:180,radius:40,slices:[6,3]}
]
var width = 500;
var height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d; });
var color = d3.schemeCategory10;
// Append a group for each pie chart, it will store the radius of each pie as a property
var pies = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });
// draw each pie wedge, using the slices property of the data bound to the parent g
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
.attr("fill",function(d,i){
return color[i];
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
If you want to set each circle to have it's own color scheme, a few options might be available. If every pie has only two colors, you could assign a fill to the parent group and use the wedge increment to set transparency, creating lighter wedges such as in your image:
var data = [
{x:100,y:100,radius:20,slices:[1,5]},
{x:150,y:180,radius:10,slices:[1,2]},
{x:180,y:130,radius:30,slices:[1,7]},
{x:50,y:50,radius:15,slices:[5,3]}
]
var width = 500;
var height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d; });
var color = ["steelblue","orange","pink","crimson"]
// Append a group for each pie chart, it will store the radius of each pie as a property
var pies = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("fill",function(d,i) { return color[i] })
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });
// draw each pie wedge, using the slices property of the data bound to the parent g
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
.attr("opacity",function(d,i){
return 1-i*0.2;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Other options are available, such as storing a local variable, storing the color as a property as we did for radius, or modifying our data structure to include a color for each wedge:
var data = [
{x:100,y:100,radius:20,
slices:[{value:1,color:"steelblue"},{value:5,color:"lightblue"} ]},
{x:150,y:180,radius:10,
slices:[{value:1,color:"crimson"},{value:2,color:"pink"}]},
{x:180,y:130,radius:30,
slices:[{value:1,color:"lawngreen"},{value:7,color:"darkgreen"}]}
]
var width = 500;
var height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d.value; });
// Append a group for each pie chart, it will store the radius of each pie as a property
var pies = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });
// draw each pie wedge, using the slices property of the data bound to the parent g
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
// remember that d3.pie creates it's own data array, thus using d.data.property:
.attr("fill",function(d){ return d.data.color; })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Now we can adapt and implement characteristics of a scatter plot such as scales and axes. This would be the same for any other scatter plot essentially, we would scale the max and min (or a defined range) for the x and y scales, and add the axes. Altogether, that might look something like:
var data = [
{x:100,y:100,radius:10,slices:[1,5]},
{x:150,y:180,radius:10,slices:[1,2,3,4]},
{x:180,y:110,radius:30,slices:[1,2,3,4,5,6,7]},
{x:50,y:100,radius:15,slices:[5,3]},
{x:50,y:180,radius:40,slices:[6,3]}
]
var width = 500;
var height = 300;
var margin = {left:30,right:10,top:30,bottom:30}
var xScale = d3.scaleLinear()
.range([0,width-margin.left-margin.right])
.domain([0,d3.max(data,function(d) { return d.x + 20 }) ]);
var yScale = d3.scaleLinear()
.range([height-margin.top-margin.bottom,0])
.domain([0,d3.max(data,function(d) { return d.y + 20}) ]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.attr("transform", "translate("+margin.left+","+margin.top+")")
var xAxis = d3.axisBottom(xScale);
g.append("g")
.attr("transform", "translate(0,"+(height-margin.bottom-margin.top)+")")
.call(xAxis);
var yAxis = d3.axisLeft(yScale);
g.append("g")
.call(yAxis);
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d; });
var color = d3.schemeCategory10;
var pies = g.selectAll(null)
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("transform",function(d) { return "translate("+xScale(d.x)+","+yScale(d.y)+")"; });
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
.attr("fill",function(d,i){
return color[i];
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Adding grid lines, legends, mouse over functionality, and other features should be relatively straightforward now - look at scatterplot examples with d3 to see how these and other features might be implemented, modifying a scatterplot of cirlces is about the same as modifying a scatterplot of pie charts.
From the sample provided by #Andrew Reid I have made it , the sample code for reference is posted here
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
</head>
<body>
<script>
var data = [
{x: 170, y: 160, radius: 20, slices: [3, 4]},
{x: 180, y: 40, radius: 30, slices: [ 6, 7]},
{x: 50, y: 80, radius: 20, slices: [5, 3]},
{x: 50, y: 180, radius: 40, slices: [6, 3]}
]
var width = 500;
var height = 300;
var margin = {left: 30, right: 10, top: 30, bottom: 30}
var xScale = d3.scaleLinear()
.range([0, width - margin.left - margin.right])
.domain([0, d3.max(data, function (d) {
return d.x + 20
})]);
var yScale = d3.scaleLinear()
.range([height - margin.top - margin.bottom, 0])
.domain([0, d3.max(data, function (d) {
return d.y + 20
})]);
xMid=d3.max(xScale.domain())/2;
yMid=d3.max(yScale.domain())/2;
console.log(xMid,yMid)
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
var xAxis = d3.axisBottom(xScale);
g.append("g")
.attr("transform", "translate(0," + (height - margin.bottom - margin.top) + ")")
.call(xAxis);
var yAxis = d3.axisLeft(yScale);
g.append("g")
.call(yAxis);
var lineX= g.append("line")
.attr("x1", 0)
.attr("x2", 500)
.attr("y1", yMid+20)
.attr("y2", yMid+20)
.attr("stroke-width", 1)
.attr("stroke", "black")
.attr("stroke-dasharray", "7,7");
var liney= g.append("line")
.attr("x1", xMid+130)
.attr("x2", xMid+130)
.attr("y1", -10)
.attr("y2", 245)
.attr("stroke-width", 1)
.attr("stroke", "black")
.attr("stroke-dasharray", "7,7");
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function (d) {
return d;
});
var colors = d3.schemeCategory20;
var color = ["steelblue","orange","green","red"]
var pies = g.selectAll(null)
.data(data)
.enter()
.append("g")
.property("radius", function (d) {
return d.radius;
})
.attr("transform", function (d) {
return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")";
})
.attr("fill", function (d, i) {
return color[i];
});
pies.selectAll()
.data(function (d) {
return pie(d.slices);
})
.enter()
.append("path")
.attr("d", function (d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d)
})
.attr("opacity",function(d,i){ return 1-i*0.7; });
</script>
</body>

D3 - Using JSON data to create multiple bars

I'm learning D3 and have JSON data. I want to build multiple bars from this JSON data to draw graph like this already built in excel. I can draw one line of Pax_Rev on SVG but I'm not sure how to add other lines from the data. When I do console.log(dataset.length), it shows me 0 which means only one item in dataset which is expected.
<script>
var dataset = [{"Pax_Rev": 1000, "Crg_Rev": 500,
"Fixed_Costs": 800, "Variable_Costs": 200}];
var width = 500;
var height = 1000;
var barPadding = 1;
var svg = d3.select("body")
.append("svg")
.append("g")
.attr("width", width)
.attr("height", height)
.attr("class", "svg")
svg3.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", function(d){
return height - d.Pax_Rev // How to add other items like Crg_Rev etc?
})
.attr("width", 20)
.attr("height", function(d){
return d.Pax_Rev
});
</script>
As I explained in your previous question, this is the expected behaviour. Since you have just one object in your data array, D3 "enter" selection will create just one element.
If you look at the API, you'll see that selection.data():
Joins the specified array of data with the selected elements[...] The specified data is an array of arbitrary values (e.g., numbers or objects). (emphases mine)
Therefore, we have to convert that huge object in several objects. This is one of several possible approaches:
var dataset = [{
"Pax_Rev": 1000,
"Crg_Rev": 500,
"Fixed_Costs": 800,
"Variable_Costs": 200
}];
var data = [];
for (var key in dataset[0]) {
data.push({
category: key,
value: dataset[0][key]
})
}
console.log(data)
Now, we have a data array, with several objects, one for each bar, and we can create our bar chart.
Here is a demo:
var dataset = [{
"Pax_Rev": 1000,
"Crg_Rev": 500,
"Fixed_Costs": 800,
"Variable_Costs": 200
}];
var data = [];
for (var key in dataset[0]) {
data.push({
category: key,
value: dataset[0][key]
})
}
var svg = d3.select("svg");
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.value
})])
.range([120, 10]);
var xScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.category
}))
.range([40, 280])
.padding(0.2);
var rects = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("fill", "steelblue")
.attr("x", function(d) {
return xScale(d.category)
})
.attr("width", xScale.bandwidth())
.attr("y", function(d) {
return yScale(d.value)
})
.attr("height", function(d) {
return 120 - yScale(d.value)
});
var yAxis = d3.axisLeft(yScale);
var xAxis = d3.axisBottom(xScale);
svg.append("g").attr("transform", "translate(40,0)").call(yAxis);
svg.append("g").attr("transform", "translate(0,120)").call(xAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

D3: How to dynamically refresh a graph by changing the data file source?

How do I update the data on demand by changing the file d3 accesses? With a click, for example, it would read data from a new data file and add more nodes to the graph like AJAX.
I use d3.tsv to read in data.tsv, one of many files of the same format.
I made a simple graph to illustrate my question. Thanks in advance.
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 400,
height = 200;
var x = d3.scale.linear().range([0, width]),
y = d3.scale.linear().range([height, 0]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
d3.tsv("data.tsv", function(error, data) {
if (error) console.warn(error);
x.domain(d3.extent(data, function(q) {return q.xCoord;}));
y.domain(d3.extent(data, function(q) {return q.yCoord;}));
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("r", 10)
.attr("cx", function(d) { return x(d.xCoord); })
.attr("cy", function(d) { return y(d.yCoord); })
});
</script>
update the graph
Try this.
var width = 400,
height = 200;
var x = d3.scale.linear().range([0, width]),
y = d3.scale.linear().range([height, 0]);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var dataSource = 'data.tsv',
dataSource2 = 'data2.tsv';
function updateChart(sourcefile) {
d3.tsv(sourcefile, function(error, data) {
if (error) console.warn(error);
x.domain(d3.extent(data, function(q) {return q.xCoord;}));
y.domain(d3.extent(data, function(q) {return q.yCoord;}));
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("r", 10)
.attr("cx", function(d) { return x(d.xCoord); })
.attr("cy", function(d) { return y(d.yCoord); })
});
}
updateChart(dataSource);
//here is where you change the data..
d3.select(#button).on("click", function() {
updateChart(dataSource2)
})

Resources