This question already has answers here:
D3 Appending Text to a SVG Rectangle
(2 answers)
Display text on rect using D3.js
(2 answers)
SVG: text inside rect
(5 answers)
Closed 2 years ago.
I am currently trying to get D3 running in Jupyter Notebook, kind of following this guide: https://www.stefaanlippens.net/jupyter-custom-d3-visualization.html
Using the following code i wanted to add numbers to the bars in the bar chart:
%%javascript
(function(element) {
require(['d3'], function(d3) {
var data = [1, 2, 4, 8, 16, 8, 4, 2, 1]
var svg = d3.select(element.get(0)).append('svg')
.attr('width', 400)
.attr('height', 200);
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('width', function(d) {return d*10})
.attr('height', 24)
.attr('x', 0)
.attr('y', function(d,i) {return i*30})
.style('fill', 'darkgreen')
svg.selectAll('rect')
.append('text')
.text('mynumberhere')
.attr('color', 'FF0000')
})
})(element);
Currently only the bar chart is displayed, but no numbers are displayed. Using the HTML inspector though, i can see that inside the element is a element. Although it is not diplayed. Any ideas why that could be?
It's not possible to append a text to a rect element, they're not meant to have children! You can choose to use g-groups instead, and put both the rect and the text inside:
(function(element) {
var data = [1, 2, 4, 8, 16, 8, 4, 2, 1]
var svg = d3.select(element).append('svg')
.attr('width', 400)
.attr('height', 200);
var g = svg.selectAll('g')
.data(data)
.enter()
.append('g');
g.append('rect')
.attr('width', function(d) {
return d * 10
})
.attr('height', 24)
.attr('x', 0)
.attr('y', function(d, i) {
return i * 30
})
.style('fill', 'darkgreen')
g
.append('text')
.text(function(d) { return d; })
.attr('color', 'FF0000')
.attr('dy', 16)
.attr('dx', 3)
.attr('x', function(d) {
return d * 10
})
.attr('y', function(d, i) {
return i * 30
})
})(document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Related
On the page I have a number of rectangles with the same class, say class one.
How do I apply a transition to all those rectangles so they move to a new position with a new class (maybe class two), but keeping those old rectangles stationary in the same position?
Could someone please correct me if I have explained it incorrectly?
For example I have these rectangles with class "start"
d3.select("svg")
.selectAll("rect")
.data([10,20,30,40,50])
.enter()
.append("rect")
.attr("class", "start")
.attr("x", d => d)
.attr("y", 1)
.attr("width", 5)
.attr("height", 5);
These rectangles coordinates are (10, 1), (20, 1), (30, 1) ...
Then I move them
d3.selectAll("rect")
.transition()
.attr("y", (d, i) => i + 5 * 10);
They will appear at the new co-ordinates (10, 50), (20, 51), (30, 52) ...
How can I make it so that the original rectangles with class start at (10, 1), (20, 1), (30, 1) ... are still there but have new rectangles at (10, 50), (20, 51), (30, 52) ... with class stop?
As already made clear in your edit, you don't want to apply the transition to the existing elements: you want to clone them and apply the transition to their clones (or clone them before applying the transition to the original ones, which is the same...).
That being said, D3 has a pretty handy method named clone, which:
Inserts clones of the selected elements immediately following the selected elements and returns a selection of the newly added clones.
So, supposing that your selection is named rectangles (advice: always name your selections), instead of doing this...
rectangles.transition()
.attr("class", "stop")
.attr("y", (d, i) => i + 5 * 10);
...clone them first:
rectangles.each(cloneNodes)
.transition()
.attr("class", "stop")
.attr("y", (d, i) => i + 5 * 10);
function cloneNodes() {
d3.select(this).clone(false);
}
Here is the demo:
const svg = d3.select("svg");
const rectangles = d3.select("svg")
.selectAll(null)
.data([10, 20, 30, 40, 50])
.enter()
.append("rect")
.attr("class", "start")
.attr("x", d => d)
.attr("y", 1)
.attr("width", 5)
.attr("height", 5);
rectangles.each(cloneNodes)
.transition()
.attr("class", "stop")
.attr("y", (d, i) => i + 5 * 10);
function cloneNodes() {
d3.select(this).clone(false);
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
There is no need to use each and a function to clone.
rectangles.clone(false)
.transition()
.attr("class", "stop")
.attr("y", (d, i) => i + 5 * 10);
const svg = d3.select("svg");
const rectangles = d3.select("svg")
.selectAll(null)
.data([10, 20, 30, 40, 50])
.enter()
.append("rect")
.attr("class", "start")
.attr("x", d => d)
.attr("y", 1)
.attr("width", 5)
.attr("height", 5);
rectangles.clone(false)
.transition()
.attr("class", "stop")
.attr("y", (d, i) => i + 5 * 10);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
I am attempting to add a simply bar chart to my tooltip; it consists of 2 variables -- men and women. I was hoping someone might be able to help me put this inside of the tooltip instead of appending it to where it is currently being appended. I've given this a particular area to be appended just so that I know that it is, in fact, showing up(which it is), but I don't know how to get it into the tool tip. Any help is much appreciated. Oh, and this needs to be done in d3, which is partial to why I am asking this question -- I saw a similar question that wasn't implemented in pure d3, and I couldn't completely follow what was going on to emulate it in this example.
.on("mouseover", function(d)
{
tip.show(d);
var state = d.properties.name;
var men = d.properties.men;
var women = d.properties.women;
var dataset = [men, women];
var barHeight = 20;
var x = d3.scale.linear()
.domain([0, d3.max(dataset)])
.range([0, width/2]);
var chart = d3.select(".chart")
.attr("width", width)
.attr("height", barHeight * dataset.length);
var bar = chart.selectAll("g")
.data(dataset)
.enter().append("g")
.attr("transform", function(d, i)
{
return "translate(0," + i * barHeight + ")";
});
bar.append("rect")
.attr("width", x)
.attr("height", barHeight - 1);
bar.append("text")
.attr("x", function(d)
{
return x(d)/2+5;
})
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d)
{
return "$" + d;
});
})
Since you didn't shared the whole code to create the chart, this answer will deal with your question's title only:
How to create a chart inside a tooltip?
I'm not a d3.tip() user, since I create my own tooltips. But what you want is not complicated at all: As the tooltips are <div> elements, you can definitely add a SVG inside them.
However, you have to know where to create the SVG. So, in the following demo, I'm creating this d3.tip tooltip:
var tool_tip = d3.tip()
.attr("class", "d3-tip")
.offset([20, 120])
.html("<p>This is a SVG inside a tooltip:</p>
<div id='tipDiv'></div>");
//div ID here --^
The important part here is this: there is a inner <div> inside the d3.tip div, with a given ID (in that case, tipDiv). I'm gonna use that ID to create my SVG inside the tooltip:
selection.on('mouseover', function(d) {
tool_tip.show();
var tipSVG = d3.select("#tipDiv")
//select the div here--^
.append("svg")
//etc...
})
Here is the demo:
var svg = d3.select("body")
.append("svg")
.attr("width", 300)
.attr("height", 300);
var tool_tip = d3.tip()
.attr("class", "d3-tip")
.offset([20, 120])
.html("<p>This is a SVG inside a tooltip:</p><div id='tipDiv'></div>");
svg.call(tool_tip);
var data = [14, 27, 19, 6, 17];
var circles = svg.selectAll("foo")
.data(data)
.enter()
.append("circle");
circles.attr("cx", 50)
.attr("cy", function(d, i) {
return 20 + 50 * i
})
.attr("r", function(d) {
return d
})
.attr("fill", "teal")
.on('mouseover', function(d) {
tool_tip.show();
var tipSVG = d3.select("#tipDiv")
.append("svg")
.attr("width", 200)
.attr("height", 50);
tipSVG.append("rect")
.attr("fill", "steelblue")
.attr("y", 10)
.attr("width", 0)
.attr("height", 30)
.transition()
.duration(1000)
.attr("width", d * 6);
tipSVG.append("text")
.text(d)
.attr("x", 10)
.attr("y", 30)
.transition()
.duration(1000)
.attr("x", 6 + d * 6)
})
.on('mouseout', tool_tip.hide);
.d3-tip {
line-height: 1;
padding: 6px;
background: wheat;
border-radius: 4px solid black;
font-size: 12px;
}
p {
font-family: Helvetica;
}
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js"></script>
<p>Hover over the circles:</p>
So I'm sort of new to Javascript and I am trying to create a histogram using d3.js. I've been following tutorials and examples of previously created histograms in d3 but cannot figure out how to make my rectangles appear.
My histogram currently contains 4 bins with the numbers 1, 2, 3 and 4 in each bin symbolizing a color attribute of each data point in my dataset. When I do console.log(d) in the .attr "x" function it will appear as an a kind of array with 4 different indices, each with the total number of data points in my dataset with that specific color. Now I'm trying to make that "array" into rectangles but my width and height functions aren't correct. If someone could explain what d.dx and d.y do any why they're wrong that would be helpful. I'm using d3.v3.min.js as my script src value
d3.csv("data.csv", function(data) {
var map = data.map(function (i) { return parseInt(i.color); })
var histogram = d3.layout.histogram()
.bins(4)(map)
var canvas = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 500);
var bars = canvas.selectAll(".bar")
.data(histogram)
.enter()
.append("g")
bars.append("rect")
.attr("x", function (d)
{
//console.log(d)
return d.x * 5; })
.attr("y", 0)
.attr("width",function(d) { return d.dx; })
.attr("height", function(d) { d.y; })
.attr("fill", "steelblue");
});
I updated your plunk as follows.
bars.append("rect")
.attr("x", function(d) { return d.x*100; })
.attr("y", 50)
.attr("height", function(d) { return d.y * 10;})
.attr("width", function(d) { return d.dx*50;})
.attr("fill", "steelblue")
.on("mouseout", function()
{
d3.select(this)
.attr("fill", "steelblue");
})
.on("mouseover", function()
{
d3.select(this)
.attr("fill", "orange");
});
Your code seems to work fine, only your elements are overlapping (also, d3 v4 was referenced instead of v3). What I did is:
multiply d.x by 50 to space the elements
multiplied d.dx by 50 to reduce the overlapping
As to your former questions:
d.x corresponds to the extent of a bin, in your case 0.75 (4 ranges make between 1 and 4 make 0.75: 1+(0.75*4)=4)
*d.y corresponds to the 'height' of a bin, i.e. the number of elements.
I have an external JSON file structured like so:
{
data: [
{
0: 'cat',
1: 232,
2: 45
},
{
0: 'dog',
1: 21,
2: 9
},
{
0: 'lion',
1: 32,
2: 5
},
{
0: 'elephant',
1: 9,
2: 4
},
]
}
Using d3.js I want to access the data with key 2 for the height in a bar chart, so I have set this up:
d3.select('#chart').append('svg')
.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('width', barWidth)
.attr('height', function(d) {
return d.data['2'];
});
I can see the SVG canvas but I can't get the rectangles for the bar chart height, any ideas anyone?
Thanks
I changed a few things to make the code function how a default bar chart would. Your main problem was using d.data instead of d. After that, you have to set the x and y coordinates and an alternating color (or a padding with x) so that the rectangles don't overlap each other.
var width = 30;
var padding = 5;
var chartheight = 100;
d3.select('#chart').append('svg')
.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('width', width)
.attr('height', function(d) {
return d[2];
})
.attr('x', function(d, i) {
return i*(width + padding);
})
.attr('y', function(d, i) {
return chartheight - d[2];
});
d3.selectAll('rect')
.style('fill', function(d, i) {
return i % 2 == 0 ? "#0f0" : "#00f";
});
The d variable sent to your function is already your data, and its called for each item in your array:
.attr('height', function(d) {
return d['2'];
});
hello world> I have been battling this problem for a while now. Im trying to create a bar graph that will take an array of objects as data with time being a date object and value being a number.
My Scale looks like this
d3.time.scale.utc()
.domain(d3.extent(data, function (d) { return d.time; }))
.rangeRound([20, this.state.width - 60]).nice(data.length);
My rectangles are being drawn like this, using the same scale
const self = this,
xScale = this.state.xScale,
yScale = this.state.yScale,
barWidth = this.getBarWidth(data.length),
bars = chart.selectAll('rect')
.data(data);
// UPDATE
bars
.transition()
.duration(500)
.style('fill', color)
.attr('x', function(d) {
console.log(xScale(d.time)- (barWidth / 2));
return xScale(d.time) - (barWidth / 2);
})
.attr('width', barWidth)
.attr('y', function(d) { return yScale(d.value); })
.attr('height', function(d) { return self.state.height - yScale(d.value); });
// ENTER
bars
.enter()
.append('rect')
.style('fill', color)
.attr('class', 'bar')
.attr('x', function(d) {
console.log(xScale(d.time) - barWidth);
return xScale(d.time) - barWidth;
})
.attr('width', barWidth + (barWidth / data.length))
.attr('y', function(d) { return yScale(d.value); })
.attr('height', function(d) { return self.state.height - yScale(d.value); });
// EXIT
bars
.exit()
.transition()
.duration(500)
.style('fill', 'red')
.style('opacity', 0)
.remove();
I get the problem below where the ticks have some length and the axis another and the tick marks don't match the bars.
Please friends, help me find a solution to the below problem.
My bar problem