Trying to link object and text when dragging in v4 - d3.js

I have been trying to learn enough about d3.drag to integrate it into a sankey diagram for v4 similar to this example.
The start point was Mikes circle dragging example here which I tried to integrate with the object+text drag implementation here.
Tragically I can't quite work out how to grab the 'g' element using d3.drag.
There have been several iterations over the past few days, but I think the closest I have come is the example below which will only grab a specific rectangle for dragging (I suspect I may have butchered the use of 'this').
var margin = {top: 10, right: 10, bottom: 30, left: 10},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var rectangles = d3.range(20).map(function() {
return {
x: Math.round(Math.random() * (width)),
y: Math.round(Math.random() * (height))
};
});
var color = d3.scaleOrdinal(d3.schemeCategory20);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var group = svg.append("g")
.data(rectangles)
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
group.selectAll("rect")
.data(rectangles)
.enter().append("rect")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("height", 60)
.attr("width", 30)
.style("fill", function(d, i) { return color(i); });
group.selectAll("text")
.data(rectangles)
.enter().append("text")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("text-anchor", "start")
.style("fill", "steelblue")
.text("Close");
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text").attr("x", d.x = d3.event.x).attr("y", d.y = d3.event.y);
d3.select(this).select("rect").attr("x", d.x = d3.event.x).attr("y", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("active", false);
}
.active {
stroke: #000;
stroke-width: 2px;
}
.rect {
pointer-events: all;
stroke: none;
stroke-width: 40px;
}
<script src="//d3js.org/d3.v4.min.js"></script>
I tried to implement the code per the answer here which is a close analog, but the changes involved with moving from the force diagram defeated me as well.
Any guidance appreciated. I feel like I'm missing something fundamental.

I tweaked your code a little. I grouped both rectangles and text and added the drag attribute to the group. So, when we drag, both text and rectangle move in sink.
var margin = {top: 10, right: 10, bottom: 30, left: 10},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var rectangles = d3.range(10).map(function() {
return {
x: Math.round(Math.random() * (width)),
y: Math.round(Math.random() * (height))
};
});
var color = d3.scaleOrdinal(d3.schemeCategory20);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var group = svg.selectAll('g')
.data(rectangles).enter().append("g").attr("transform",
"translate(" + margin.left + "," + margin.top + ")")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
group.append("rect")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("height", 60)
.attr("width", 30)
.style("fill", function(d, i) { return color(i); });
group.append("text")
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.attr("text-anchor", "start")
.style("fill", "steelblue")
.text("Close");
function dragstarted(d) {
d3.select(this).raise().classed("active", true);
}
function dragged(d) {
d3.select(this).select("text").attr("x", d.x = d3.event.x).attr("y", d.y = d3.event.y);
d3.select(this).select("rect").attr("x", d.x = d3.event.x).attr("y", d.y = d3.event.y);
}
function dragended(d) {
d3.select(this).classed("active", false);
}
.active {
stroke: #000;
stroke-width: 2px;
}
.rect {
pointer-events: all;
stroke: none;
stroke-width: 40px;
}
<script src="//d3js.org/d3.v4.min.js"></script>
Hope this helps.

You can also try this alternative way of grouping and moving groups.
var margin = {top: 10, right: 10, bottom: 30, left: 10},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var rectangles = d3.range(10).map(function() {
return {
x: Math.round(Math.random() * (width)),
y: Math.round(Math.random() * (height))
};
});
var color = d3.scaleOrdinal(d3.schemeCategory20);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var group = svg.selectAll('g')
.data(rectangles)
.enter().append("g")
.attr("transform",function(d) {
return "translate(" + (margin.left + d.x) + "," + (margin.top + d.y) + ")"
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
group.append("rect")
.attr("height", 60)
.attr("width", 30)
.style("fill", function(d, i) { return color(i); });
group.append("text")
.attr("text-anchor", "start")
.style("fill", "steelblue")
.text("Close");
function dragstarted(d) {
d._drag = {
distance:0,
threshold:2,
initiated: false
};
}
function dragged(d) {
if (!d._drag.initiated) {
d._drag.initiated = true;
d3.select(this).raise().classed("active", true);
}
d._drag.distance += d3.event.dx * d3.event.dx + d3.event.dy * d3.event.dy;
d.x = d3.event.x;
d.y = d3.event.y;
d3.select(this).attr('transform', 'translate('+[d.x,d.y]+')');
}
function dragended(d) {
if (d._drag.distance < d._drag.threshold) {
d3.select(window).on('click.drag', null);
return;
}
d3.select(this).classed("active", false);
}
.active {
stroke: #000;
stroke-width: 2px;
}
.rect {
pointer-events: all;
stroke: none;
stroke-width: 40px;
}
<script src="//d3js.org/d3.v4.min.js"></script>

Related

How to assign id to the bar chart

How can I assign id base on its name?
the link of the screenshot of the console is down below.
Thanks
serie.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("class","bar")
.attr("x", function(d) { return x(d.data.Company); })
.attr("y", function(d) { return y(d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[1]); })
.attr("width", x.bandwidth())
.attr("id",function(d,i){
return "id"+ d.name;
})
I discourage assigning ids to every bar as ids need to be unique. Its better to have a class instead. But as requested, I have posted a fiddle below. Hover on the bars to get the id. And you also said you need the id for animating so I've also added some animations when the chart loads (If that's what you meant by animation).
// Setup svg using Bostock's margin convention
var margin = {
top: 20,
right: 160,
bottom: 35,
left: 30
};
var width = 700 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
/* Data in strings like it would be if imported from a csv */
var data = [{
mystate: "Intuit",
name: "Asian",
y0: 53,
y1: 1824,
value: "1771"
}, {
mystate: "Intuit",
name: "Latino",
y0: 2707,
y1: 1231,
value: "1771"
}, {
mystate: "Intuit",
name: "Black_or_African_American",
y0: 2060,
y1: 1824,
value: "1771"
}, {
mystate: "Intuit",
name: "Caucasian",
y0: 355,
y1: 1024,
value: "1771"
}];
// Transpose the data into layers
var dataset = d3.layout.stack()(['y0', 'y1'].map(function(values) {
return data.map(function(d) {
return {
x: d.name,
y: +d[values],
};
});
}));
// Set x, y and colors
var x = d3.scale.ordinal()
.domain(dataset[0].map(function(d) {
return d.x;
}))
.rangeRoundBands([10, width - 10], 0.02);
var y = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
})])
.range([height, 0]);
var colors = ["b33040", "#d25c4d", "#f2b447", "#d9d574"];
// Define and draw axes
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
.tickSize(-width, 0, 0)
.tickFormat(function(d) {
return d
});
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Create groups for each series, rects for each segment
var groups = svg.selectAll("g.cost")
.data(dataset)
.enter().append("g")
.attr("class", "cost")
.style("fill", function(d, i) {
return colors[i];
});
var rect = groups.selectAll("rect")
.data(function(d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return height;
})
.attr("height", function(d) {
return 0;
})
.attr("width", x.rangeBand())
.attr('id', function(d) {
return 'id' + d.x;
})
.on("mouseover", function() {
console.log(d3.select(this).attr('id'));
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);
}).transition().duration(1000)
.delay(function(d, i) {
return i * 300;
})
.attr("height", function(d) {
return y(d.y0) - y(d.y0 + d.y);
}).attr("y", function(d) {
return y(d.y0 + 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");
svg {
font: 10px sans-serif;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
}
path.domain {
stroke: none;
}
.y .tick line {
stroke: #ddd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.13/d3.min.js"></script>
Now coming back to your code, when you transform the data by using d3.layout.stack() there is no property called name in your new data (I suppose) and hence your ids are idundefined

d3 using zoom and brush on a bar chart

Hi I am trying to adapt Matthew Izanuk's Unit Bar Chart with Brush and Zoom to a randomly generated bar chart I have already created.
I get an error with the zoom function, on line 261
line 261 is
x.domain(t.rescaleX(x2).domain());
Here is the code and jsFiddle
<!DOCTYPE html>
<body>
<style>
div {
display: inline-block;
vertical-align: top;
}
#bar_chart {
border: 2px solid lightgray;
border-radius: 15px;
}
#json {
max-height: 600px;
width: 200px;
overflow: scroll;
border: 2px solid gray;
border-radius: 15px;
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
</style>
<div id="bar_chart">
</div>
<div id="json"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
//************* generate data ************
var data = [];
var space = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var spaceLength = space.length;
makedata();
function makedata() {
var obj = {};
for (var i = 0; i < spaceLength; i++) {
obj = {};
value = Math.floor(Math.random() * 500);
rand = Math.floor(Math.random() * space.length)
name = space.charAt(rand);
obj["name"] = name;
obj["val"] = value;
data.push(obj);
space = space.slice(0, rand) + space.slice(rand + 1, space.length)
}
}
// To display json in html page
document.getElementById("json").innerHTML = "<pre>" + JSON.stringify(data, null, 4) + "</pre>";
var margin = {
top: 50,
right: 20,
bottom: 90,
left: 50
},
margin2 = {
top: 530,
right: 20,
bottom: 30,
left: 50
},
width = 1000 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom,
height2 = 600 - margin2.top - margin2.bottom;
var x = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([0, width]);
var x2 = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.val
})])
.range([height, 0]);
var y2 = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.val
})])
.range([height2, 0]);
var brush = d3.brushX()
.extent([
[0, 0],
[width, height2]
])
.on("brush", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", zoomed);
var svg = d3.select("#bar_chart")
// .data(data)
.append("svg")
.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("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var g0 = focus.append("g")
.attr("class", "focus")
.attr("transform", "translate(0,0)");
var xAxis = d3.axisBottom(x);
var xAxis2 = d3.axisBottom(x2);
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
focus.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y)
.ticks(7));
var tooltip = d3.select("#info")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
var focus_group = focus.append("g");
focus_group.attr("clip-path", "url(#clip)");
var rects = focus_group.selectAll('rect')
.data(data);
//********* Bar Chart 1 ****************
var newRects1 = rects.enter();
newRects1.append('rect')
.attr('class', 'bar mainBars')
.attr('x', function(d, i) {
return x(d.name);
})
.attr('y', function(d, i) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10)
.attr("transform", "translate(" + 4 + ",0)")
.style('fill', 'lightblue')
.style('stroke', 'gray');
var focus_group = context.append("g");
focus_group.attr("clip-path", "url(#clip)");
var brushRects = focus_group.selectAll('rect')
.data(data);
//********* Brush Bar Chart ****************
var brushRects1 = brushRects.enter();
brushRects1.append('rect')
.attr('class', 'bar mainBars')
.attr('x', function(d, i) {
return x2(d.name);
})
.attr('y', function(d, i) {
return y2(d.val);
})
.attr('height', function(d, i) {
return height2 - y2(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10)
.attr("transform", "translate(" + 4 + ",0)")
.style('fill', 'lightblue')
.style('stroke', 'gray');
//append brush xAxis2
context.append("g")
.attr("class", "axis x-axis")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
//create brush function redraw scatterplot with selection
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
focus.selectAll(".mainBars")
.attr("x", function(d) {
return x(d.name);
})
.attr("y", function(d) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10);
focus.select(".x-axis").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
focus.selectAll(".mainBars")
.attr("x", function(d) {
return x(d.name);
})
.attr("y", function(d) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10);
focus.select(".x-axis").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
}
</script>
Any help would be greatly appreciated,
thanks
As mentioned in the comments, invert only exists for continuous ranges. Your scaleBand is a ordinal scale composed of discreet values. One way I've worked around this is to simply figure which values in my domain fall in the selection:
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return;
// ignore brush-by-zoom
// get bounds of selection
var s = d3.event.selection,
nD = [];
// for each "tick" in our domain is it in selection
x2.domain().forEach((d)=>{
var pos = x2(d) + x2.bandwidth()/2;
if (pos > s[0] && pos < s[1]){
nD.push(d);
}
});
// set new domain
x.domain(nD);
// redraw
focus.selectAll(".mainBars")
// hide bars not in domain
.style("opacity", function(d){
return x.domain().indexOf(d.name) === -1 ? 0 : 100;
})
.attr("x", function(d) {
return x(d.name)+ x.bandwidth()/2 - 5;
})
.attr("y", function(d) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10);
focus.select(".x.axis").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
Running code:
<!DOCTYPE html>
<head>
<style>
div {
display: inline-block;
vertical-align: top;
}
#bar_chart {
border: 2px solid lightgray;
border-radius: 15px;
}
#json {
max-height: 600px;
width: 200px;
overflow: scroll;
border: 2px solid gray;
border-radius: 15px;
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
</style>
</head>
<body>
<div id="bar_chart">
</div>
<div id="json"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
//************* generate data ************
var data = [];
var space = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var spaceLength = space.length;
makedata();
function makedata() {
var obj = {};
for (var i = 0; i < spaceLength; i++) {
obj = {};
value = Math.floor(Math.random() * 500);
rand = Math.floor(Math.random() * space.length)
name = space.charAt(rand);
obj["name"] = name;
obj["val"] = value;
data.push(obj);
space = space.slice(0, rand) + space.slice(rand + 1, space.length)
}
}
// To display json in html page
document.getElementById("json").innerHTML = "<pre>" + JSON.stringify(data, null, 4) + "</pre>";
var margin = {
top: 50,
right: 20,
bottom: 90,
left: 50
},
margin2 = {
top: 530,
right: 20,
bottom: 30,
left: 50
},
width = 1000 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom,
height2 = 600 - margin2.top - margin2.bottom;
var x = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([0, width]);
var x2 = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([0, width]);
var y = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.val
})])
.range([height, 0]);
var y2 = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.val
})])
.range([height2, 0]);
var brush = d3.brushX()
.extent([
[0, 0],
[width, height2]
])
.on("brush", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([
[0, 0],
[width, height]
])
.extent([
[0, 0],
[width, height]
])
.on("zoom", zoomed);
var svg = d3.select("#bar_chart")
// .data(data)
.append("svg")
.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("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var xAxis = d3.axisBottom(x);
var xAxis2 = d3.axisBottom(x2);
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
focus.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y)
.ticks(7));
var tooltip = d3.select("#info")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden");
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
var focus_group = focus.append("g");
focus_group.attr("clip-path", "url(#clip)");
var rects = focus_group.selectAll('rect')
.data(data);
//********* Bar Chart 1 ****************
var newRects1 = rects.enter();
newRects1.append('rect')
.attr('class', 'bar mainBars')
.attr('x', function(d, i) {
return x(d.name) + x.bandwidth()/2;
})
.attr('y', function(d, i) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10)
.style('fill', 'lightblue')
.style('stroke', 'gray');
var focus_group = context.append("g");
focus_group.attr("clip-path", "url(#clip)");
var brushRects = focus_group.selectAll('rect')
.data(data);
//********* Brush Bar Chart ****************
var brushRects1 = brushRects.enter();
brushRects1.append('rect')
.attr('class', 'bar mainBars')
.attr('x', function(d, i) {
return x2(d.name);
})
.attr('y', function(d, i) {
return y2(d.val);
})
.attr('height', function(d, i) {
return height2 - y2(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10)
.attr("transform", "translate(" + 4 + ",0)")
.style('fill', 'lightblue')
.style('stroke', 'gray');
//append brush xAxis2
context.append("g")
.attr("class", "axis x-axis")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
//create brush function redraw scatterplot with selection
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
// get bounds of selection
var s = d3.event.selection,
nD = [];
x2.domain().forEach((d)=>{
var pos = x2(d) + x2.bandwidth()/2;
if (pos > s[0] && pos < s[1]){
nD.push(d);
}
});
x.domain(nD);
focus.selectAll(".mainBars")
.style("opacity", function(d){
return x.domain().indexOf(d.name) === -1 ? 0 : 100;
})
.attr("x", function(d) {
return x(d.name)+ x.bandwidth()/2 - 5;
})
.attr("y", function(d) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10);
focus.select(".x.axis").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
function zoomed() {
/*
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
focus.selectAll(".mainBars")
.attr("x", function(d) {
return x(d.name) + x.bandwidth()/2;
})
.attr("y", function(d) {
return y(d.val);
})
.attr('height', function(d, i) {
return height - y(d.val)
})
.attr('opacity', 0.85)
.attr('width', 10);
focus.select(".x-axis").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
*/
}
</script>

D3 Chart version 4 Normalized Stacked Bar Chart from vertical to horizontal

This question is pretty such similar to this D3JS question but I am using latest D3 version(//d3js.org/d3.v4.min.js).
I am trying make this Normalized Stacked Bar Chart
chart horizontal. Is there any optimized way in latest version to achieve this?
I have swapped the x axis and y axis as below
var svg = d3.select("svg"),
margin = {top: 20, right: 60, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var y = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
.align(0.1);
var x = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var stack = d3.stack()
.offset(d3.stackOffsetExpand);
d3.csv("data.csv", type, function(error, data) {
if (error) throw error;
data.sort(function(a, b) { return b[data.columns[1]] / b.total - a[data.columns[1]] / a.total; });
y.domain(data.map(function(d) { return d.State; }));
z.domain(data.columns.slice(1));
var serie = g.selectAll(".serie")
.data(stack.keys(data.columns.slice(1))(data))
.enter().append("g")
.attr("class", "serie")
.attr("fill", function(d) { return z(d.key); });
serie.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.data.State); })
.attr("x", function(d) { return x(d[1]); })
.attr("height", function(d) { return x(d[0]) - x(d[1]); })
.attr("width", y.bandwidth());
g.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(y));
g.append("g")
.attr("class", "axis axis--x")
.call(d3.axisLeft(x).ticks(10, "%"));
var legend = serie.append("g")
.attr("class", "legend")
.attr("transform", function(d) { var d = d[d.length - 1]; return "translate(" + (y(d.data.State) + y.bandwidth()) + "," + ((x(d[0]) + x(d[1])) / 2) + ")"; });
legend.append("line")
.attr("x1", -6)
.attr("x2", 6)
.attr("stroke", "#000");
legend.append("text")
.attr("y", 9)
.attr("dy", "0.35em")
.attr("fill", "#000")
.style("font", "10px sans-serif")
.text(function(d) { return d.key; });
});
function type(d, i, columns) {
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}
Referring the example
You will need to reverse the domains:
var y = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
.align(0.1);
var x = d3.scaleLinear()
.rangeRound([height, 0]);
Swap the x to y as the domains are reverse so x will become y and y will become x, when you create the rectangles.
serie.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("y", function(d) {
return y(d.data.State);
})
.attr("x", function(d) {
return x(d[1]);
})
.attr("width", function(d) {
return x(d[0]) - x(d[1]);
})
.attr("height", y.bandwidth());
Change the legend position accordingly to position it on the top bar.
var legend = serie.append("g")
.attr("class", "legend")
.attr("transform", function(d) {
var d = d[0];//get the top data for placing legends on that.
return "translate(" + ((x(d[0]) + x(d[1])) / 2) + ", " +(y(d.data.State) - y.bandwidth())+ ")";
});
Finally position the legend lines:
legend.append("line")
.attr("y1", 5)
.attr("x1", 15)
.attr("x2", 15)
.attr("y2", 12)
.attr("stroke", "#000");
working code here
Below example also will help you
var initStackedBarChart = {
draw: function(config) {
me = this,
domEle = config.element,
stackKey = config.key,
data = config.data,
margin = {top: 20, right: 20, bottom: 30, left: 50},
parseDate = d3.timeParse("%m/%Y"),
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
xScale = d3.scaleLinear().rangeRound([0, width]),
yScale = d3.scaleBand().rangeRound([height, 0]).padding(0.1),
color = d3.scaleOrdinal(d3.schemeCategory20),
xAxis = d3.axisBottom(xScale),
yAxis = d3.axisLeft(yScale).tickFormat(d3.timeFormat("%b")),
svg = d3.select("#"+domEle).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var stack = d3.stack()
.keys(stackKey)
/*.order(d3.stackOrder)*/
.offset(d3.stackOffsetNone);
var layers= stack(data);
data.sort(function(a, b) { return b.total - a.total; });
yScale.domain(data.map(function(d) { return parseDate(d.date); }));
xScale.domain([0, d3.max(layers[layers.length - 1], function(d) { return d[0] + d[1]; }) ]).nice();
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return yScale(parseDate(d.data.date)); })
.attr("x", function(d) { return xScale(d[0]); })
.attr("height", yScale.bandwidth())
.attr("width", function(d) { return xScale(d[1]) - xScale(d[0]) });
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + (height+5) + ")")
.call(xAxis);
svg.append("g")
.attr("class", "axis axis--y")
.attr("transform", "translate(0,0)")
.call(yAxis);
}
}
var data = [{"date":"4/1854","total":8571,"disease":1,"wounds":0,"other":5},{"date":"5/1854","total":23333,"disease":12,"wounds":0,"other":9},{"date":"6/1854","total":28333,"disease":11,"wounds":0,"other":6},{"date":"7/1854","total":28772,"disease":359,"wounds":0,"other":23},{"date":"8/1854","total":30246,"disease":828,"wounds":1,"other":30},{"date":"9/1854","total":30290,"disease":788,"wounds":81,"other":70},{"date":"10/1854","total":30643,"disease":503,"wounds":132,"other":128},{"date":"11/1854","total":29736,"disease":844,"wounds":287,"other":106},{"date":"12/1854","total":32779,"disease":1725,"wounds":114,"other":131},{"date":"1/1855","total":32393,"disease":2761,"wounds":83,"other":324},{"date":"2/1855","total":30919,"disease":2120,"wounds":42,"other":361},{"date":"3/1855","total":30107,"disease":1205,"wounds":32,"other":172},{"date":"4/1855","total":32252,"disease":477,"wounds":48,"other":57},{"date":"5/1855","total":35473,"disease":508,"wounds":49,"other":37},{"date":"6/1855","total":38863,"disease":802,"wounds":209,"other":31},{"date":"7/1855","total":42647,"disease":382,"wounds":134,"other":33},{"date":"8/1855","total":44614,"disease":483,"wounds":164,"other":25},{"date":"9/1855","total":47751,"disease":189,"wounds":276,"other":20},{"date":"10/1855","total":46852,"disease":128,"wounds":53,"other":18},{"date":"11/1855","total":37853,"disease":178,"wounds":33,"other":32},{"date":"12/1855","total":43217,"disease":91,"wounds":18,"other":28},{"date":"1/1856","total":44212,"disease":42,"wounds":2,"other":48},{"date":"2/1856","total":43485,"disease":24,"wounds":0,"other":19},{"date":"3/1856","total":46140,"disease":15,"wounds":0,"other":35}];
var key = ["wounds", "other", "disease"];
initStackedBarChart.draw({
data: data,
key: key,
element: 'stacked-bar'
});
.axis text {
font: 10px sans-serif;
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.path-line {
fill: none;
stroke: yellow;
stroke-width: 1.5px;
}
svg {
background: #f0f0f0;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id='stacked-bar'></div>

D3 Tree Brushing

I am trying to brush a vertical tree layout.
But the selections are not working.
Please help me with respect to selection of the classed node.
I am very new to D3 and not able to understand this.
Here is my style sheet
.node {
stroke: black;
stroke-width: 1px;
}
.node .selected {
stroke: red;
}
.node text { font: 10px sans-serif; }
.link {
fill: none;
stroke: #ccc;
stroke-width: 1px;
}
.brush .extent {
fill-opacity: .1;
stroke: #fff;
shape-rendering: crispEdges;
Here is my code
var margin = {top: 140, right: 120, bottom: 20, left: 120},
width = 960 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
root = treeData[0];
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 100; });
// Declare the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter the nodes.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
nodeEnter.append("circle")
.attr("r", 10)
.style("fill", "#fff");
nodeEnter.append("text")
.attr("y", function(d) {
return d.children || d._children ? -18 : 18; })
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name; })
.style("fill-opacity", 1);
// Declare the links…
var link = svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
// Enter the links.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", diagonal);
}
var brush = svg.append("g")
.attr("class", "brush")
.call(d3.svg.brush()
.x(d3.scale.identity().domain([0, 1000]))
.y(d3.scale.identity().domain([0, 1000]))
.on("brush",function(){
var extent =d3.event.target.extent();
console.log(extent);
svg.selectAll(".node")
.classed("selected",function(d){
// return true;
return extent[0][1] <= d.x && d.x < extent[1][0]
&& extent[0][1] <= d.y && d.y < extent[1][1];
});
})

Problems creating Sankey diagrams using d3.js (3)

This is in relation to my previous questions "Problems creating Sankey diagrams using d3.js (1 and 2)
Here I'm trying to replicate the example provided by Malcolm Maclean in his book "D3 Tips and Tricks" his implementation of "sankey-formatted-json" works, mine doesn't:>)
My test dataset (TestDrive_ext'json) is
{
"nodes":[
{"node":0,"name":"node0"},
{"node":1,"name":"node1"},
{"node":2,"name":"node2"},
{"node":3,"name":"node3"},
{"node":4,"name":"node4"},
{"node":5,"name":"node5"},
{"node":6,"name":"node6"},
{"node":7,"name":"node7"},
{"node":8,"name":"node8"},
{"node":9,"name":"node9"},
{"node":10,"name":"node10"},
{"node":11,"name":"node11"},
{"node":12,"name":"node12"},
{"node":13,"name":"node13"},
{"node":14,"name":"node14"},
{"node":15,"name":"node15"},
{"node":16,"name":"node16"},
{"node":17,"name":"node17"}
],
"links":[
{"source":3,"target":0,"value":1},
{"source":3,"target":1,"value":2},
{"source":5,"target":1,"value":1},
{"source":0,"target":2,"value":1},
{"source":3,"target":2,"value":1},
{"source":4,"target":2,"value":1},
{"source":16,"target":2,"value":1},
{"source":4,"target":3,"value":4},
{"source":7,"target":3,"value":1},
{"source":12,"target":3,"value":1},
{"source":10,"target":4,"value":1},
{"source":12,"target":4,"value":1},
{"source":13,"target":4,"value":1},
{"source":1,"target":5,"value":1},
{"source":16,"target":5,"value":1},
{"source":13,"target":6,"value":1},
{"source":14,"target":6,"value":1},
{"source":0,"target":7,"value":1},
{"source":2,"target":7,"value":1},
{"source":12,"target":7,"value":1},
{"source":4,"target":8,"value":1},
{"source":16,"target":10,"value":1},
{"source":11,"target":12,"value":1},
{"source":15,"target":12,"value":1},
{"source":7,"target":13,"value":2},
{"source":11,"target":13,"value":1},
{"source":6,"target":14,"value":2},
{"source":0,"target":15,"value":2},
{"source":6,"target":15,"value":3},
{"source":9,"target":15,"value":1},
{"source":12,"target":15,"value":3},
{"source":4,"target":16,"value":1},
{"source":5,"target":16,"value":1},
{"source":9,"target":16,"value":1},
{"source":10,"target":16,"value":1},
{"source":1,"target":17,"value":1},
{"source":6,"target":17,"value":1}
]}
The code copied from Malcolm's example is
<!DOCTYPE html>
<meta charset="utf-8">
<title>SANKEY Experiment</title>
<style>
.node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
.link {
fill: none;
stroke: #000;
stroke-opacity: .2;
}
.link:hover {
stroke-opacity: .5;
}
</style>
<body>
<p id="chart">
<script type="text/javascript" src="d3/d3.v3.js"></script>
<script src="js/sankey.js"></script>
<script>
var units = "Widgets";
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = 700 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"), // zero decimal places
format = function(d) { return formatNumber(d) + " " + units; },
color = d3.scale.category20();
// append the svg canvas to the page
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Set the sankey diagram properties
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
// load the data
d3.json("data/TestDrive_ext.json", function(error, graph) {
sankey
.nodes(graph.nodes)
.links(graph.links)
.layout(32);
// add in the links
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
// add the link titles
link.append("title")
.text(function(d) {
return d.source.name + " → " +
d.target.name + "\n" + format(d.value); });
// add in 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));
// add the rectangles for the nodes
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) {
return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) {
return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) {
return d.name + "\n" + format(d.value); });
// add in the title for the nodes
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
// the function for moving the nodes
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + d.x + "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
) + ")");
sankey.relayout();
link.attr("d", path);
}
});
</script>
</body>
</html>
Again, this doesn't throw any errors in the Javascript console but the diagram doesn't render but as I said previously Malcolm's does
A jsFiddle, which I don't really understand how to interpret, is available at http://jsfiddle.net/Dzy82f/z6bEx/
Once more, any insights that would help me understand where I'm going wrong would be much appreciated
Many thanks
Julian

Resources