I'm aiming to add a border around the circular avatars I create in my D3 visualization. I create these circular avatars by using clip-path. When I add a border to my node it is a square border around the whole node, rather than circular like I'm aiming for (and I understand why, because this node is rectangular). Here is what that currently looks like:
I'm struggling in getting this border to instead appear around the circular, clipped, image.
Here is the code where I currently set the (rectangular) border:
var nodeEnter = node.enter().append('svg:g')
.attr('class', 'node')
.attr('cursor', 'pointer')
.attr('style', function(d) {
var color;
if (d.strength > 2) {
color = 'blue';
} else {
color = 'red';
}
return 'outline: thick solid ' + color + ';';
})
.attr('transform', function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.call(force.drag);
...and this is how I declare my clip-path:
var clipPath = defs.append('clipPath')
.attr('id', 'clip-circle')
.append('circle')
.attr('r', 25);
My full example can be found here:
http://blockbuilder.org/MattDionis/5f966a5230079d9eb9f4
How would I go about setting this as a circular border around the image rather than rectangular around the entire node?
You could just add a circle of slightly larger radius (then your clip-path) into your node:
nodeEnter.append('circle')
.attr('r',30)
.style('fill', function(d) {
return d.strength > 2 ? 'blue' : 'red'
});
var images = nodeEnter.append('svg:image')
.attr('xlink:href', function(d) {
return d.avatarUrl;
})
.attr('x', function(d) {
return -25;
})
.attr('y', function(d) {
return -25;
})
.attr('height', 50)
.attr('width', 50)
.attr('clip-path', 'url(#clip-circle)');
Updated code.
Related
I'm working on a heatmap chart in D3 and I can't figure out how to add the text on mouseover. I am not sure how to proceed. If you could give me some clues, I would appreciate it. In the following snippet you can find the code. both the working and the non-working codeblocks. Thanks!
console.log(d3)
let screenWidth = 800
let screenHeight = 400
//load data
d3.csv('./datos/locations.csv').then(function(data){
let filtered = []
for(let item of data) {
if(item.location === "location one") {
filtered.push(item)
}
}
build(filtered)
})
//Create canvas
function createSVG() {
let container = d3.select('#container')
svg = container.append('svg')
.attr('id', 'canvas')
.attr('width', screenWidth)
.attr('height', screenHeight)
}
//Create chart
function build(data) {
let rectWidth = screenWidth / 24
let rectHeight = screenHeight / 7
let rects = svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', function(d,i) {
return (parseInt(d.hour) - 1) * rectWidth})
.attr('y', function(d,i){
return (parseInt(d.day) - 1) * rectHeight})
.attr('height', rectHeight)
.attr('width', rectWidth)
.style('fill', 'black')
.style('stroke', 'white')
.on('mouseover', function(d,i) {
let rects = d3.select(this)
.append('text')
.attr('x')
.attr('y')
.style('font-weight', 500)
.style('font-family', 'Arial')
.style('fill', 'red')
.text(function (d,i) {return d.value})})
}
function main() {
createSVG()
build()
}
main()
```
You can append a <div> with position: absolute to body and position it on mousemove event. Change the opacity to update its display or hidden.
var div = d3.select('body').append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
...
.on('mouseover', function(d) {
div.transition()
.duration(200)
.style('opacity', .9);
div.html('<h3>' + d.status + '</h3>' + '<p>' + timeFormat(new Date(d.date)) + ' at ' + monthDayFormat(new Date(d.date)) + '</p>')
.style('left', (d3.event.pageX) + 'px')
.style('top', (d3.event.pageY - 28) + 'px');
})
https://jsfiddle.net/z9ucLqu2/
<text> nodes cannot be children of <rect>s, only just as <line>s or <circle>s can't. They are figure nodes and are not meant to have children. Append the tooltip to the SVG or a <g> instead.
This means that you cannot access d.value through the function (d,i) {return d.value}) anymore, but you can get it because you have access to d from .on('mouseover', function(d,i) {, just remove everything but d.value.
If you use x and y from the <rect>, what is going to happen is that the <text> element covers the <rect>, catches the mouse event and triggers a mouseout immediately on the <rect>. Since you'll probably want to remove the tooltip on mouseout, you'll get the text node flickering on and off. Either move the text to the right by at least rectWidth or use d3.event to get the mouse coordinates of the event and position it a little down and to the right, using something like .attr('x', d3.event.clientX + 10) to move it right.
possible duplicates: D3 text on mouseover
D3 donut chart text centering
but unsure what is happening in respect to my problem and quite stuck.
Im building a data visualization with many layouts. I am currently trying to make a piechart with text centered in the middle and whenever someone mouse overs the arcs, it displays the text of it in the center.
function GUP_PieRender() {
var svg = d3.select(targetDOMelement).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(dataset))
.enter().append("g")
.attr("class", "arc")
.on("mouseover", function(d) { d3.select("text").text(d.data.ResearchArea)}); //Problem area
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.ResearchArea); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle");
}
What it is doing instead is displaying the text in another D3 barchart layout that has text. So I must be calling the mouseover event too early and appending it to the last text element in that?
Can I get a remedy?
Thanks.
The problem here (inside your "mouseover" handler) is simply this:
d3.select("text")
When you do this, D3 selects the first text element it finds in that page. You don't want that, obviously.
Therefore, just do:
g.select("text")
That way, you only select text elements inside your g selection.
Alternatively, you can also do:
d3.select(this).select("text")
Since this in this context is the group element.
Here is a demo (I'm trying to imitate your code):
var data = ["foo", "bar", "baz"];
data.forEach(function(d) {
render(d);
})
function render(data) {
var svg = d3.select("body")
.append("svg")
.attr("width", 100)
.attr("height", 100);
var g = svg.selectAll(null)
.data([data])
.enter()
.append("g")
.on("mouseover", function(d) {
g.select("text").text(String)
})
.on("mouseout", function(d) {
g.select("text").text(null)
})
g.append("circle")
.attr("cx", 50)
.attr("cy", 50)
.attr("r", 20);
g.append("text")
.attr("x", 25)
.attr("y", 20);
}
svg {
background-color: tan;
border: 1px solid darkgray;
margin-right: 10px;
}
circle {
fill: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
I am using CodeFlower built upon D3.js. I want to show an image as a background instead of arbitrary colors, and i successfully did that using SVG Patterns.
DEMO FIDDLE
// Enter any new nodes
this.node.enter().append('defs')
.append('pattern')
.attr('id', function(d) { return (d.id+"-icon-img");}) // just create a unique id (id comes from the json)
.attr('patternUnits', 'userSpaceOnUse')
.attr('width', 80)
.attr('height', 80)
.append("svg:image")
.attr("xlink:xlink:href", function(d) { return (d.icon);}) // "icon" is my image url. It comes from json too. The double xlink:xlink is a necessary hack (first "xlink:" is lost...).
.attr("x", 0)
.attr("y", 0)
.attr("height", 80)
.attr("width", 80)
.attr("preserveAspectRatio", "xMinYMin slice");
this.node.enter().append('svg:circle')
.attr("class", "node CodeFlowerNode")
.classed('directory', function(d) { return (d._children || d.children) ? 1 : 0; })
.attr("r", function(d) { return d.children ? 3.5 : Math.pow(d.size, 2/5) || 1; })
.style("fill", function(d) { return ("url(#"+d.id+"-icon-img)");})
/* .style("fill", function color(d) {
return "hsl(" + parseInt(360 / total * d.id, 10) + ",90%,70%)";
})*/
.call(this.force.drag)
.on("click", this.click.bind(this))
.on("mouseover", this.mouseover.bind(this))
.on("mouseout", this.mouseout.bind(this));
The problem i am seeing is the image is not centrally aligned in the circle it is kind of tile layout composed of 4 images.
How can i make its position center and covering the circle nicely.
DEMO FIDDLE
You need to change the way you define your pattern. You should define it with respect to the element it is being applied to. So leave patternUnits at the default of objectBoundingBox, and set the width and height to 1.
Then you need to also set the patternContentUnits to objectBoundingBox also, and give the <image> the same size (width and height of 1).
this.node.enter().append('defs')
.append('pattern')
.attr('id', function(d) { return (d.id+"-icon");})
.attr('width', 1)
.attr('height', 1)
.attr('patternContentUnits', 'objectBoundingBox')
.append("svg:image")
.attr("xlink:xlink:href", function(d) { return (d.icon);})
.attr("height", 1)
.attr("width", 1)
.attr("preserveAspectRatio", "xMinYMin slice");
Demo fiddle here
I am making a d3 graph and trying to put a border around my rect elements. The rect elements are appended to a cell and the text elements are appended to the same cell. Thus if I change the stroke in the rect I lose all the text for some reason, and if I change the stroke in the cell the borders and fonts change too.
This is a portion of my code for drawing the graph.
this.svg = d3.select("#body").append("div")
.attr("class", "chart")
.style("position", "relative")
.style("width", (this.w +this.marginTree.left+this.marginTree.right) + "px")
.style("height", (this.h + this.marginTree.top + this.marginTree.bottom) + "px")
.style("left", this.marginTree.left +"px")
.style("top", this.marginTree.top + "px")
.append("svg:svg")
.attr("width", this.w)
.attr("height", this.h)
.append("svg:g")
.attr("transform", "translate(.5,.5)");
this.node = this.root = this.nestedJson;
var nodes = this.treemap.nodes(this.root)
.filter(function(d) { return !d.children; });
this.tip = d3.tip()
.attr('class', 'd3-tip')
.html(function(d) {
return "<span style='color:white'>" + (d.name+",\n "+d.size) + "</span>";
})
this.svg.call(this.tip);
var cell = this.svg.selectAll("g")
.data(nodes)
.enter().append("svg:g")
.attr("class", "cell")
.call(this.position)
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", function(d) { return this.zoom(this.node == d.parent ? this.root : d.parent); })
.style("border",'black');
var borderPath = this.svg.append("rect")
.attr("x", this.marginTree.left)
.attr("y", this.marginTree.top)
.attr("height", this.h - this.marginTree.top - this.marginTree.bottom )
.attr("width", this.w - this.marginTree.left - this.marginTree.right)
.style("stroke", 'darkgrey')
.style("fill", "none")
.style("stroke-width", '3px');
cell.append("svg:rect")
.attr("id", function(d,i) { return "rect-" + (i+1); })
.attr("class","highlighting2")
.attr("title", function(d) {return (d.name+", "+d.size);})
.attr("data-original-title", function(d) {return (d.name+",\n "+d.size);})
.attr("width", function(d) { return d.dx - 1; })
.attr("height", function(d) { return d.dy ; })
.on('mouseover', this.tip.show)
.on('mouseout', this.tip.hide)
.style("fill", function(d) {return coloring(d.color);});
cell.append("svg:text")
.attr("class", "treemap-text nameTexts")
.attr("id", function(d,i) { return "name-" + (i+1); })
.attr("x", cellMargin)
.attr("y", function(d) { return parseInt($('.treemap-text').css('font-size'))+cellMargin; })
.text(function(d) {return (d.name);});
cell.append("svg:text")
.attr("class", "treemap-text sizeTexts")
.attr("id", function(d,i) { return "size-" + (i+1); })
.attr("x", cellMargin)
.attr("y", function(d) { return 2*parseInt($('.treemap-text').css('font-size'))+2*cellMargin; })
.text(function(d) {return (d.size);});
Additionally, I thought about creating lines and drawing four lines around each rect element, but was wondering if there is an easier way. Thanks.
I didn't check fully through your source, it would also be helpful to work with jsbin, codepen, jsfiddle or other online platforms to show your problem.
Actually I think you just have misinterpreted the SVG presentation attributes and their styling with CSS. For SVG elements only SVG presentation attributes are valid in CSS. This means there is no border property as you have it in your code. Also note that for <text> elements the fill color is the font-body color and the stroke is the outline of the font. Consider that stroke and fill are inherited down to child element which means that if you have a rectangle with a stroke style and some containing text element that they will have the stroke applied as outline and you'd need to override the styles there.
Hope you can solve your issue.
Cheers
Gion
Hello I am working with d3 diagonal diagram and would like to add a gradient to path which links my circles...
I am generating my tree with:
var width = 800,
height = 700;
element.html('');
var color = d3.interpolateLab("#008000", "#c83a22");
var scale = d3.scale.linear().domain([0, 100]).range(["red", "green"]);
var cluster = d3.layout.cluster()
.size([height, width - 160]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var svg = d3.select('#tab-manageAccess').append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(40,0)');
/*svg.append("linearGradient")
.attr("id", "line-gradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0).attr("y1", y(0))
.attr("x2", 0).attr("y2", y(1000))
.selectAll("stop")
.data([
{offset: "0%", color: "red"},
{offset: "40%", color: "red"},
{offset: "40%", color: "black"},
{offset: "62%", color: "black"},
{offset: "62%", color: "lawngreen"},
{offset: "100%", color: "lawngreen"}
])
.enter().append("stop")
.attr("offset", function(d) { return d.offset; })
.attr("stop-color", function(d) { return d.color; });*/
var nodes = cluster.nodes(scope.accessTree),
links = cluster.links(nodes);
var link = svg.selectAll('.link')
.data(links)
.enter().append('path')
.attr('class', 'link')
.attr('d', diagonal);
var node = svg.selectAll('.node')
.data(nodes)
.enter().append('g')
.attr('class', 'node')
.attr('transform', function(d) { return 'translate(' + d.y + ',' + d.x + ')'; });
node.append('circle')
.attr('r', 4.5);
node.append('text')
.attr('dx', function(d) { return d.children ? -8 : 8; })
.attr('dy', 3)
.style('text-anchor', function(d) { return d.children ? 'end' : 'start'; })
.style('font-weight', 'bold')
.attr('fill', function (d) {
var color = '#4D7B88';
if (d.depth === 0) {
color = '#7F3762';
} else if(d.depth === 1) {
color = '#83913D';
}
return color;
})
.text(function(d) { return d.name; });
d3.select(self.frameElement).style('height', height + 'px');
I found this example: https://gist.github.com/mbostock/4163057 co I created variable color with d3.interpolateLab("#008000", "#c83a22"); and then added .style("fill", function(d) { return color(d.t); })
.style("stroke", function(d) { return color(d.t); }) to path element but it doesn't work :( can anyone help me?
The aspect of Mike Bostock's code that you're missing is where he divides the path up into hundreds of different sub-paths and sets the color on each one separately. Go to the live version at http://bl.ocks.org/mbostock/4163057 and check the DOM to see what's really going on.
Why does he do that? Because, while you can set the stroke of an SVG line or path to a gradient, you can't tell it to make the gradient follow the slope or curve of that line. The angle of the gradient is defined when the gradient is created, based on either:
the rectangular bounding box for the element that uses it
(if gradientUnits is set to ObjectBoundingBox), or
the user coordinate system where the object is drawn
(if gradientUnits is set to userSpaceOnUse).
The way you have it set up (in your commented out code) basically creates a hidden gradient background over the entire image, and then lets it show through wherever you draw your lines. Clearly not what you wanted.
Hence, Mike's complex function and the hundreds of sub-paths it creates. Probably not what you want, either, especially if you want the graph to be interactive.
For simple lines, there is another way to get gradients to line up correctly from start to finish of your line.
I've got a very simple example with plain SVG (no D3) up here: http://codepen.io/AmeliaBR/pen/rFtGs
In short, you have to define your line to go in the direction that matches up with the gradient, and then use transforms (scale/rotate/translate) to actually position the line where you want it.
How tricky that would be to implement in D3 depends on how complex your layout is. If you were just using simple lines, I think this would work:
calculate the length of the line and its slope using simple geometry from the (x1,y1) and (x2,y2) values,
draw the line from (0,0) to (0,length) (assuming a vertical gradient),
add a transform attribute of translate(x1,y1) rotate(slope)
With paths, you'd need to know what type of path you're dealing with and use regular expressions to parse and edit the path's d attribute. Very messy.
Maybe just try line markers for start and end?