d3 scaling basic shapes - javascript

I'm trying to dynamically scale some shapes based on the data. Here's what I have so far:
svg.selectAll(".point")
.data(data)
.enter()
.append("path")
.attr("class", "point")
.attr("d", d3.svg.symbol().type("triangle-up"))
.attr("transform", function(d) { return "translate(" + x(d.x) + "," +
y(d.y) + ")"; });
Is there another attr that I can add? I tried to use the following but not working:
.attr("transform", "scale(xMap)");
xMap is a value in data scaled to a range.

You can do the translate and the scale in the same anonymous function. Besides that, you have to concatenate the string with the number. The way you're doing...
"scale(xMap)"
... you're passing literally "xMap" to the scale().
Thus, it should be:
.attr("transform", function(d) {
return "translate(" + x(d.x) + "," + y(d.y) + ") scale(" + xMap + ")";
});

Related

Disable D3 zoom on circle PNG background

I am trying to disable the D3 zoom on a particular element. This element happens to be the PNG background to a circle.
Right now this is not working. I have tried to offset the scale parameter in the zoom, but the background PNG still 'grows' with the circle. Here is my jsfiddle.
This is how I try to offset the zoom:
d3.selectAll("#grump_avatar").attr("transform", "scale(" + 1/d3.event.scale + ")");
I know there are similar questions on SO, but please note none of them have received a satisfactory response thus far. Better luck here, hopefully.
Lots of issues with this code:
Matching by id is an exact match.
Your ids are on def attributes, which aren't the objects, you don't want to scale (those would be the circles).
To match multiple objects, you should be using a class on the circles.
You apply the zoom directly to the svg, you should be wrapping everything in a g. SVG handles the events, g is the zoomable "canvas".
Once you apply the zoom correctly you are going to lose your circle placement because you overwrite the transform without reapplying the translate.
You've made no use of d3 data-binding, so you can't persist your data correctly.
All this in mind, here is how I would refactor your code:
var config = {
"avatar_size": 100
}
var body = d3.select("body");
var svg = body.append("svg")
.attr("width", 500)
.attr("height", 500);
var g = svg.append("g");
var defs = svg.append('svg:defs');
data = [{
posx: 100,
posy: 100,
img: "https://cdn0.iconfinder.com/data/icons/flat-round-system/512/android-128.png",
}, {
posx: 200,
posy: 200,
img: "https://cdn1.iconfinder.com/data/icons/social-media-set/24/Reverbnation-128.png"
}, {
posx: 300,
posy: 300,
img: "https://cdn1.iconfinder.com/data/icons/user-pictures/100/male3-128.png"
}];
defs.selectAll("pattern")
.data(data)
.enter()
.append("pattern")
.attr("id", (d, i) => "grump_avatar" + i)
.attr("width", config.avatar_size)
.attr("height", config.avatar_size)
.attr("patternUnits", "userSpaceOnUse")
.append("svg:image")
.attr("xlink:href", (d) => d.img)
.attr("width", config.avatar_size)
.attr("height", config.avatar_size)
.attr("x", 0)
.attr("y", 0);
g.selectAll(".grump_avatar")
.data(data)
.enter()
.append("circle")
.attr("class", "grump_avatar")
.attr("transform", (d) => "translate(" + d.posx + "," + d.posy + ")")
.attr("cx", config.avatar_size / 2)
.attr("cy", config.avatar_size / 2)
.attr("r", config.avatar_size / 2)
.style("fill", "white")
.style("fill", (d, i) => "url(#grump_avatar" + i + ")");
var zoom = d3.behavior.zoom()
.on("zoom", function() {
g.attr('transform', 'translate(' + d3.event.translate + ') scale(' + d3.event.scale + ')');
d3.selectAll(".grump_avatar").attr("transform", (d) => {
return "scale(" + 1 / d3.event.scale + ")" + "translate(" + (d.posx - d3.event.translate[0]) + "," + (d.posy - d3.event.translate[1]) + ")";
});
});
svg.call(zoom);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
EDITS based on comments:
To scale the circles opposite the zoom and position them, the key is:
d3.selectAll("circle")
.attr("transform", function(d){
return 'scale(' + 1 / d3.event.scale + ')'; // inverse of scale for size
})
.attr("cx", function(d){
return d.x * d3.event.scale; // change position based on scale, d.x is the original unscaled position
})
.attr("cy", function(d){
return d.y * d3.event.scale;
});

Calling D3.JS function on 'load' rather than on mouse event

I am trying to make modifications to the D3 sankey example here:
http://bl.ocks.org/d3noob/c2637e28b79fb3bfea13
I want to shift the y-position of each node to a specified location (300px in the example below).
The most straight forward way I can to see to achieve this is to simply repurpose dragmove() to be called after the the SVG elements have been added. I've made changes to d.y in this function to shift to 300px:
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + d.x + "," + (
d.y = 300
) + ")");
sankey.relayout();
link.attr("d", path);
}
This function is called when adding the nodes:
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() {
this.parentNode.appendChild(this); })
.on("drag", dragmove));
At present, it shifts to 300px as expected on the specified mouse event, but I want it to shift on its own after all the SVG elements have been added instead.
Simply using .call() without the mouse event doesn't work.
I've also tried to incorporate the shift in var node instead:
return "translate(" + d.x + "," + 300 + ")"; })
However this leads to a mismatch between the leads and the nodes, and calling sankey.relayout() doesn't seem to make a difference.
Found a solution.
First I removed .call() at the end of var node, as I don't need the drag event:
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
Then I set d.y to the arbitrary location (300):
return "translate(" + d.x + "," + (d.y = 300) + ")"; });
Finally I forced it to re-draw the connecting links immediately after var node.
sankey.relayout();
link.attr("d", path);

Overlapping legend d3.js

I am creating the legends with triangle shapes. One is "Yes", the other one is "No". By running the code below, it generate two triangles but they are overlapping. I am trying to seperate them by using this line of code .attr("y", function(d,i) {return 50+i*40;}) but seems like it doesn't work.
Can anyone tell me how to fix it? Thanks!
Click here! This is an html sreenshot for this part of script
var legendname = ["Yes","No"];
var legend = svg.selectAll(".legend")
.data(legendname)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(" + (w + 150) + "," + (m.t - 30) + ")";
});
legend.append("path")
.attr("d", d3.svg.symbol().type("triangle-up").size(128))
*** .attr("y", function(d,i) {return 50+i*40;})
.style('fill', function(d) {return color(d);});
legend.append("text")
.attr("y", function(d,i) {return 50+i*20;})
.attr("x", 30)
.text(function(d) { return d; })
You will have to update the translate y attribute of groups instead of the paths. And also there is no need for extra calculations for y attributes of texts and paths then.
.attr("transform", function(d, i) {
return "translate(" + (w + 150) + "," + (30+i*40) + ")";
});
Working Code Snippet:
var w=40; //Sample chart width
var color = d3.scale.category20c();
var svg = d3.select("body").append("svg").attr({ height: 500, width: 400 });
var legendname = ["Yes", "No"];
var legend = svg.selectAll(".legend")
.data(legendname)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(" + (w + 150) + "," + (30+i*40) + ")";
});
legend.append("path")
.attr("d", d3.svg.symbol().type("triangle-up").size(128))
.style('fill', function(d,i) {
return color(i);
});
legend.append("text")
.attr("dx",10)
.attr("dy",".4em")
.text(function(d) {
return d;
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Tooltip in linegraph doesn't show using d3-tip

I've got this linechart, on each value in the chart I am placing a dot.
when hovering over the dot I would like to show the value using a d3-tip tooltip.
Here is what I got so far:
var svg = chart.append("svg")
.attr("width", outerWidth)
.attr("height", outerHeight)
.append("g")
.attr("transform", "translate(0,10)");
newData.graphSeries.forEach(function (current, index, all) {
//current = this exact part of the array
//index = the array index nr [0][1][2] etc
//all = the complete array
var graph = current.Values,
color = current.Color;
var nextLine = d3.svg.line()
.interpolate(current.Interpolate)
.x(function (d) {
return x(d.x);
})
.y(function (d) {
return y(d.y);
});
svg.append("path")
.datum(graph)
.attr("transform", "translate(" + yAxisWidth + ",0)")
.attr("class", "line stroke-" + color)
.attr("d", nextLine);
//Placing tooltips
if (current.Tooltips == true) {
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong> TEST: " + newData.y.Unit + "</strong><span>" + d.x + "</span>"
});
//create circles to place the tooltip on
svg.selectAll('dot')
.data(graph)
.enter().append("circle")
.attr("r", 3,5)
.attr("style", "cursor: pointer")
.attr("class", "circle-" + color)
.attr("transform", "translate(" + yAxisWidth + ",0)")
.attr("cx", function(d) { return x(d.x) })
.attr("cy", function(d) { return y(d.y) })
.on('mouseover', tip.show )
.on('mouseout', tip.hide);
svg.call(tip);
}
});
I checked if d3-tip exists in the code and it does.
I can console.log the tip variable, also the dots are showing and even the mouseover and mouseout are working correctly.
Still somehow tip.show doesn't seem to work.
I thought maybe it would show somewhere else in the document, but can't see it anywhere.
Could you please help out.
Best,
Bart
The problem was actually easier to solve then expected.
The tooltip might be 'pushed' away by all other html code.
Adding .style('z-index', '99999999999'); will help to get that straight.
See:
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.style('z-index', '99999999')
.html(function(d) {
return "<strong> TEST: " + newData.y.Unit + "</strong><span>" + d.x + "</span>"
});

Rotation with transition

As stated in previous questions (for example d3.js Odd Rotation Behavior), when you want to rotate an SVG object on its place you should use
.attr('transform','rotate(45,100,100)')
where the first number is the rotation degrees, and the other two are the coordinates of the rotation origin.
The problem here is that I want to execute the rotation inside a transition:
.transition()
.attr('transform', 'rotate(45,100,100)')
.duration(300)
and I get a strange behaviour, I can see a translation before the expected rotation.
You can see the result here: http://jsfiddle.net/uwM8u/121/
Is there a better way to obtain this?
The D3 way to do this is to use a custom tween function, which in this case is a simple string interpolation:
.attrTween("transform", function() {
return d3.interpolateString(d3.select(this).attr("transform"),
"rotate(" + (rotation+=45) + "," +
(diamond.x+diamond.width/2) + "," +
(diamond.y+diamond.width/2) + ")");
})
Complete demo here.
Add a parent <g> element with the translation so you are actually rotating the shape about the origin.
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height", 300);
svg
.append("text")
.text("click the square")
.attr("x", w/2)
.attr("y", w/2)
svg
.append("g")
.attr("transform", "translate(" + diamond.x + "," + diamond.y +")")
.append("rect")
.attr("transform", "rotate(" + rotation + ")")
.attr("x", -diamond.width / 2)
.attr("y", -diamond.width / 2)
.attr("height", diamond.width)
.attr("width", diamond.width)
.attr("fill", "teal")
.on("mousedown", function(){
d3.select(this)
.transition()
.attr("transform", "rotate("+ (rotation+=45) +")")
.duration(300)
});
or as a jsfiddle

Categories