Reputation: 157
I have a simple bar chart here that should nicely transition all the bars over slightly to the left to make room for a new bar to appear on the right of the graph whenever new data is submitted via a button click event, however they all seem to fire over to the left side on top of each other.
Here is my code for my transition:
bars.transition()
.duration(1000)
.attr("x", function(d, i) {
return x(i);
})
.attr("y", function(d) {
return y(d.count);
})
.attr("width", x.rangeBand())
.attr("height", function(d) {
return height - y(d.count);
});
Here is a jsfiddle of the code so far: https://jsfiddle.net/foL2Lcg1/1/
The test data for entry on the button submission I'm using is 'Service: blah' and 'Count: 60000' which you can enter below the graph.
Any suggestions?
Upvotes: 2
Views: 1316
Reputation: 14591
There is a small mistake in your code. Use x(d.service)
instead of x(i)
to calculate x position of bars in click function.
bars.transition()
.duration(1000)
.attr("x", function(d, i) {
return x(d.service); //instead of x(i);
})
.attr("y", function(d) {
return y(d.count);
})
.attr("width", x.rangeBand())
.attr("height", function(d) {
return height - y(d.count);
});
var data = [
{
service: "QA",
count: "25262"
}, {
service: "QB",
count: "42386"
}, {
service: "QUERY_NOTICICATIONS",
count: "14042"
}, {
service: "TTL",
count: "4088"
}
];
var width = 960,
barHeight = 500;
var margin = {
top: 20,
right: 30,
bottom: 50,
left: 60
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var chart = d3.select(".chart")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(data.map(function(d) {
return d.service;
}));
y.domain([0, d3.max(data, function(d) {
return d.count
})]);
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Count");
chart.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x(d.service);
})
.attr("y", function(d) {
return y(d.count);
})
.attr("height", function(d) {
return height - y(d.count);
})
.attr("width", x.rangeBand())
.on("mouseover", function(d) {
var xPos = parseFloat(d3.select(this).attr("x")) + x.rangeBand() / 2;
var yPos = parseFloat(d3.select(this).attr("y")) / 2 + height / 2;
//update tooltip positiom
d3.select("#tooltip")
.style("left", xPos + "px")
.style("top", yPos + "px")
.select("#service")
.text("Service: " + d.service);
d3.select("#tooltip")
.style("left", xPos + "px")
.style("top", yPos + "px")
.select("#count")
.text("Count: " + d.count);
//show tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
d3.select("#tooltip").classed("hidden", true);
});
d3.select("button")
.attr("id", "submit")
.on("click", function() {
var service = document.getElementById("service").value;
var count = document.getElementById("count").value;
var json = {
"count": count,
"service": service
};
data.push(json);
console.log(data);
x.domain(data.map(function(d) {
return d.service;
}));
y.domain([0, d3.max(data, function(d) {
return d.count
})]);
chart.select(".x.axis")
.transition()
.duration(1000)
.call(xAxis);
chart.select(".y.axis")
.transition()
.duration(1000)
.call(yAxis);
var bars = chart.selectAll(".bar")
.data(data);
bars.enter().append("rect");
bars.attr("class", "bar")
.attr("x", function(d) {
return x(d.service);
})
.attr("y", function(d) {
return y(d.count);
})
.attr("height", function(d) {
return height - y(d.count);
})
.attr("width", x.rangeBand())
.on("mouseover", function(d) {
var xPos = parseFloat(d3.select(this).attr("x")) + x.rangeBand() / 2;
var yPos = parseFloat(d3.select(this).attr("y")) / 2 + height / 2;
//update tooltip positiom
d3.select("#tooltip")
.style("left", xPos + "px")
.style("top", yPos + "px")
.select("#service")
.text("Service: " + d.service);
d3.select("#tooltip")
.style("left", xPos + "px")
.style("top", yPos + "px")
.select("#count")
.text("Count: " + d.count);
//show tooltip
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function() {
d3.select("#tooltip").classed("hidden", true);
});
bars.transition()
.duration(1000)
.attr("x", function(d, i) {
return x(d.service);
})
.attr("y", function(d) {
return y(d.count);
})
.attr("width", x.rangeBand())
.attr("height", function(d) {
return height - y(d.count);
});
})
#tooltip {
position: absolute;
width: 200px;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
.chart rect {
fill: steelblue;
}
.chart text {
font: 10px sans-serif;
text-anchor: end;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
svg text {
pointer-events: none;
}
rect {
-moz-transition: all 0.3s;
-o-transition: all 0.3s;
-webkit-transition: all 0.3s;
transition: all 0.3s;
}
rect:hover {
fill: orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg class="chart"></svg>
<div class="input">
<p>Service :
<input id="service" type="text" />Count :
<input id="count" type="text" />
</p>
<br/>
<button id="submit">Submit</button>
</div>
<div id="tooltip" class="hidden">
<p><strong>Tooltip</strong>
</p>
<p><span id="service"></span>
</p>
<p><span id="count"></span>
</p>
</div>
Upvotes: 2
Reputation: 10612
From the example here : https://bl.ocks.org/RandomEtc/cff3610e7dd47bef2d01
I have created this fiddle with your data : https://jsfiddle.net/thatOneGuy/foL2Lcg1/5/
I have added a draw function which you pass data. So instead of rewriting code as you were, you start by calling this function, and once you add/remove some data, you call this function again with the new set of data and it updates. Here is the function :
function draw(data) {
// measure the domain (for x, unique letters) (for y [0,maxFrequency])
// now the scales are finished and usable
x.domain(data.map(function(d) { return d.service; }));
y.domain([0, d3.max(data, function(d) { return d.count; })]);
// another g element, this time to move the origin to the bottom of the svg element
// someSelection.call(thing) is roughly equivalent to thing(someSelection[i])
// for everything in the selection\
// the end result is g populated with text and lines!
svg.select('.x.axis').transition().duration(300).call(xAxis);
// same for yAxis but with more transform and a title
svg.select(".y.axis").transition().duration(300).call(yAxis)
// THIS IS THE ACTUAL WORK!
var bars = svg.selectAll(".bar").data(data) // (data) is an array/iterable thing, second argument is an ID generator function
bars.exit()
.transition()
.duration(300)
.attr("y", y(0))
.attr("height", height - y(0))
.style('fill-opacity', 1e-6)
.remove();
// data that needs DOM = enter() (a set/selection, not an event!)
bars.enter().append("rect")
.attr("class", "bar")
.attr("y", function(d) {
return y(d.count);
})
.attr("height", function(d) {
return height - y(d.count);
})
// the "UPDATE" set:
bars.transition().duration(300).attr("x", function(d) { return x(d.service); }) // (d) is one item from the data array, x is the scale object from above
.attr("width", x.rangeBand()) // constant, so no callback function(d) here
.attr("y", function(d) { return y(d.count); })
.attr("height", function(d) { return height - y(d.count); }); // flip the height, because y's domain is bottom up, but SVG renders top down
}
Now update as you were on button click :
d3.select("button")
.attr("id", "submit")
.on("click", function() {
var service = document.getElementById("service").value;
var count = document.getElementById("count").value;
var json = {
"count": count,
"service": service
};
data.push(json);
draw(data)
})
Here is working code if fiddle isn't working :
var data = [
{
service: "QA",
count: "25262"
}, {
service: "QB",
count: "42386"
}, {
service: "QUERY_NOTICICATIONS",
count: "14042"
}, {
service: "TTL",
count: "4088"
}
];
// Mike Bostock "margin conventions"
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight/1.5 - margin.top - margin.bottom;
// D3 scales = just math
// x is a function that transforms from "domain" (data) into "range" (usual pixels)
// domain gets set after the data loads
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
// D3 Axis - renders a d3 scale in SVG
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
//.ticks(10, "%");
// create an SVG element (appended to body)
// set size
// add a "g" element (think "group")
// annoying d3 gotcha - the 'svg' variable here is a 'g' element
// the final line sets the transform on <g>, not on <svg>
var svg = d3.select(".chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
svg.append("g")
.attr("class", "y axis")
.append("text") // just for the title (ticks are automatic)
.attr("transform", "rotate(-90)") // rotate the text!
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
// d3.tsv is a wrapper around XMLHTTPRequest, returns array of arrays (?) for a TSV file
// type function transforms strings to numbers, dates, etc.
draw(data);
function type(d) {
// + coerces to a Number from a String (or anything)
d.count = +d.count;
return d;
}
function draw(data) {
// measure the domain (for x, unique letters) (for y [0,maxFrequency])
// now the scales are finished and usable
x.domain(data.map(function(d) { return d.service; }));
y.domain([0, d3.max(data, function(d) { return d.count; })]);
// another g element, this time to move the origin to the bottom of the svg element
// someSelection.call(thing) is roughly equivalent to thing(someSelection[i])
// for everything in the selection\
// the end result is g populated with text and lines!
svg.select('.x.axis').transition().duration(300).call(xAxis);
// same for yAxis but with more transform and a title
svg.select(".y.axis").transition().duration(300).call(yAxis)
// THIS IS THE ACTUAL WORK!
var bars = svg.selectAll(".bar").data(data) // (data) is an array/iterable thing, second argument is an ID generator function
bars.exit()
.transition()
.duration(300)
.attr("y", y(0))
.attr("height", height - y(0))
.style('fill-opacity', 1e-6)
.remove();
// data that needs DOM = enter() (a set/selection, not an event!)
bars.enter().append("rect")
.attr("class", "bar")
.attr("y", function(d) {
return y(d.count);
})
.attr("height", function(d) {
return height - y(d.count);
})
// the "UPDATE" set:
bars.transition().duration(300).attr("x", function(d) { return x(d.service); }) // (d) is one item from the data array, x is the scale object from above
.attr("width", x.rangeBand()) // constant, so no callback function(d) here
.attr("y", function(d) { return y(d.count); })
.attr("height", function(d) { return height - y(d.count); }); // flip the height, because y's domain is bottom up, but SVG renders top down
}
d3.select("button")
.attr("id", "submit")
.on("click", function() {
var service = document.getElementById("service").value;
var count = document.getElementById("count").value;
var json = {
"count": count,
"service": service
};
data.push(json);
draw(data)
})
#tooltip {
position: absolute;
width: 200px;
height: auto;
padding: 10px;
background-color: white;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
-moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
#tooltip.hidden {
display: none;
}
#tooltip p {
margin: 0;
font-family: sans-serif;
font-size: 16px;
line-height: 20px;
}
.chart rect {
fill: steelblue;
}
.chart text {
font: 10px sans-serif;
text-anchor: end;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
svg text {
pointer-events: none;
}
rect {
-moz-transition: all 0.3s;
-o-transition: all 0.3s;
-webkit-transition: all 0.3s;
transition: all 0.3s;
}
rect:hover {
fill: orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body>
<svg class="chart"></svg>
<div class="input">
<p>Service :
<input id="service" type="text"></input> Count :
<input id="count" type="text"></input>
</p>
<br/>
<button id="submit">Submit</button>
</div>
<div id="tooltip" class="hidden">
<p><strong>Tooltip</strong></p>
<p><span id="service"></span></p>
<p><span id="count"></span></p>
</div>
</body>
Upvotes: 2