I am trying to append text to the svg by below code for that I the following code but it's not working.
var svg = d3.select("#chart");
xScale.domain(data.map(function(d){return d.key;}))
yScale.domain([0,d3.max(data,function(d){return d.doc_count;})])
svg.append("g")
.attr("class","axis")
.attr("transform","translate("+margin.left+","+height+")")
.call(xAxis);
svg.append("g")
.attr("class","axis")
.attr("transform","translate("+margin.left+",0)")
.call(yAxis);
svg.attr("width",width + margin.left + margin.right)
.attr("height",height + margin.top + margin.bottom)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x",function(d){return margin.left + xScale(d.key)})
.attr("y",function(d){return yScale(d.doc_count)})
.attr("width", xScale.rangeBand())
.attr("height", function(d) { return height - yScale(d.doc_count); })
.attr("fill","teal");
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d){return d.doc_count;})
At the end when I see the dom there are no text element tags.
You are calling call(xAxis) and call(yAxis) which will create <text> elements so when you say d3.selectAll("text") it selects those elements created by call(xAxis) and call(yAxis).
so suppose your data count is 5 then it has already 5 <text> elements and it won't append a new one.
Update your code to
svg.append("g")
.attr("class","axis")
.attr("transform","translate("+margin.left+","+height+")")
.call(xAxis);
svg.append("g")
.attr("class","axis")
.attr("transform","translate("+margin.left+",0)")
.call(yAxis);
after below code
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d){return d.doc_count;})
The issue in your code is the same as Soham mentioned in his answer. When you use text element selector, it will return all elements inside your SVG including the axis texts which got created automatically by d3 while creating axes.
So the best possible selection would be to use a different selector for the additional text labels.
svg.selectAll("text.label") //Or simply (".label")
.data(data)
.enter()
.append("text")
.attr("class","label")
.text(function(d){return d.doc_count;})
You will also have to specify the text position attributes in this case.
If you would like to position text elements relative to the rect elements, you can create group elements for grouping rect and text elements and set the position attribute (transform) of group elements.
Related
I am very new to d3js, css, html and trying to practice different examples of d3js. I am trying to add a d3js graph in a slideshow instead of adding it to a body of webpage. I am kind of stuck on how to do this. How do i place a graph in slideshow ? For your reference below is my attempted code-
<div class="mySlides fade">
<div class="numbertext">2 / 3</div>
Something goes here in slide 2
</div>
</div>
<div class="mySlides fade">
<div class="numbertext">3 / 4</div>
<h1 style="font-size:400%;"><u>Data</u></h1>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
/*
* value accessor - returns the value to encode for a given data object.
* scale - maps value to a visual display encoding, such as a pixel position.
* map function - maps from data value to display value
* axis - sets up axis
*/
// setup x
var xValue = function(d) { return d.Contributions;}, // data -> value
xScale = d3.scale.linear().range([0, width]), // value -> display
xMap = function(d) { return xScale(xValue(d));}, // data -> display
xAxis = d3.svg.axis().scale(xScale).orient("bottom");
// setup y
var yValue = function(d) { return d.Deaths;}, // data -> value
yScale = d3.scale.linear().range([height, 0]), // value -> display
yMap = function(d) { return yScale(yValue(d));}, // data -> display
yAxis = d3.svg.axis().scale(yScale).orient("left");
// setup fill color
var cValue = function(d) { return d.State;},
color = d3.scale.category10();
// add the graph canvas to the body of the webpage
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 + ")");
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// load data
d3.csv("data.csv", function(error, data) {
// change string (from CSV) into number format
data.forEach(function(d) {
d.Contributions = +d.Contributions;
d.Deaths = +d.Deaths;
// console.log(d);
});
// // don't want dots overlapping axis, so add in buffer to data domain
xScale.domain([d3.min(data, xValue)-1, d3.max(data, xValue)+1]);
yScale.domain([d3.min(data, yValue)-1, d3.max(data, yValue)+1]);
// // x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("Conntributions");
// // y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Deaths");
// draw dots
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", xMap)
.attr("cy", yMap)
.style("fill", function(d) { return color(cValue(d));})
.on("mouseover", function(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d["State"] + "<br/> (" + xValue(d)
+ ", " + yValue(d) + ")")
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
// draw legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate("+ (i * 20 + 137) + ", 6)";
});
// draw legend colored rectangles
var boxSize = 17;
legend.append("rect")
.attr("x", 0)
.attr("width", boxSize)
.attr("height", boxSize)
.style("fill", color);
// draw legend text
legend.append("text")
.attr("x", -8.2)
.attr("y", 19)
.attr("dy", ".35em")
.style("text-anchor", "end")
.attr("transform","rotate(-43)")
.text(function(d) { return d;})
});
</script>
</div>
This code gets me a graph in all pages of slideshow which i don't want but instead i would like to add a graph in page 3 of my slideshow.
This is an interesting question: normally, the answer here would be just "select the div you want by ID or any other CSS selector that suits you" (or, since that this has been answered many many times, just a comment and a vote to close). The basis for that answer is that this...
var svg = d3.select("body").append("svg")
... will append the SVG at the end of the body. So far, nothing new or difficult.
But why did I said that this is an interesting question?
Because of the funny way (if you don't mind me saying so) you tried to select the div: you thought that D3 would create the chart inside that div just by putting the respective script inside it.
Of course putting the script inside the container div is not the way of doing this. But just for the sake of curiosity, there is a way of doing what you thought you were doing (again, selecting the element that contains the script): by using document.currentScript, which:
Returns the element whose script is currently being processed.
So, all we need in your situation is:
var container = document.currentScript.parentNode;
var svg = d3.select(container)
.append("svg")
//etc...
Which appends the SVG in the <div> (or any other containing element) that contains the <script>.
Here is a basic demo:
<script src="https://d3js.org/d3.v5.min.js"></script>
<div>
<h3>I am div 1</h3>
</div>
<div>
<h3>I am div 2, I have a chart</h3>
<script>
var container = document.currentScript.parentNode;
var svg = d3.select(container)
.append("svg");
var data = d3.range(10).map(d => Math.random() * 150);
var scale = d3.scaleBand()
.domain(data)
.range([0, 300])
.padding(0.4);
var bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.style("fill", "steelblue")
.attr("x", d => scale(d))
.attr("width", scale.bandwidth())
.attr("height", d => 150 - d)
.attr("y", Number)
</script>
</div>
<div>
<h3>I am div 3</h3>
</div>
<div>
<h3>I am div 4</h3>
</div>
Note that document.CurrentScript doesn't work on IE (if you care for IE, just give the div an ID and select it).
I'm using D3 v4 and can't seem to get multiple items to append to a node. In the code below I'm trying to get text to appear with the image as part of my force simulation. Both the image and text need to move together around the screen. It works perfectly if I only append either the image or the text but I can't get it to group both. When I run this it just shows 1 node in the corner.
this.node = this.d3Graph.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(Nodes)
.enter()
.append("svg:image")
.attr("xlink:href", 'https://seeklogo.com/images/T/twitter-2012-negative-logo-5C6C1F1521-seeklogo.com.png')
.attr("height", 50)
.attr("width", 50)
.append("text")
.attr("x", 20)
.attr("y", 20)
.attr("fill", "black")
.text("test text");
this.force.on('tick', this.tickActions);
tickActions() {
this.node
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
this.force
.restart()
}
You cannot append a <text> element to an <image> element. You have to append the <text> to the <g>.
The easiest solution is breaking your selection:
this.node = this.d3Graph.selectAll(null)
.data(Nodes)
.enter()
.append("g")
.attr("class", "nodes");
this.node.append("svg:image")
.attr("xlink:href", 'https://seeklogo.com/images/T/twitter-2012-negative-logo-5C6C1F1521-seeklogo.com.png')
.attr("height", 50)
.attr("width", 50);
this.node.append("text")
.attr("x", 20)
.attr("y", 20)
.attr("fill", "black")
.text("test text");
Here we use the data to create <g> elements in the enter selection. Then, to each <g> element, we append an <image> and a <text> as children.
In this axis label example, which uses D3 v4, it adds the x axis and the text label as separate nodes under svg.
// Add the x Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// text label for the x axis
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height + margin.top + 20) + ")")
.style("text-anchor", "middle")
.text("Date");
When I chain the code above (hence moving the text element under the x axis group):
// Add the x Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.append("text")
.style("text-anchor", "middle")
.text("Date");
Then my axis title is not visible any more (see screenshot below). I can still find my text element in DOM, under the x axis group, but it's not there in the rendered HTML.
I want to know:
Is it by design that D3 wants me to add axis and its label separately (i.e., not chaining)?
Why is my text element not visible after I move it under the x axis group?
D3 axis label has to be added separately?
No, it doesn't. You can chain, that's not the problem. The problem here is the fill of the text element.
As you can see in the screenshot you linked, the container group has "none" as fill. Since the text inherits the parent's attributes/styles, you'll have to change its fill from "none" to any color you want:
var svg = d3.select("svg");
var xScale = d3.scaleLinear()
.domain([1, 10])
.range([10,390]);
var xAxis = d3.axisBottom(xScale);
var gX = svg.append("g")
.attr('class', 'axis')
.attr("transform","translate(0,40)")
.call(xAxis)
.append("text")
.attr("fill", "black")//set the fill here
.attr("transform","translate(120, 40)")
.text("Hello World!!!");
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="400" height="80"></svg>
PS: this problem wouldn't happen in D3 v3.x.
I'm trying to get these 2 divs called bar1 and bar2 to appear next to one another. I tried messing a little bit with the svg and div ids. Not sure how to get these two graphs to be side-by-side.
Here is bar1:
var margin = {top: 20, right: 20, bottom: 30, left: 40};
var width = 360 - margin.left - margin.right,
height = 330 - margin.top - margin.bottom;
var chart1 = d3.select("#bar1")
.append("svg")
.attr('id', 'bar1svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
var svg = d3.select("#bar1svg");
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var g = chart1.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv ( "data/housingdata.csv", function(d) {
d.median_housing_prices = +d.median_housing_prices;
return d;
}, function(error, data) {
if (error) throw error;
x.domain(data.map(function(d) { return d.neighborhoods; }));
y.domain([0, d3.max(data, function(d) { return d.median_housing_prices; })]);
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(20, 's'))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Frequency");
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.neighborhoods); })
.attr("y", function(d) { return y(d.median_housing_prices); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.median_housing_prices); });
});
Here is bar 2:
var chart2 = d3.select("#bar2")
.append("svg")
.attr('id', 'bar2svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
var g2 = chart2.append("g2")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("data/housingdata2.csv", function(d) {
d.median_housing_prices = +d.median_housing_prices;
return d;
}, function(error, data) {
if (error) throw error;
x.domain(data.map(function(d) { return d.neighborhoods; }));
y.domain([0, d3.max(data, function(d) { return d.median_housing_prices; })]);
g2.append("g2")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g2.append("g2")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(10, 's'))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Frequency");
g2.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.neighborhoods); })
.attr("y", function(d) { return y(d.median_housing_prices); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.median_housing_prices); });
});
There is a lot of d3 code there, obscuring the fact that you don't have a d3 or even an SVG problem, but a standard HTML / CSS layout issue.
Browsers try to position adajacent <svg> (and <img>) elements side by side, but then switch to a vertical layout if they judge the elements together as "too wide" for the current width. In CSS layout lingo, they have display: inline by default. <div> are block elements, however, so lay out vertically unless that is changed by CSS. The trick is subverting this, and insisting on the side by side layout, like so:
Note the second <svg> is clipped by the window border, because you insisted on side by side layout, come what may.
All of the approaches to get this are HTML / CSS fixes. E.g.:
Put the SVGs in adjacent table cells
Use the float property to make the SVGs float left or right.
Add a container <div> and absolutely position the SVGs therein.
Add a container <div> and use text wrapping control to keep the SVGs side by side.
Probably a half-dozen others.
Using a containing <div> with CSS property white-space: nowrap and then setting your SVG-continaing <div> elements to display: inline is an easy solution that nicely structures your HTML, yet is simple, reliable, and maintanable in ways the table, float, and absolute positioning approaches aren't. The quick and dirty formulation in your HTML follows. (It's neater to define the styling in your CSS file.)
<div style="white-space: nowrap;">
<div id="bar1" style="display: inline"></div>
<div id="bar2" style="display: inline"></div>
</div>
Here is a running demo of the idea.
The disadvantage to this "always go side by side!" command is, well,
you may be successful. Then the elements are side by side always, even
if there isn't enough room for them to be fully displayed. If you don't want to be so always and forever, try a float: left style on #bar1, with very little
margins on your <div> and <svg>. In the Fiddle, you can change the window width to see this dynamc layout decision in action.
I'v just started playing with d3js and find it strange that I have to create multiple selectors for each element I want to link to the background data structure for example separate selectors such as one for overlay text and one for rectangles to make an annotated bar graph.
svg.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr('y',function(d,i){return i*10;})
.attr('height',10)
.attr('width',function(d){return d.interestingValue})
.fill('#00ff00');
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr('y',function(d,i){return i*10;})
.fill('#0000ff')
.text(function(d){return d.interestingValue});
Is there a more convenient way of combining these into a single selection and enter() chain that creates both the rects and the text elements?
Use a G (group) element. Use a single data-join to create the G elements, and then append your rect and text element. For example:
var g = svg.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d) { return "translate(0," + i * 10 + ")"; });
g.append("rect")
.attr("height", 10)
.attr("width", function(d) { return d.interestingValue; });
g.append("text")
.text(function(d) { return d.interestingValue; });