Related
I have 2 problems here:
1/ These buttons (you can run the code snippet below) should reverse back to their former opacity at the second click but they don't ! Can someone explain me what's wrong with the code ?
2/ How do I reverse back to the text former color on the second click without having to define that former color (again!) in the toggle function. ?
Explanation: As you can see when running the following snippet, the text (1,2,3...) turns white at the first click. I'd like to set it back to its former color at the second click without having to write:
var newTextFill = active ? "white" : "**former_color**";
For instance, in this example the author doesn't specify the font-size the legend should return to on a second click and yet you can see it shrinking back to its original size. How can I do the same thing here with the font colors ?
var name = ["123456789"];
var color = d3.scaleOrdinal()
.range(["#00baff", "#0014fe", "#00dbaf", "#f4bf02", "#ffa600", "#ff0000", "#ff00c4", "#ee693e", "#99958f"]);
var svg = d3.select("body")
.append("svg")
.attr("width", 300)
.attr("height",300)
var legende = svg.selectAll("bar")
.data(name)
.enter()
.append("g")
.attr("x", 0)
.attr("y", function (d,i) {return 12 + i * 20})
legende.append("text")
.text(function(d,i) { return name[i]})
.attr("class", "legtext")
.style("font-family", "helvetica")
.style("fill-opacity", 0.8)
.attr("x", 4)
.attr("y", function (d,i) {return 12 + i * 20})
.style("font-size", 10)
.style("fill", function(d,i) { return color(i)})
.style("text-decoration", "none")
.style("opacity", 1);
legende.append("rect")
.attr("class", "recta")
.attr("x", 2)
.attr("y", function (d,i) {return 1 + i * 20})
.attr("width", 65)
.attr("height", 15)
.style("fill", function(d,i) { return color(i)})
.style("opacity", 0.09);
legende.append("rect")
.attr("class", "rectastroke")
.attr("id", function(d,i) {return "str" + i})
.attr("x", 2)
.attr("y", function (d,i) {return 1 + i * 20})
.attr("width", 65)
.attr("height", 15)
.style("stroke", function(d,i) { return color(i)})
.style("stroke-width", 0.5)
.style("fill", "none")
.attr("display", "none");
legende.on("click", function (d,i)
{var active = d.active ? false : true;
var newOpacity = active ? 1 : 0.1;
var newTextFill = active ? "white" : "blue";
d3.select(this)
.select("rect")
.style("opacity", newOpacity)
d3.select(this)
.select("text")
.style("fill", newTextFill)
.raise()
d.active = active });
legende.on("mouseover", function(d, i)
{d3.select(this).selectAll(".rectastroke").attr("display", "true")});
legende.on("mouseout", function(d, i)
{d3.select(this).selectAll(".rectastroke").attr("display", "none")});
body {font-family: 'Open Sans', sans-serif;
font-size: 11px;
font-weight: 300;
fill: #242424;
text-align: center;
cursor: default;}
.legende {cursor: pointer;}
.recta {cursor: pointer;}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div class="colored buttons"></div>
</body>
</html>
In the bl.ocks you linked she is, in fact, specifying the font size the legend will return to on a second click... in the CSS.
What's happening is that if active is false, the setter will return undefined...
d3.select(this)
.style("font-size", function() {
if (active) {return "25px"}
})
... and the UA will set the style based on the CSS.
You can do the same thing to your rectangles without specifying any newOpacity:
d3.select(this)
.select("rect")
.style("opacity", function() {
if (active) return 1;
})
Here is the demo:
var data = "123456789".split("").map(function(d) {
return {
value: d
}
});
var color = d3.scaleOrdinal()
.range(["#00baff", "#0014fe", "#00dbaf", "#f4bf02", "#ffa600", "#ff0000", "#ff00c4", "#ee693e", "#99958f"]);
var svg = d3.select("body")
.append("svg")
.attr("width", 300)
.attr("height", 300)
var legende = svg.selectAll("bar")
.data(data)
.enter()
.append("g")
.attr("x", 0)
.attr("y", function(d, i) {
return 12 + i * 20
})
legende.append("text")
.text(function(d, i) {
return d.value
})
.attr("class", "legtext")
.style("font-family", "helvetica")
.style("fill-opacity", 0.8)
.attr("x", 4)
.attr("y", function(d, i) {
return 12 + i * 20
})
.style("font-size", 10)
.style("fill", function(d, i) {
return color(i)
})
.style("text-decoration", "none")
.style("opacity", 1);
legende.append("rect")
.attr("class", "recta")
.attr("x", 2)
.attr("y", function(d, i) {
return 1 + i * 20
})
.attr("width", 65)
.attr("height", 15)
.style("fill", function(d, i) {
return color(i)
});
legende.on("click", function(d, i) {
var active = d.active ? false : true;
d3.select(this)
.select("rect")
.style("opacity", function() {
if (active) return 1;
})
d3.select(this)
.select("text")
.style("fill", function(e, j) {
if (active) {
return "white";
} else {
return color(i)
}
})
.raise()
d.active = active
});
.legende {
cursor: pointer;
}
.recta {
opacity: 0.1;
cursor: pointer;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
For the text color, however, since you are not specifying it in the CSS, you'll have to use a normal if...else code.
I creating a stacked bar chart using this example. The chart works and renders but I can't add a mouseover label.
I tried this...
DATE.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
.append("svg:title")
.text(functino(d){return "foo"});
But this after adding the .append("svg:title... the graph stops rendering. If I remove the .style("fill... line, the graph renders, however it's not stacked and there's no mouseover feature.
I have also tried using the tooltip route. (Source)
.on("mouseover", function() { tooltip.style("display", null); })
.on("mouseout", function() { tooltip.style("display", "none"); })
.on("mousemove", function(d) {
var xPosition = d3.mouse(this)[0] - 15;
var yPosition = d3.mouse(this)[1] - 25;
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
tooltip.select("text").text(d.y);
});
// Prep the tooltip bits, initial display is hidden
var tooltip = svg.append("g")
.attr("class", "tooltip")
.style("display", "none");
tooltip.append("rect")
.attr("width", 30)
.attr("height", 20)
.attr("fill", "white")
.style("opacity", 0.5);
tooltip.append("text")
.attr("x", 15)
.attr("dy", "1.2em")
.style("text-anchor", "middle")
.attr("font-size", "12px")
.attr("font-weight", "bold");
But still not luck. Is there a library I need to load? Not sure what's going on.
The graph stop rendering when you try to append the title because you have a typo: it's function, not functino.
Besides that, this is what you need to get the value of each stacked bar:
.append("title")
.text(function(d){
return d[1]-d[0]
});
Here is the demo: https://bl.ocks.org/anonymous/raw/886d1749c4e01e191b94df23d97dcaf7/
But I don't like <title>s. They are not very versatile. Thus, instead of creating another <text>, as the second code you linked does, I prefer creating a div:
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
Which we position and set the HTML text this way:
.on("mousemove", function(d) {
tooltip.html("Value: " + (d[1] - d[0]))
.style('top', d3.event.pageY - 10 + 'px')
.style('left', d3.event.pageX + 10 + 'px')
.style("opacity", 0.9);
}).on("mouseout", function() {
tooltip.style("opacity", 0)
});
And here is the demo: https://bl.ocks.org/anonymous/raw/f6294c4d8513dbbd8152770e0750efd9/
I am working on a population pyramid that has an updating function.
http://bricbracs.com/hh/
As you can see the bars expand and contract in a horizontal line when you update it with new data. I want to modify the transition effect so that the bars enter and exit vertically like this:
http://vis.stanford.edu/jheer/d3/pyramid/shift.html
I have been following this tutorial and modifying the code but so far no luck.
https://strongriley.github.io/d3/tutorial/bar-2.html
Here is the code that first draws the bars on loading. (this is the male bar group, the female bar group is the same)
leftBarGroup.selectAll('.bar.left')
.data(data)
.enter()
.append('rect')
.attr('class', 'bar left')
.attr('y', function(d) { return yScale(d.group); })
.attr("width", 0)
.attr("opacity", 0)
.transition()
.duration(500)
.attr('width', function(d) { return xScale(d.male); })
.attr('height', yScale.rangeBand())
.attr("opacity", 1)
And here is the corresponding part of the code in the updating function which changes the bars.
var sel = leftBarGroup.selectAll('.bar.left')
.attr('class', 'bar left')
.data(data)
.data(data, function(d) { return d.male; })
.transition()
.attr('y',0)
.duration(500)
.attr('y', function(d) { return yScale(d.group); })
.attr('height', yScale.rangeBand())
.attr('width', function(d) { return xScale(d.male); })
.attr('height', yScale.rangeBand())
Thanks in advance.
Here's one way to reproduce the effect in your linked example. I offset the bars and then slide them back into place. You then handle the top and bottom bars slightly different.
Note, I only did the slide down on the male side of the pyramid, if you need help going the rest of the way just leave me a comment.
var sel = leftBarGroup.selectAll('.bar.left')
.attr('class', 'bar left')
.data(data)
.data(data, function(d) {
return d.male;
})
// offset y to slide down
.attr('y', function(d){
var self = d3.select(this);
return +self.attr('y') - yScale.rangeBand();
})
.transition()
.duration(500)
// slide it back into place
.attr('y', function(d) {
return yScale(d.group);
})
// and set new width
.attr('width', function(d) {
return xScale(d.male);
});
// for the very top bar
// not only slide it but "fade it in"
leftBarGroup.select(':last-child')
.style('opacity', 0)
.transition()
.duration(500)
.style('opacity', 1)
.attr('y', function(d) {
return yScale(d.group);
});
// append a fake bar on the bottom
// to slide and fade out
leftBarGroup.append('rect')
.attr('y', function(d) {
return yScale('0-4');
})
.attr('height', yScale.rangeBand())
.attr('width', function(){
return leftBarGroup.select(':first-child').attr('width');
})
.style('fill', 'steelblue')
.style('opacity', 0.6)
.transition()
.duration(500)
.attr('y', function(d) {
return yScale('0-4') + yScale.rangeBand();
})
.style('opacity', 0)
.remove();
EDITS
Going up is just a matter of reversing the logic:
var sel = leftBarGroup.selectAll('.bar.left')
.attr('class', 'bar left')
.data(data)
.data(data, function(d) {
return d.male;
})
// offset y to slide up
.attr('y', function(d){
var self = d3.select(this);
return +self.attr('y') + yScale.rangeBand()
})
.transition()
.duration(500)
// slide it back into place
.attr('y', function(d) {
return yScale(d.group);
})
// and set new width
.attr('width', function(d) {
return xScale(d.male);
});
// for the very bottom bar
// not only slide it but "fade it in"
leftBarGroup.select(':first-child')
.style('opacity', 0)
.transition()
.duration(500)
.style('opacity', 1)
.attr('y', function(d) {
return yScale(d.group);
});
// append a fake bar on the top
// to slide and fade out
var w = leftBarGroup.select(':last-child').attr('width');
leftBarGroup.append('rect')
.attr('class','fake')
.attr('y', function(d) {
return yScale('90+');
})
.attr('height', yScale.rangeBand())
.attr('width', w)
.style('fill', 'steelblue')
.style('opacity', 0.6)
.transition()
.duration(500)
.attr('y', function(d) {
return yScale('90+') - yScale.rangeBand();
})
.style('opacity', 0)
.remove();
Updated code sample here.
I'm having a go at my first line chart in d3.js and would like to draw the path between the data points gradually, but also display each data point as a solid circle.
I have already got this working, but what I would really like to try and do is show each circle appear as the line hits each data point.
Right now the data points appear first and then the line path is drawn between them as per this snippet of my code:
var path = g.append("svg:path")
.attr("d", line(data));
var totalLength = path.node().getTotalLength();
console.log(totalLength);
path
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(2000)
.ease("linear")
.attr("stroke-dashoffset", 0);
Working example - http://codepen.io/anon/pen/JYqPvZ
Is what I'm trying to achieve possible?
Thanks
You could write a custom tween function:
path
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(2000)
.ease("linear")
// custom tween
.tween("line", function() {
// set up an interp function
var interp = d3.interpolateNumber(totalLength, 0);
var self = d3.select(this);
// this is called on each animation frame
return function(t) {
// calculate offset and apply it
var offset = interp(t);
self.attr("stroke-dashoffset", offset);
// calculate current x position of line
var xPos = this.getPointAtLength(totalLength - offset).x;
// for each point see if we can "show" the circle
g.selectAll(".point").each(function(){
var point = d3.select(this);
if (xPos > (+point.attr('cx'))){
point.style('opacity',1);
}
})
};
});
Working code:
var data = [3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 7],
w = 600,
h = 400,
margin = 20,
y = d3.scale.linear().domain([0, d3.max(data)]).range([0 + margin, h - margin]),
x = d3.scale.linear().domain([0, data.length]).range([0 + margin, w - margin]);
var vis = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
var g = vis.append("svg:g")
.attr("transform", "translate(0, 400)");
var line = d3.svg.line()
//.interpolate("cardinal")
.x(function(d,i) { return x(i); })
.y(function(d) { return -1 * y(d); });
var path = g.append("svg:path")
.attr("d", line(data));
var totalLength = path.node().getTotalLength();
console.log(totalLength);
path
.attr("stroke-dasharray", totalLength + " " + totalLength)
.attr("stroke-dashoffset", totalLength)
.transition()
.duration(2000)
.ease("linear")
.tween("line", function() {
var interp = d3.interpolateNumber(totalLength, 0);
var self = d3.select(this);
return function(t) {
var offset = interp(t);
self.attr("stroke-dashoffset", offset);
var xPos = this.getPointAtLength(totalLength - offset).x;
g.selectAll(".point").each(function(){
var point = d3.select(this);
if (xPos > (+point.attr('cx'))){
point.style('opacity',1);
}
})
};
});
g.append("svg:line")
.attr("x1", x(0))
.attr("y1", -1 * y(0))
.attr("x2", x(w))
.attr("y2", -1 * y(0))
g.append("svg:line")
.attr("x1", x(0))
.attr("y1", -1 * y(0))
.attr("x2", x(0))
.attr("y2", -1 * y(d3.max(data)));
g.selectAll(".xLabel")
.data(x.ticks(5))
.enter().append("svg:text")
.attr("class", "xLabel")
.text(String)
.attr("x", function(d) { return x(d) })
.attr("y", 0)
.attr("text-anchor", "middle")
g.selectAll(".yLabel")
.data(y.ticks(4))
.enter().append("svg:text")
.attr("class", "yLabel")
.text(String)
.attr("x", 0)
.attr("y", function(d) { return -1 * y(d) })
.attr("text-anchor", "right")
.attr("dy", 4)
g.selectAll(".xTicks")
.data(x.ticks(5))
.enter().append("svg:line")
.attr("class", "xTicks")
.attr("x1", function(d) { return x(d); })
.attr("y1", -1 * y(0))
.attr("x2", function(d) { return x(d); })
.attr("y2", -1 * y(-0.3))
g.selectAll(".yTicks")
.data(y.ticks(4))
.enter().append("svg:line")
.attr("class", "yTicks")
.attr("y1", function(d) { return -1 * y(d); })
.attr("x1", x(-0.3))
.attr("y2", function(d) { return -1 * y(d); })
.attr("x2", x(0))
var points = g.selectAll(".point")
.data(data)
.enter().append("svg:circle")
.attr("class","point")
.attr("stroke", "steelblue")
.attr("fill", function(d, i) { return "steelblue" })
.attr("cx", function(d, i) { return x(i); })
.attr("cy", function(d, i) { return -1 * y(d); })
.attr("r", function(d, i) { return 5 })
.style("opacity", 0)
.on('click', function(p){
console.log('clicked! - ' + p );
})
.on("mouseover", function(d) {
d3.select(this).attr("r", 8).style("fill", "red").attr("stroke", "red");
})
.on("mouseout", function(d) {
d3.select(this).attr("r", 5).style("fill", "steelblue").attr("stroke", "steelblue");
});
var val = points.append('g');
val.selectAll('text')
.data(function(d,i){ return d.values})
.enter().append('text')
.attr("x", function(d, i) {
return x(i) + x.rangeBand() / 2;})
.attr("y", function(d, i) { return y(d.value) })
.attr('dy', -10)
.attr("text-anchor", "middle")
.text(function(d) { return d.value; })
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
line {
stroke: black;
}
text {
font-family: Arial;
font-size: 9pt;
}
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
I have the following d3.js script to create bar chart which works fine.
I added functionality to show tool tip (not sure whether i added it at right place or not) which works fine but it has created an issue with existing mouseout event.
Issue:
The issue is that the following code is not working anymore. When i mouse over it does not turn into grey.
.on('mouseout', function (d) {
d3.select(this)
.attr('fill', 'blue');
However, if i comment the following lines than above mouseout event works perfect.
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
Complete Script
var jsonData = #Html.Raw(Json.Encode(Model));
data = jsonData;
InitChart();
function InitChart() {
var barData = data;
var vis = d3.select('#SummaryChart'),
WIDTH = 500,
HEIGHT = 375,
MARGINS = {
top: 20,
right: 20,
bottom: 20,
left: 150
},
xRange = d3.scale.ordinal().rangeRoundBands([MARGINS.left, WIDTH - MARGINS.right], 0.1).domain(barData.map(function (d) {
return d.Date;
})),
yRange = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([0,
d3.max(barData, function (d) {
return d.Duration;
})
]),
xAxis = d3.svg.axis()
.scale(xRange)
.tickSize(0)
.tickSubdivide(true),
yAxis = d3.svg.axis()
.scale(yRange)
.tickSize(0)
.orient("left")
.tickSubdivide(true);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Duration:</strong> <span style='color:red'>" + d.Duration + "</span>";
})
vis.call(tip);
vis.append('svg:g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + (HEIGHT - MARGINS.bottom) + ')')
.call(xAxis);
vis.append('svg:g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + (MARGINS.left) + ',0)')
.call(yAxis);
vis.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("font-size", "20px")
.attr("x", WIDTH)
.attr("y", HEIGHT + 20)
.text("Time");
vis.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("font-size", "20px")
.attr("y", 100)
.attr("x",-100)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text("Hours:");
vis.selectAll('rect')
.data(barData)
.enter()
.append('rect')
.attr('x', function (d) {
return xRange(d.Date);
})
.attr('y', function (d) {
return yRange(d.Duration);
})
.attr('width', xRange.rangeBand())
.attr('height', function (d) {
return ((HEIGHT - MARGINS.bottom) - yRange(d.Duration));
})
.attr('fill', 'blue')
.on('mouseover', function (d) {
d3.select(this)
.attr('fill', 'grey');
})
.on('mouseout', function (d) {
d3.select(this)
.attr('fill', 'blue');
})
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
}
</script>
Can someone point out what is wrong and how i cam make it working with both tooltip and on mouse turning into grey working both together?
Unlike jQuery, D3 allows only a single callback per action. Therefore if you attach two .on('mouseout') callbacks, only the last one will execute. See:
d3.select('div')
.on('mouseout', function() {console.log('A')})
.on('mouseout', function() {console.log('B')})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div style='width:250px;height:250px;background:red'></div>
You have two ways around this. One, as suggested in comments, would be to call both attr and tooltip in the callback, as this:
.on('mouseout', function (d) {
d3.select(this).attr('fill', 'blue');
tip.hide();
}
Second would be to use the dot . notation, as described in the API, second paragraph
If an event listener was already registered for the same type on the selected element, the existing listener is removed before the new listener is added. To register multiple listeners for the same event type, the type may be followed by an optional namespace, such as "click.foo" and "click.bar".
So in your case
.on('mouseout.attr', function (d) {
d3.select(this)
.attr('fill', 'blue');
})
.on('mouseout.tip', tip.hide)
Working example:
d3.select('div')
.on('mouseout.logA', function() {console.log('A')})
.on('mouseout.logB', function() {console.log('B')})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div style='width:250px;height:250px;background:red'></div>