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>"
});
Related
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>
I'm building a bubble chart from d3js but keep receiving a TypeError: undefined is not a function with the enter() method. I've tried just about everything and I can't determine why this error is being produced besides the fact that the filter method is returning null. Currently the bubble chart itself is not displayed.
var diameter = 310,
format = d3.format(",d"),
color = d3.scale.category20c();
var bubble = d3.layout.pack()
.sort(null)
.size([diameter, diameter])
.padding(1.5);
var svg = d3.select("bubble")
.append("svg")
.attr("width", diameter)
.attr("height", diameter)
.attr("class", "bubble");
var node = svg.selectAll(".node")
.data(bubble.nodes({children: [{packageName: "food", className: "food", value: 100}]}))
.filter(function(d) { return !d.children; })
// ================================
// This is causing the error below
// ================================
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("title")
.text(function(d) { return d.className + ": " + format(d.value); });
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return color(d.packageName); });
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.className.substring(0, d.r / 3); });
d3.select(self.frameElement).style("height", diameter + "px");
JsFiddle: http://jsfiddle.net/26Tra/
In your data binding, you are using keys such as packageName and className that are generated by the classes function, which is missing from your code. I added it and now your data binding is correct:
var node = svg.selectAll(".node")
.data(bubble.nodes(classes(root)).filter(function (d) {return !d.children;}))
.enter()
...
NOTE: because I mocked some very simple data, with only one packageName, fruits, I used className instead of packageName for the coloring. I also changed the color category to category10() to give it more contrast.
Complete FIDDLE.
I have a D3 bar chart with the associated data points displayed as text on top of each bar. I want to display the text only on mouseover and also make the bar have a different fill color. So, essentially, on mouseover, the bar has to be styled to have a different fill color and the text opacity should go to 1 (from '0').
I am having trouble effecting two separate events on mouseover. I have given an index_value attribute to both elements in order to use d3.select(this).attr(index_value). But my mouseover function does not work. I have no idea why. Here's my relevant code section.
The bar chart
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr('data-value', function(d){return d[region]})
.attr("x", function(d) { return x(d.year); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d[region]); })
.attr("height", function(d) { return height - y(d[region]); })
.attr("fill", color)
.attr("index_year", function(d, i) { return "index-" + d.year; })
.attr("class", function(d){return "bar " + "bar-index-" + d.year;})
.attr("color_value", color)
.on('mouseover', synchronizedMouseOver)
.on("mouseout", synchronizedMouseOut);
The text overlay
svg.selectAll(".bartext")
.data(data)
.enter()
.append("text")
.attr("text-anchor", "middle")
.attr("x", function(d,i) {
return x(d.year)+x.rangeBand()/2;
})
.attr("y", function(d,i) {
return height - (height - y(d[region])) - yTextPadding;
})
.text(function(d){
return d3.format(prefix)(d3.round(d[region]));
})
.attr("index_year", function(d, i) { return "index-" + d.year; })
.attr("class", function(d){return "bartext " + "label-index-" + d.year;})
.on("mouseover", synchronizedMouseOver)
.on("mouseout", synchronizedMouseOut);
And the mouseover function
var synchronizedMouseOver = function() {
var bar = d3.select(this);
console.log(bar);
var indexValue = bar.attr("index_year");
var barSelector = "." + "bar " + "bar-" + indexValue;
var selectedBar = d3.selectAll(barSelector);
selectedBar.style("fill", "#f7fcb9");
var labelSelector = "." + "bartext " + "label-" + indexValue;
var selectedLabel = d3.selectAll(labelSelector);
selectedLabel.style("opacity", "1");
};
This can be achieved by simplifying your listeners. You don't need to add listeners to both rects and text. Just add them to the rects. Here are the simplified listeners:
function synchronizedMouseOver(d) {
var bar = d3.select(this)
.style("fill","red");
var text = d3.select(".label-index-" + d.year)
.style("opacity","1");
};
function synchronizedMouseOut(d) {
var bar = d3.select(this)
.style("fill",color);
var text = d3.select(".label-index-" + d.year)
.style("opacity","0");
};
Your two friends here are this and d, the DOM element for the rect and its data node, respectively.
Here is a FIDDLE with the behavior that you desire.
I am following code at this location:
My json file looks like this
[
{"name":"flare.analytics.A","size":3938,"imports":["flare.analytics.B,flare.analytics.C"]},
{"name":"flare.analytics.B","size":3812,"imports":["flare.analytics.C,flare.analytics.D"]},
{"name":"flare.analytics.C","size":3812,"imports":["flare.analytics.D,flare.analytics.E"]},
{"name":"flare.analytics.D","size":743, "imports":["flare.analytics.E,flare.analytics.F"]},
{"name":"flare.analytics.E","size":3534,"imports":["flare.analytics.F,flare.analytics.G"]},
{"name":"flare.analytics.F","size":5731,"imports":["flare.analytics.G,flare.analytics.H"]},
{"name":"flare.analytics.G","size":7840,"imports":["flare.analytics.H,flare.analytics.I"]},
{"name":"flare.analytics.H","size":5914,"imports":["flare.analytics.I,flare.analytics.A"]},
{"name":"flare.analytics.I","size":3416,"imports":["flare.analytics.B,flare.analytics.A"]}
]
I added a tooltip in above code following this tutorial
http://bl.ocks.org/Caged/6476579.
I added
.d3-tip in Style section
Then i added function
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Frequency:</strong> <span style='color:red'>" + d.frequency + "</span>";
})
svg.call(tip);
Now in above html function instead of d.frequency i added d3.select("text").text() so it became
return "<strong>Frequency:</strong> <span style='color:red'>" + d3.select("text").text() + "</span>";
In my mouseovered function i added
node
.classed("mouseover", tip.show);
in mouseouted i added
node
.classed("mouseover", tip.hide);
The problem is, it always select the first element from my tree and displays as tooltip
I found an answer to this at Show d3 node text only on hover.
But I am not sure how would i integrate that in my code
UPDATE
node = node
.data(nodes.filter(function(n) { return !n.children; }))
.enter().append("text")
.attr("class", "node")
.attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
.attr("dy", ".31em")
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")" + (d.x < 180 ? "" : "rotate(180)"); })
.style("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.text(function(d) { return d.key; })
.on("mouseover", mouseovered)
.on("mouseout", mouseouted);
});
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<span style='color:red'>" + d3.select("text").text()+ "</span>";
})
You can set the tip's html property when in the mouseovered function (rather than where you define 'tip'):
function mouseovered(d) {
tip.html("<strong>Frequency:</strong> <span style='color:red'>" + d.key + "</span>"
);
See this fiddle: http://jsfiddle.net/henbox/XqEMf/3/
Otherwise you're defining the value of the tooltip before you do any mouse-overing
Note you'll also want to change the tip definition to just:
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0]);
I am currently using the d3.layout.tree() to compute the positions of my data.
var tree = d3.layout.tree()
.sort(null)
.size([size.height, size.width - maxLabelLength * options.fontSize])
.children(function(d)
{
return (!d.contents || d.contents.length === 0) ? null : d.contents;
});
Initially I compute and add my nodes like this:
var nodes = tree.nodes(treeData);
var nodeGroup = layoutRoot.selectAll("g.node")
.data(nodes, function (d) { return d.name })
.enter()
.append("svg:g")
.attr("class", "node")
.attr("transform", function(d)
{
return "translate(" + d.y + "," + d.x + ")";
});
nodeGroup.append("svg:circle")
.attr("class", "node-dot")
.attr("r", options.nodeRadius);
Now I add a new node to the treeData and also to the layoutRoot:
var grp = layoutRoot.selectAll("g.node")
.data(nodes, function (d) { return d.name })
.enter()
.append('svg:g')
.attr("transform", function (d)
{
return "translate(" + d.y + "," + d.x + ")";
})
grp.append("svg:circle")
.attr("class", "node-dot")
.attr("r", options.nodeRadius)
The problem is now, that the newly computed nodes that are already present in the rootLayout have different x,y coordinates after having added a new node. But they are not within the enter() or exit() selection and are thus not redrawn at their correct position. How is this supposed to be handled, ie. how should the position of the nodes that have not changed anything but their coordinates be updated/refreshed?
I a noob to d3js. So don't be too harsh :D
I would separate the enter() selection from the update of nodes like this :
var nodeGroup = layoutRoot.selectAll("g.node")
.data(nodes, function (d) { return d.name });
// Enter selection
nodeGroup.enter()
.append("svg:g")
.attr("class", "node")
// Update
nodeGroup.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
var nodeDots = layoutRoot.selectAll("g.node-dot")
.data(nodes, function (d) { return d.name });
// Enter
nodeDots.enter()
.append("circle")
.attr("class", "node-dot")
// Update
nodeDots.attr("r", options.nodeRadius);
Hope this helps, but in a general way of speaking, it is perhaps easier to code this way, with separation of enter and updates (see here for more info)