I am trying to nest an individual rect inside each svg element created in the g element, but I can't seem to get it to work.
Here is my code + Plnk;
Plunker
var bar = g.selectAll(".barContainer")
.data(thisData.labels)
.enter()
bar
.append("svg")
.attr("class", "barContainer")
bar
.insert('rect')
.attr('width', function(d) {
console.log(d)
return d.value
})
.attr('height', 20)
.attr('x', 0)
.attr('y', 0)
Currently the DOM is displaying the rect and container class on the same level, where as I would like to nest the rect inside each container.
I've tried a few things but can't seem to crack it, I was hoping someone could point me in the right direction?
Thanks
You have a 'g' element which you append the svg to and then you also append the rect to the 'g'. You want to append the rect to the svg element you create. Something like this :
var bar = g.selectAll(".barContainer")
.data(thisData.labels)
.enter()
var barRectSVG = bar
.append("svg")
.attr("class", "barContainer")
barRectSVG
.insert('rect')
.attr('width', function(d) {
console.log(d)
return d.value
})
.attr('height', 20)
.attr('x', 0)
.attr('y', 0)
Updated plnkr : http://plnkr.co/edit/WYbjT7ekjUuopzs0H9Pi?p=preview
Related
In this jsFiddle I combine D3 with interact.js, both working on SVG. There's a group that contains a rect and an image. The group class is resizable and that works fine. The problem is that the rect, when resized, should clip the image (i.e. the image should never be out of the rectangle borders) but it does not. I use a D3 clipPath for that, but it's not working. What is the problem?
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 200);
var g = svg.append('g')
.attr('class', 'resize-me');
var rect = g.append('rect')
.attr('stroke', 'blue')
.attr('x', 0)
.attr('y', 0)
.attr("width", 200)
.attr("height", 200)
.attr('stroke-width', 2)
.attr('stroke', 'white')
.attr('fill', 'grey');
var image = g.append('image')
.attr('x', 0)
.attr('y', 0)
.attr('width', 128)
.attr('height', 128)
.attr("xlink:href", imageUrl);
interact('.resize-me')
.resizable({
edges: { left: true, right: true, bottom: true, top: true }
})
.on('resizemove', function(event) {
var target = event.target;
var rect = target.childNodes[0];
var img = target.childNodes[1];
var x = (parseFloat(target.getAttribute('endx')) || 0)
var y = (parseFloat(target.getAttribute('endy')) || 0)
rect.setAttribute('width', event.rect.width);
rect.setAttribute('height', event.rect.height);
x += event.deltaRect.left
y += event.deltaRect.top
rect.setAttribute('transform', 'translate(' + x + ', ' + y + ')')
rect.setAttribute('endx', x)
rect.setAttribute('endy', y)
// clip image
svg.append('defs')
.append('clipPath')
.attr('id', 'clip2')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', event.rect.width)
.attr('height', event.rect.height);
image.attr("clip-path", "url(#clip2)");
});
Do you want the image to always expand to fill the grey box? https://jsfiddle.net/alexander_L/u607w315/12/ (version 12 for image expanding)
Also, you are appending <def/> tags every time the event fires:
It gets quickly out of hand.
You should either attach the <def/> to some dummy one element array data and use the d3.js update pattern or simpler you could just create the <def/> tag in your source code and update the attribute of that same tag each time.
You can do it once at the start:
var def = svg.append('defs')
.append('clipPath')
.attr('id', 'clip2')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', 200)
.attr('height', 200);
And then use this variable and update during your event:
def.attr('x', 0)
.attr('y', 0)
.attr('width', event.rect.width)
.attr('height', event.rect.height);
Then you avoid this issue.
https://jsfiddle.net/alexander_L/u607w315/11/ (version 11 for image clipping)
Do you want this behaviour:
The image is clipped when the grey box is smaller in one dimension than the image?
UPDATE
Since the OP noticed a bug in the original code which causes the grey box to always snap back to at least the height or width of the image, I tried to also solve this problem.
However, I also noticed some odd behaviour, that the top left corner of the box could not be extended further up or left so I fixed that first: https://jsfiddle.net/alexander_L/u607w315/25/
See the .gif of the new behaviour and the old bug the OP mentioned:
How do I best update a body text element from a SVG tree node mouseover event? When I try the following the text is updated, but the SVG is removed from the display. Here is a the code:
var svg = d3.select('body')
.append('text').text('The Entry Point and M Code: ')
.attr('class', 'centralText')
.attr('x', 10)
.attr('y', 10)
.attr('text-anchor', 'middle')
.append('svg')
here is my event code:
var nodeEnter = node.enter().append('g')
.attr('class', node_class)
.attr('transform', function(d) {
return 'translate(' + source.x0 + ',' + source.y0 + ')'; })
.style('cursor', function(d) {
return (d.children || d._children) ? 'pointer' : '';})
.on('click', click)
.on("mouseover", function(d) {
d3.select('body')
.text('M Code: is this')
There are two separate issues that are coming up. First, your initialization of svg is appending it to a text element, which is a bad idea. Second, to update the text you're replacing the body text and not the text element.
Two changes need to be made. First, change your mouseover function to this:
d3.select('body').select('text')
.text('M Code: is this');
This would normally fix it, but the second problem is that the svg is appended to the text element so it will still be erased. To fix this, change your declaration to the following:
d3.select('body')
.append('text').text('The Entry Point and M Code: ')
.attr('class', 'centralText')
.attr('x', 10)
.attr('y', 10)
.attr('text-anchor', 'middle');
var svg = d3.select('body').append('svg');
I am new to D3.js and am trying to build rectangles that represent all nodes from an XML file. So far so good but I want interactivity with each of the rectangles I draw and to be able to capture the nodes that have been touched for further processing. So let's say I click on a rectangle, I can make it react by doing an onclick event (like increasing the font size) but I can't seem to retrieve some of the info. I'd like to create an array with the text of each item that was clicked on.
Here's the code for one instance of the rectangle.
d3.select("#chart")
.append("svg")
.attr("width", 600)
.attr("height", 2000)
.style("background", "#93A1A1")
d3.select("svg")
.append("rect").attr("x", 50)
.attr("y", 25)
.attr("height", 20)
.attr("width", 200)
.attr("title", "resourceDef")
.style("fill", "#CB4B19")
d3.select("svg")
.append("text")
.attr("x", 55)
.attr("y", 37)
.attr("font-size", 11)
.attr("font-family", "sans-serif")
.text("resourceDef")
.on("mouseover", function(d) {
tempText = this.text;
alert(tempText);
d3.select(this)
.attr("font-size", 15)})
.on("mouseout", function(d) {
d3.select(this)
.attr("font-size", 11)})
I can grab style info by using but not the title and I can't find that info anywhere. Thanks for your help, I know it's a long question with probably a simple answer.
You can attach a mouse over event on the rectangle DOM by doing something like this:
d3.select("svg")
.append("rect").attr("x", 50)
.attr("y", 25)
.attr("height", 20)
.attr("width", 200)
.attr("title", "resourceDef")
.style("fill", "#CB4B19")
.on("click", function (d) {
var t = d3.select(this).attr("title");
//pushing the title into the array.
clickedTitles.push(t);
console.log(t);
});
You can get the attribute of a DOM(in your case tite) by doing something like this:
.on("click", function (d) {
var t = d3.select(this).attr("title");
clickedTitles.push(t);
console.log(t)
})
You can store the clicked rectangles title in an array like this:
//create an array
var clickedTitles = [];
//in your click function push the title into the array
clickedTitles.push(t);
//use the clickedTitles where ever you need in the code
Full code is here.
I want to display the legend (caption?) of some data.
For each part of the legend, I append a rect and a p to a parent division.
The problem is that rect are not showing up.
Here is my code:
var groups = {{ groups|safe }};
groups.forEach(function(group, i) {
var div = d3.select("#collapse-legend");
div.append("rect")
.attr("width", 17)
.attr("height", 17)
.style("fill-opacity", 1)
.style("fill", function (d) { return c(i); });
div.append("p").text(group)
});
Now, when I check the content of my web page, I get both rect and p, but rect:
is not showing up
seems to have a width of 0 (showing its area with firefox)
Is there some mistake in my code? Are there better ways to achieve this? I am very new to javascript and d3.js so please be indulgent ^^
Update
So this is what I ended with.
HTML:
<div ...>
<svg id="legend-svg"></svg>
</div>
JavaScript:
// set height of svg
d3.select("#legend-svg").attr("height", 18*(groups.length+1));
// for each group, append rect then text
groups.forEach(function(group, i) {
d3.select("#legend-svg").append("rect")
.attr("y", i*20)
.attr("width", 18)
.attr("height", 18)
.style("fill-opacity", 1)
.style("fill", function (d) { return c(i); });
d3.select("#legend-svg").append("text")
.attr("x", 25)
.attr("y", i*20+15)
.text(group);
});
SVG elements like <rect> cannot be direct children of html <div> elements. You must put them inside an <svg> container element.
SET X + Y values for rect :
.attr("x", 50)
.attr("y", 50)
Here is my code, i am adding a series of rectangle - http://jsfiddle.net/nikunj2512/74qrC/6/
i want to add a slider like a image slider which has left and right arrow buttons for navigation or something similar, so that user can navigate through the rectangle boxes.
I don't know how to achieve this thing.
This is the d3.js code which is creating the rectangle boxes:
var width = 4000,
height = 200,
margin = 2,
nRect = 20,
rectWidth = (width - (nRect - 1) * margin) / nRect,
svg = d3.select('#chart').append('svg')
.attr('width', width)
.attr('height', height);
var data = d3.range(nRect),
posScale = d3.scale.linear()
.domain(d3.extent(data))
.range([0, width - rectWidth]);
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', posScale)
.attr('width', rectWidth)
.attr('height', height);
This was fun... So, I think I suggested this approach in your other question, but basically I apply a clip path to the rectangles:
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", clipWidth)
.attr("height", clipHeight);
var g = svg.append("g");
g.selectAll("rect").data(data)
.enter()
.append('rect')
.attr("class", "area").attr("clip-path", "url(#clip)")
.attr('x', xScale)
.attr('width', rectWidth)
.attr('height', rectHeight)
.style('fill', d3.scale.category20());
...and then I skew the domain up or down and update the plot using a transition with a delay of 500ms:
var update = function(){
g.selectAll("rect")
.transition().duration(500)
.attr('x', xScale);
};
d3.select("#left").on("click", function(){
xScale.domain([xScale.domain()[0] - 1, xScale.domain()[1] - 1]);
update();
});
and voila, a working jsFiddle: http://jsfiddle.net/reblace/74qrC/9/
Now, the trick is to load images in those boxes, but you should be able to do some googling for how to apply images to svg elements and there's plenty of resources out there to help you do that.