I am displaying different radius circles with different color.
I am trying to place the text(radius value) below each circle but not getting displayed though i could see the text in the browser inspector.
following is the code:
var width=960,height=500;
var margin = {top: 29.5, right: 29.5, bottom: 29.5, left: 59.5};
radiusScale = d3.scale.sqrt().domain([1, 100]).range([10, 39]),
colorScale = d3.scale.category10();
// 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 + ")");
var i =0;
while (i<=50)
{
console.log("i value is " + i + " value of scale " +i+ " is " + radiusScale(i));
var circle = svg.append("g").append("circle")
.attr("id","circle" + i)
.attr("cx", i*12 )
.attr("cy", 20)
.attr("fill",colorScale(i))
.attr("r", radiusScale(i))
.append("text").attr("dx",i*12).text(function(d){return radiusScale(i)});
i=i+10;
}
should i be adding the text in svg instead of circle to display below the corresponding circles.
SVG will not display text appended to circle elements. You append to the g element:
var g = svg.append("g");
g.append("circle")
.attr("id","circle" + i)
.attr("cx", i*12 )
.attr("cy", 20)
.attr("fill",colorScale(i))
.attr("r", radiusScale(i));
g.append("text").attr("dx",i*12).text(function(d){return radiusScale(i)});
Also note that your function(d) in .text() isn't necessary, you can do just
g.append("text").attr("dx",i*12).text(radiusScale(i));
Related
I am working on this project with D3js and I have come across a problem now.
When I have more data, the bars of my barchart will append correctly in the same line as the name but when I fetch less data from my database, the bars will "loose control" and append higher than their name and causing a bad view of my chart.
Here's a picture of what I'll have if I load more data do it.
And here's my second picture of the chart if I load less data.
I don't really understand what I am missing here but I believe is something with the height of the Y-axis and the bars y-position. Can you please help me sort this out?
Here is my code:
var margin = { top: 20, right: 30, bottom: 40, left: 90 },
width = 360 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Parse the Data
var data2 = d3.json("/Events/BarChart/4").then(function (data) {
console.log(data);
// Add X axis
var x = d3.scaleLinear()
.domain([0, 5])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end")
;
// Y axis
var y = d3.scaleBand()
.range([0, height])
.domain(data.map(function (d) { return d.name; }))
svg.append("g")
.call(d3.axisLeft(y))
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", 4)
.attr("y", function (d) { return y(d.name) + 10; })
.attr("width", function (d) { return x(d.value); })
.attr("height", 20)
.attr("fill", function (d) {
if (d.value > 1) {
return "rgb(51, 80, 92)";
}
else if (d.value > 1 && d.value < 4) {
return "rgb(118, 161, 179)"
}
else {
return "rgb(171, 209, 224)";
}
})
})
The issue arises because you manually assign each rectangle a height of 20 pixels, but you give the scale a range of 0 - 240 (the value of height). The scale will divide the range into equal segments (bands), one for each value in its domain. When you have only two values in the domain they will have bands of 120 px each (reduced if there is padding). Nowhere does the scale "know" you have assigned a height of just 20 px for each bar; afterall, you told it to spread values evenly over a range of 0 - 240. These conflicting instructions are why your bars aren't aligned with your axis.
When using d3 scales you will find it much easier if you use the scale for both axis and drawing the data itself (rects/circles/etc): this way they will always be aligned.
The d3 band scale offers a convenient method: scale.bandwidth(), this returns the length/width/height of a band in the scale: at its simplest (without padding) it is the size of the range divided by how many distinct values are in the domain. We can use this value to set bar height:
var margin = { top: 20, right: 30, bottom: 40, left: 90 },
width = 360 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
var data = [
{name: "a", value: 1},
{name: "b", value: 2}
]
// Add X axis
var x = d3.scaleLinear()
.domain([0, 5])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end")
;
// Y axis
var y = d3.scaleBand()
.range([0, height])
.domain(data.map(function (d) { return d.name; }))
svg.append("g")
.call(d3.axisLeft(y))
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", 4)
.attr("y", function (d) { return y(d.name); })
.attr("width", function (d) { return x(d.value); })
.attr("height", y.bandwidth())
.attr("fill", function (d) {
if (d.value > 1) {
return "rgb(51, 80, 92)";
}
else if (d.value > 1 && d.value < 4) {
return "rgb(118, 161, 179)"
}
else {
return "rgb(171, 209, 224)";
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="my_dataviz"></div>
I also noticed that you add 10 pixels to the y value of each bar: this was probably to manually align the bars better with multiple data entries. Generally this will cause problems (unless manually correcting for them): scale(value) and scale.bandwidth() for y/x and height/width respectively produces bars centered on axis ticks. If you want padding (space between the bars), it is simplest to set that using the scale: scale.padding(number) where number is a value between 0 and 1 representing the portion of each segment that is empty:
var margin = { top: 20, right: 30, bottom: 40, left: 90 },
width = 360 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
var data = [
{name: "a", value: 1},
{name: "b", value: 2}
]
// Add X axis
var x = d3.scaleLinear()
.domain([0, 5])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end")
;
// Y axis
var y = d3.scaleBand()
.range([0, height])
.padding(0.1)
.domain(data.map(function (d) { return d.name; }))
svg.append("g")
.call(d3.axisLeft(y))
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", 4)
.attr("y", function (d) { return y(d.name); })
.attr("width", function (d) { return x(d.value); })
.attr("height", y.bandwidth())
.attr("fill", function (d) {
if (d.value > 1) {
return "rgb(51, 80, 92)";
}
else if (d.value > 1 && d.value < 4) {
return "rgb(118, 161, 179)"
}
else {
return "rgb(171, 209, 224)";
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="my_dataviz"></div>
But what if you don't want 120 px wide segments? You want your bars to be always 20-ish pixels, regardless of how many bars you have. Well we can modify the range of the scale to reflect the length of the domain:
var margin = { top: 20, right: 30, bottom: 40, left: 90 },
width = 360 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
var data = [
{name: "a", value: 1},
{name: "b", value: 2}
]
// Add X axis
var x = d3.scaleLinear()
.domain([0, 5])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(0," + data.length*20 + ")")
.call(d3.axisBottom(x))
.selectAll("text")
.attr("transform", "translate(-10,0)rotate(-45)")
.style("text-anchor", "end")
;
// Y axis
var y = d3.scaleBand()
.domain(data.map(function (d) { return d.name; }))
.range([0, data.length*20])
.padding(0.1);
svg.append("g")
.call(d3.axisLeft(y))
//Bars
svg.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", 4)
.attr("y", function (d) { return y(d.name); })
.attr("width", function (d) { return x(d.value); })
.attr("height", y.bandwidth())
.attr("fill", function (d) {
if (d.value > 1) {
return "rgb(51, 80, 92)";
}
else if (d.value > 1 && d.value < 4) {
return "rgb(118, 161, 179)"
}
else {
return "rgb(171, 209, 224)";
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="my_dataviz"></div>
I also updated the transform for the x axis, you could go further an adjust svg height to be better sized as well
I would like to draw few text box and chain it with arrow lines. I use below code to draw the text box few issues there:
text box is black and no text show there.
One box is missing, it should be 5 box but only 4 can be seen.
how can I add a arrow line to connect each other!
test()
function test() {
var data = ["a","b","c","d","e"]
width = 800
height = 600
margin = 10
//var svg = d3.select("svg");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
svg.style("border","5px solid red");
svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var group = svg.selectAll('g')
.data(data).enter()
.append('g')
.attr('transform',function(d,i) {
//console.log(i,d);
return 'translate('+(100*i)+',0)';
});
var box = group.selectAll('rect')
.data(function(d) {
return d;
});
box.enter()
.append("rect")
.attr("width", 30)
.attr("height", 30)
.attr('font-size',2)
.attr("x", function(d, i) {
//console.log(d);
return 60 + 2*d;
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
text box is black and no text shown.
You aren't appending any text. Text also can't be appended to a rectangle, so there is no need to apply font properties to a rectangle. Text can be appended to a g though. So we can use a parent g to hold both rectangle and text. Something like:
group.append("rect")...
group.append("text")...
The boxes are black because you haven't applied a fill. The default fill is black.
One box is missing, it should be 5 box but only 4 can be seen.
This is because when you enter the parent g elements, you select all g elements. This includes the one you've already appended (svg.append("g")). The enter selection is intended to create elements such that every item in the data array is paired with an element in the DOM. Since you already have a g in your selection, the enter selection will only create 4 new ones (representing data array items with indexes 1-4 but not 0).
Instead of selectAll("g") you could specify a class name or, in the event you simply want to enter everything and there isn't a need to ever update a selection: selectAll(null). The latter option will always return an empty selection, which will result in the enter selection containing one element per item in the data array.
Note, that the parent's datum is passed to appended children automagically, there is no need to use the .data method to pass this onward unless you are handling nested data.
Here's a snippet addressing issues in one and two:
test()
function test() {
var data = ["a","b","c","d","e"]
width = 800
height = 600
margin = 10
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.style("border","5px solid red");
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var group = svg.selectAll(null)
.data(data).enter()
.append('g')
.attr('transform',function(d,i) {
return 'translate('+(40*i)+',0)';
});
group
.append("rect")
.attr("width", 30)
.attr("height", 30)
.attr("fill","yellow")
group.append("text")
.text(function(d) { return d; })
.attr("y", 20)
.attr("x", 15)
.attr("text-anchor","middle");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
I also changed svg to refer to the parent g, the one with the margin applied. Before the g with the margin remained unused, along with the margin. I also modified the spacing to keep everything in view.
how can I add a arrow line to connect each other!
This can be done in many ways and really is a separate issue from the others, so I'll only quickly demonstrate one of many options. I'll modify your data structure a bit so that each datum has positional data and then add arrows using SVG markers:
test()
function test() {
var data = [{name:"a"},{name:"b"},{name:"c"},{name:"d"},{name:"e"}]
width = 800
height = 600
margin = 10
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.style("border","5px solid red")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs")
.append("marker")
.attr("id","pointer")
.attr("markerWidth", 10)
.attr("markerHeight", 10)
.attr("orient","auto")
.attr("refY", 5)
.append("path")
.attr("d", "M 0 0 L 10 5 L 0 10 z")
var group = svg.selectAll(null)
.data(data).enter()
.append('g')
.attr('transform',function(d,i) {
d.x = 40*i+15, d.y=30;
return 'translate('+(40*i)+',0)';
});
group
.append("rect")
.attr("width", 30)
.attr("height", 30)
.attr("fill","yellow")
group.append("text")
.text(function(d) { return d.name; })
.attr("y", 20)
.attr("x", 15)
.attr("text-anchor","middle");
links = [
{source: data[0], target: data[1]},
{source: data[0], target: data[2]}
]
svg.selectAll(null)
.data(links)
.enter()
.append("path")
.attr("d", function(d) {
var midX = (d.source.x+d.target.x)/2;
return "M"+d.source.x+" "+d.source.y+"Q"+midX+" "+200+" "+d.target.x+" "+(d.target.y+6);
})
.attr("fill","none")
.attr("stroke","black")
.attr("stroke-width",1)
.attr("marker-end","url(#pointer)");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
I am building my first bar chart using d3.js v5 what I want is that the bars should be aligned properly on the xaxis
I have almost done building the chart but can't figure out the problem
var headingOne;
var headingTwo;
var dataset;
var description;
var frequency;
var barPadding = 20;
document.addEventListener("DOMContentLoaded", function() {
var req = new XMLHttpRequest(); // Next time will use d3.json();
req.open(
"GET",
"https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json",
true
);
req.send();
req.onload = function() {
let json = JSON.parse(req.responseText);
headingOne = json.source_name;
headingTwo = `From ${json.from_date.substring(0,4)} to ${json.to_date.substring(0,4)}`;
dataset = json.data;
descripton = json.description;
d3
.select("body")
.append("h1")
.text(headingOne)
.attr("class", "headings")
.attr("id", "title");
d3
.select("body")
.append("h2")
.text(headingTwo)
.attr("class", "headings");
var margin = { top: 20, right: 20, bottom: 50, left: 40 },
height = 600 - margin.top - margin.bottom,
width = 1100 - margin.left - margin.right;
var minDate = new Date(dataset[0][0]);
var maxDate = new Date(dataset[dataset.length - 1][0]);
var xScale = d3.scaleTime().domain([minDate, maxDate]).range([barPadding, width - barPadding]);
var yScale = d3.scaleLinear().domain([0, d3.max(dataset, d => d[1])]).range([height, barPadding]);
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
var svg = d3
.select("body")
.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 + ")");
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", (d, i) => i * (width / dataset.length))
.attr("data-date", (d) => d[0])
.attr("y", (d) => yScale(d[1]))
.attr("data-gdp", (d) => d[1])
.attr("width", width / dataset.length)
.attr("height", d => height - yScale(d[1]))
svg.append("g").attr("transform", "translate("+barPadding+"," + (height) + ")").attr("id", "x-axis").call(xAxis);
svg.append("g").attr("transform", "translate("+margin.left+", 0)").attr("id", "y-axis").call(yAxis);
};
});
I expect the bars properly aligned on the xaxis.
Now the bars started before the xaxis (start of the xaxis towards left) starting point which is wrong but finished in the right position (end of the xaxis towards right)
the data is exceeding the limit.
I've already read:
https://bl.ocks.org/mbostock/3887235
http://zeroviscosity.com/d3-js-step-by-step/step-1-a-basic-pie-chart
Center align a pie chart on svg
Consider the following:
var dataAsCsv = `Col1,Col2
Type1,123456
Type2,789012
Type3,34567`;
var data = d3.csvParse(dataAsCsv);
var margin = {top: 50, right: 20, bottom: 50, left: 80},
width = 1400 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svgPie = d3.select('body').append('svg')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var gPie = svgPie.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory20b);
var label = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
var path = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.pie()
.value(function(d) { return d.Col2; })
.sort(null);
var arc = gPie.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
arc.append("path")
.attr("d", path)
.attr("fill", function(d) { return color(d.data.Col1); });
arc.append("text")
.attr("transform", function(d) { return "translate(" + label.centroid(d) + ")"; })
.attr("dy", "0.35em")
.text(function(d) { return d.data.Col1; });
<script src="https://d3js.org/d3.v4.js"></script>
I am trying to center the pie chart vertically and horizontally with respect to the entire svg element that it is in. I tried modifying my code to the examples above to no avail.
You just have to translate the parent g element at half width horizontally and at half height vertically:
Instead of:
var gPie = svgPie.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
write:
var gPie = svgPie.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
Check the demo:
var dataAsCsv = `Col1,Col2
Type1,123456
Type2,789012
Type3,34567`;
var data = d3.csvParse(dataAsCsv);
var margin = {top: 50, right: 20, bottom: 50, left: 80},
width = 1400 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svgPie = d3.select('body').append('svg')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var gPie = svgPie.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory20b);
var label = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
var path = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.pie()
.value(function(d) { return d.Col2; })
.sort(null);
var arc = gPie.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
arc.append("path")
.attr("d", path)
.attr("fill", function(d) { return color(d.data.Col1); });
arc.append("text")
.attr("transform", function(d) { return "translate(" + label.centroid(d) + ")"; })
.attr("dy", "0.35em")
.text(function(d) { return d.data.Col1; });
<script src="https://d3js.org/d3.v4.js"></script>
I am planning to have different colors for each element in the .I was just exploring if this is possible.
My code:
var svg = d3.select("#graphid").append("svg")
.style("margin-left","30px")
//.style("background-color","lavender")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var focus = svg.append("g")
.attr("class", "focus")
.attr("id","focusid")
.style("background-color","#F8FCFB")
//.call(zoom)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var fo= d3.select("#focusid").style("background-color","azure");
console.log(fo);
var context = svg.append("g")
.attr("id","contextid")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var contx= d3.select("#contextid").style("background-color","lavender");
............../
..............//
.............///
the groups were not getting the colors set? What am i missing here?
To solve this you need to append a 'rect' to you SVG and fill that as you can't just fill a 'g'. So create a rectangle then append the focus to that rectangle. same with context :)
//create the rectangle to fill here
var rect = svg.append('rect')
.attr('id', 'rect')
.style('fill', 'red')
.attr("height", height)
.attr("width", width);
var focus = svg.append("g")
.attr("class", "focus")
.attr("id","focusid")
//.call(zoom)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Ive done it for focus, leave it up to you to do context :)
Updated fiddle : http://fiddle.jshell.net/zua7L31d/6/
Finished both them off :
Added a new rectangle for the 'context'
var contextRect = svg.append('rect')
.attr('id', 'rect2')
//.style('fill', 'red')
.attr("height", rect2Height)
.attr("width", rect1Width)
.attr('x', 0)
.attr('y', rect1Height)
;
Final fiddle : http://fiddle.jshell.net/zua7L31d/7/
Hope that helped :)