Can I use images as the background rectangles for d3 treemaps? - d3.js

Is it possible to make a treemap in d3 with the background of each rectangle be an image? I am looking for something similar to what was done in Silverlight here, but for d3. If it is possible, are there any recommended tutorials that walk through the process of connecting the background to an image?

Yes, there are several ways of using images in SVGs. You probably want to define the image as a pattern and then use it to fill the rectangle. For more information, see e.g. this question (the procedure is the same regardless of the element you want to fill).
In D3 code, it would look something like this (simplified).
svg.append("defs")
.append("pattern")
.attr("id", "bg")
.append("image")
.attr("xlink:href", "image.jpg");
svg.append("rect")
.attr("fill", "url(#bg)");

Its important to note, that the image needs to have width, height attributes
chart.append("defs")
.append('pattern')
.attr('id', 'locked2')
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', 4)
.attr('height', 4)
.append("image")
.attr("xlink:href", "locked.png")
.attr('width', 4)
.attr('height', 4);

Using patterns to add an image in a rectangle can make your visualisation quite slow.
You can do something like that instead, this is the code I used for my rectangular nodes into a force layout, I wanted to put rectangles filled by an image as nodes:
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node");
node.append("rect")
.attr("width", 80)
.attr("height", 120)
.attr("fill", 'none')
.attr("stroke", function (d) {
return colors(d.importance);
});
node.append("image")
.attr("xlink:href", function (d) { return d.cover;})
.attr("x", 2)
.attr("width", 76)
.attr("height", 120)
.on('dblclick', showInfo);

Related

Why a horizontal line is not working correctly in stack bar chart

I have a stacked bar chart. You can see the fiddle here.
I have drawn a line that is actually a horizontal line leveling the current stack of a bar. Below is the code.
.on('mouseenter', function (actual, i) {
const y = yScale(actual.y + actual.y0);
debugger;
line = svg.append('line')
.attr('id', 'limit')
.attr('x1', 0)
.attr('y1', y)
.attr('x2', width)
.attr('y2', y);
And the output is,
Here, you can see that, for the monthly data, the line is correct. But for the quarterly data, the line is a bit above the actual position. And for the yearly data, the line is not showing.
What is the problem here?
And how can I show a tooltip along with the line?
Looking at the fiddle, it seems that the scale you are using to render the rectangles is not yScale, but actually just y
Changing the following fragment:
const y = yScale(actual.y + actual.y0)
line = svg.append('line')
.attr('id', 'limit')
.attr('x1', 0)
.attr('y1', y)
.attr('x2', width)
.attr('y2', y);
To:
const limitY = y(actual.y + actual.y0);
line = svg.append('line')
.attr('id', 'limit')
.attr('x1', 0)
.attr('y1', limitY)
.attr('x2', width)
.attr('y2', limitY);
Adjusts the position of the line to match the rectangles, because it is now using the same scale that the bars and the axis are using.
Regarding the tooltip, I see there is a rectangle you want to append:
line.append("rect")
.attr("width", "10px")
.attr("height", "10px")
.style("fill", "red");
However, a <line> can not have a <rect> element inside. What you actually want is to add the <rect> to the <svg>:
svg.append("rect")
.attr('id', 'myId') // Also give it an Id for clean up
.attr("width", "10px")
.attr("height", "10px")
.attr("y", limitY) // The limitY is available to position the tooltip under the line
.style("fill", "red");
Don't forget to remove it in the mouseout event, as you are doing with <line#limit>:
.on("mouseout", function() {
svg.selectAll('#limit').remove();
// clean the rectangle on mouseout:
svg.selectAll('#myId').remove();
})
You can use the same premise of the above <rect> in a <g> element to create a full tooltip with text and background, but coding it is outside of the scope of this answer. I hope the above explanations can give you a direction.
Here is a fiddle with the changes.

Putting tspan inside rectangle D3JS SVG

i'm working in projet of data visualization using D3JS.
i have to put some information inside a rectangle, and i did that using wrap function to split the content, no i'm looking to put each line (tspan) inside a rectangle. and i don't know how to do that, any help is appreciated for me.enter image description here
the small rectangles will contain informations. does any one have an example how to that. thakns
there!
Actually, what you need to do is set right position for groups.
var data = [0,1,2,3,4]; // example data
var selection = svg.selectAll('g').data(data).enter();
// set position for rects and texts using transform-translate
var groups = selection.append('g')
.attr('transform', function(d, i) { return 'translate(0,' + i * 35 + ')' });
groups.append('rect')
.attr('width', 100)
.attr('height', 30)
.attr('fill', 'white')
.attr('stroke', 'black'); // all rects
groups.append('text')
.text((d) => d)
.attr('font-size', '10px')
.attr('y', 15); // all texts
And an additional link for you, maybe it would be helpful.

Shading inside an arc D3

I am working on an SVG, and came across the issue of trying to share different colors on separate sides of an arc. I have created this example to help go through this problem I'm having:
const svg = d3.select('#chart')
.attr("viewBox", "0, 0, " + 50 + ", " + 47 + "")
// clip to cut off circle
svg.append("defs").append("clipPath")
.attr("id", "cut-off")
.append("rect")
.attr("width", 44)
.attr("height", 23.75)
.attr("x", 25)
.attr("y", 42.25)
.attr("transform", "translate(" + -22 + "," + -28.5 + ")");
svg.append("circle")
.attr("cx", 25)
.attr("cy", 4.75)
.attr("r", 23.75)
.attr("fill", "orange")
.attr("opacity", 0.25)
.attr("stroke", 'black')
.attr("stroke-width", 0.25)
.attr("clip-path", "url(#cut-off)");
svg.append("rect")
.attr("x", 3)
.attr("y", 0)
.attr("width", 44)
.attr("height", 14)
.attr("fill", 'blue')
.attr("opacity", 0.2)
// here's the one
svg.append("rect")
.attr("x", 0)
.attr("y", 14)
.attr("width", 17)
.attr("height", 20)
.attr("fill", 'green')
.attr("opacity", 0.2)
#chart {
width: 500px;
height: 470px;
border: 2px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg
version="1.1"
baseProfile="full"
xmlns="http://www.w3.org/2000/svg"
id="chart"></svg>
The rectangle that is currently shaded green I would like to instead shade two different colors. I would like the area inside of the arc (the area currently overlapping orange and green) to be set to one color, and the area outside of the arc (only green) to be set to another color. I think this probably requires using 2 rects, and cutting off the rects based on the circle, but I'm not sure how to do this.
Note: The way the arc was drawn was by drawing a circle, and then clipping a rectangle over the parts of the circle I don't want shown. Given that I'm trying to fill color differently based on what side of the circle's line the fill color is on, I'm not sure if this is the best way to draw the arc.
Thanks in advance for help with this!!
You can use cloneNode on your green rect and set an clip-path attribute on the clonedNode. Point the clip-path url to a circle with the same d attribute as your original circle defined under defs tag.
If you provide a fiddle, I can perhaps help.
I used this link here - How to calculate the SVG Path for an arc (of a circle) - to solve the problem. Anybody trying to draw circles (or parts of a circle) as a path using svg arc should check this link.

Add custom html to nodes in d3 js tree

I'm trying to build a tree like d3 js tree. I need to add a div and 2 or 3 buttons in that div for each node of the tree. Clicking on that node button should show a popup.
I'm trying for this kind of functionality
There are other plugins similar to this. But i need this in d3 js tree as its navigation and animations are smooth.
I have done this:
Use base example from D3 tree web page.
Added more SVG elements in the nodes
Added a "popup" menu when you click the node text (Add, Remove, Edit, Move) to perform this simple operations on the node.
In my experience, it is better to use SVG elements instead of a DIV (You can display buttons as images or shapes, and text as svg:text.
Here is some code:
function clickLabel(d) {
// this removes the popup if it was displayed on another node beforehand
// is=2 identifies markers...
d3.selectAll("[marker='2']").remove();
// Every node has an ID, and I add shapes to it
d3.select("[id_node='" + d.id + "']")
.append("image")
.attr("marker", 2)
.attr("xlink:href", "/Content/delete_item.png")
.attr("x", 0)
.attr("y", -50)
.attr("height", 32)
.attr("width", 32)
.on("click", removeItem);
d3.select("[id_node='" + d.id + "']")
.append("image")
.attr("marker", 2)
.attr("xlink:href", "/Content/edit.png")
.attr("x", -50)
.attr("y", -30)
.attr("height", 32)
.attr("width", 32)
.on("click", editItem);
d3.select("[id_node='" + d.id + "']")
.append("image")
.attr("marker", 2)
.attr("xlink:href", "/Content/add_item.png")
.attr("x", 20)
.attr("y", 10)
.attr("height", 32)
.attr("width", 32)
.on("click", addItem);
d3.select("[id_node='" + d.id + "']")
.append("image")
.attr("marker", 2)
.attr("xlink:href", "/Content/next_item.png")
.attr("x", -30)
.attr("y", 20)
.attr("height", 32)
.attr("width", 32)
.on("click", moveItem);
// Stop events or else it gets de-selected
event.stopPropagation();
}
Hope this helps!
You can use .append("svg:foreignObject") to add custom html to nodes in d3 js tree, for example jsfiddle example

How to properly get text width to center labels above graph bars?

I currently have a graph that has associated bar values displaying above each bar, but I'm having difficulty centering the value labels, due to not being able to fetch each text element's width.
This is how my graph is drawing at the moment:
All I need to do is to subtract half of each text element's width, but I can't seem to do so with the following Coffeescript:
#Drawing value labels
svg.selectAll("rect")
.data(data)
.enter()
.append("text")
.text((d)-> d.Total)
.attr("width", x.rangeBand())
.attr("x", (d)->
textWidth = d3.selectAll("text").attr("width")
x(d.Year) + (x.rangeBand() / 2) - (textWidth / 2)
)
.attr("y", (d)-> y(d.Total) - 5)
.attr("font-size", "10px")
.attr("font-family", "sans-serif")
#Drawing bars
svg.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", (d)-> x(d.Year))
.attr("width", x.rangeBand())
.attr("y", (d)-> y(d.Total))
.attr("height", (d)-> height - y(d.Total))
Is there a way that I can access each text element's width attribute to set a value to offset?
A perhaps simpler way is to use a text-anchor of middle with x set to the left side of the bar plus half the width of the bar:
svg.selectAll(".bar-label")
.data(data)
.enter().append("text")
.text((d)-> d.Total)
.attr("class", "bar-label")
.attr("text-anchor", "middle")
.attr("x", (d)-> x(d.Year) + x.rangeBand()/2)
.attr("y", (d)-> y(d.Total) - 5)
You can get the bounding box of the text and use those values to apply a transform to center it. An example for getting the bounding box is here. The code would look something like
.text(function(d) { return d.Total; })
.attr("x", function(d) {
return x(d.Year) + (x.rangeBand() / 2) - (this.getBBox().width / 2);
}
...

Resources