I have several graphs set up to zoom on the container and it works great. However, on the initial load, the zoom level is way too close. Is there a method of setting the initial zoom level to avoid having to first zoom out? I am familiar with the .scale() method but have not had any luck implementing it. Is this the way to go or is there something I am missing?
Here is what I have thus far as pertaining to zoom:
var margin = {top: 20, right: 120, bottom: 20, left: 120},
width = 50000 - margin.right - margin.left,
height = 120000 - margin.top - margin.bottom;
var x = d3.scale.linear()
.domain([0, width])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, height])
.range([height, 0]);
var tree = d3.layout.tree()
.size([height, width])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.x, d.y]; });
function zoom(d) {
svg.attr("transform",
"translate(" + d3.event.translate + ")"+ " scale(" + d3.event.scale + ")");
}
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 + ")")
.attr("pointer-events", "all")
.call(d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([0,8])
.on("zoom", zoom))
.append('g');
svg.append('rect')
.attr('width', width*5)
.attr('height', height)
.attr('border-radius', '20')
.attr('fill', 'sienna');
D3v4 answer
If you are here looking for the same but with D3 v4,
var zoom = d3.zoom().on("zoom", function(){
svg.attr("transform", d3.event.transform);
});
vis = svg.append("svg:svg")
.attr("width", width)
.attr("height", height)
.call(zoom) // here
.call(zoom.transform, d3.zoomIdentity.translate(100, 50).scale(0.5))
.append("svg:g")
.attr("transform","translate(100,50) scale(.5,.5)");
I finally got this to work by setting both the initial transform and the zoom behavior to the same value.
var zoom = d3.behavior.zoom().translate([100,50]).scale(.5);
vis = svg.append("svg:svg")
.attr("width", width)
.attr("height", height)
.call(zoom.on("zoom",zooming))
.append("svg:g")
.attr("transform","translate(100,50)scale(.5,.5)");
Applies to d3.js v4
This is similar to davcs86's answer, but it reuses an initial transform and implements the zoom function.
// Initial transform to apply
var transform = d3.zoomIdentity.translate(200, 0).scale(1);
var zoom = d3.zoom().on("zoom", handleZoom);
var svg = d3.select("body")
.append('svg')
.attr('width', 800)
.attr('height', 300)
.style("background", "red")
.call(zoom) // Adds zoom functionality
.call(zoom.transform, transform); // Calls/inits handleZoom
var zoomable = svg
.append("g")
.attr("class", "zoomable")
.attr("transform", transform); // Applies initial transform
var circles = zoomable.append('circle')
.attr("id", "circles")
.attr("cx", 100)
.attr("cy", 100)
.attr('r', 20);
function handleZoom(){
if (zoomable) {
zoomable.attr("transform", d3.event.transform);
}
};
See it in action: jsbin link
Adding this answer as an addendum to the accepted answer in case anyone is still having issues:
The thing that made this really easy to understand was looking here
That being said, I set three variables:
scale, zoomWidth and zoomHeight
scale is the initial scale you want the zoom to be, and then
zoomWidth and zoomHeight are defined as follows:
zoomWidth = (width-scale*width)/2
zoomHeight = (height-scale*height)/2
where width and height are the width and height of the "vis" svg element
the translate above is then amended to be:
.attr("transform", "translate("+zoomWidth+","+zoomHeight+") scale("+scale+")")
as well as the zoom function:
d3.behavior.zoom().translate([zoomWidth,zoomHeight]).scale(scale)
What this does is effectively ensures that your element is zoomed and centered when your visualization is loaded.
Let me know if this helps you! Cheers.
D3JS 6 answer
Let's say that you want your initial position and scale to be x, y, scale respectively.
const zoom = d3.zoom();
const svg = d3.select("#containerId")
.append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom.transform, d3.zoomIdentity.translate(x, y).scale(scale)
.call(zoom.on('zoom', (event) => {
svg.attr('transform', event.transform);
}))
.append("g")
.attr('transform', `translate(${x}, ${y})scale(${k})`);
.call(zoom.transform, d3.zoomIdentity.translate(x, y).scale(scale) makes sure that when the zoom event is fired, the event.transform variable takes into account the translation and the scale. The line right after it handles the zoom while the last one is used to apply the translation and the scale only once on "startup".
I was using d3 with react and was very frustrated about the initial zoom not working.
I tried the solutions here and none of them worked, what worked instead was using an initial scale factor and positions and then updating the zoom function on the basis of those scale factor and positions
const initialScale = 3;
const initialTranslate = [
width * (1 - initialScale) / 2,
height * (1 - initialScale) / 2,
];
const container = svg
.append('g')
.attr(
'transform',
`translate(${initialTranslate[0]}, ${initialTranslate[1]})scale(${initialScale})`
);
The zoom function would look something like this
svg.call(
zoom().on('zoom', () => {
const transformation = getEvent().transform;
let {x, y, k} = transformation;
x += initialTranslate[0];
y += initialTranslate[1];
k *= initialScale;
container.attr('transform', `translate(${x}, ${y})scale(${k})`);
})
);
If you noticed the getEvent() as a function, it was because importing event from d3-selection was not working in my case. So I had to do
const getEvent = () => require('d3-selection').event;
Related
I tried to figure out the difference between 'd3.event.pageX' & 'd3.mouse(this)[0]'.
I guessed both are same but,
when I console.log both,
the value was different by '8' in my code.
var height=600;
var width=600;
var graphgap=60;
d3.csv('./details.csv').then(function(data){
var svg =d3.select('section').append('svg')
.attr('width',600).attr('height',600)
.on('mousemove',mousemove)
drawrect(data);
})
function drawrect(data){
let bars=d3.select('svg').selectAll('rect').data(data);
bars.enter().append('rect').classed('bargraph',true)
.attr('x',function(d,i){return (i+1)*graphgap})
.attr('y',function(d){return height-(d.Age)*5})
.attr('width',55)
.attr('height',function(d){return (d.Age)*(5)})
}
function mousemove(){
let mouselocation =[];
d3.select('svg').append('text')
.text(d3.event.pageX)
.attr('x',d3.event.pageX)
.attr('y',d3.event.pageY)
console.log(d3.event.pageX)
console.log(d3.mouse(this)[0])
}
So, I think these two are two different things.
Can anyone let me know why it makes a difference?
The reason why I tried to figure this out is because I was re-writing the code below.
<script>
// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
//Read the data
d3.csv("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/data_IC.csv",function(data) {
// Add X axis --> it is a date format
var x = d3.scaleLinear()
.domain([1,100])
.range([ 0, width ]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add Y axis
var y = d3.scaleLinear()
.domain([0, 13])
.range([ height, 0 ]);
svg.append("g")
.call(d3.axisLeft(y));
// This allows to find the closest X index of the mouse:
var bisect = d3.bisector(function(d) { return d.x; }).left;
// Create the circle that travels along the curve of chart
var focus = svg
.append('g')
.append('circle')
.style("fill", "none")
.attr("stroke", "black")
.attr('r', 8.5)
.style("opacity", 0)
// Create the text that travels along the curve of chart
var focusText = svg
.append('g')
.append('text')
.style("opacity", 0)
.attr("text-anchor", "left")
.attr("alignment-baseline", "middle")
// Create a rect on top of the svg area: this rectangle recovers mouse position
svg
.append('rect')
.style("fill", "none")
.style("pointer-events", "all")
.attr('width', width)
.attr('height', height)
.on('mouseover', mouseover)
.on('mousemove', mousemove)
.on('mouseout', mouseout);
// Add the line
svg
.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return x(d.x) })
.y(function(d) { return y(d.y) })
)
// What happens when the mouse move -> show the annotations at the right positions.
function mouseover() {
focus.style("opacity", 1)
focusText.style("opacity",1)
}
function mousemove() {
// recover coordinate we need
var x0 = x.invert(d3.mouse(this)[0]);
var i = bisect(data, x0, 1);
selectedData = data[i]
focus
.attr("cx", x(selectedData.x))
.attr("cy", y(selectedData.y))
focusText
.html("x:" + selectedData.x + " - " + "y:" + selectedData.y)
.attr("x", x(selectedData.x)+15)
.attr("y", y(selectedData.y))
}
function mouseout() {
focus.style("opacity", 0)
focusText.style("opacity", 0)
}
})
</script>
In documentation is written:
While you can use the native event.pageX and event.pageY, it is often
more convenient to transform the event position to the local
coordinate system of the container that received the event using
d3.mouse, d3.touch or d3.touches.
d3.event
d3.mouse - uses local coordinate (without margin (60px))
d3.event.pageX - uses global coordinate (with margin (60px))
But local cordinate start on 68px. I guess 8 pixels is used to describe the y-axis.
I'm creating a timeline line chart showing 5 or 6 different lines and want to be able to zoom in and scroll (once zoomed). I used some examples that use area charts but for some reason my line chart jumps to the right when I zoom the first time and I lose some of the data off to the right (I can no longer scroll to see it or see it when zoomed fully out). Also the lines appear over the y axis when I zoom or scroll.
I've copied it to JSFiddle (see here) with a dataset from 1 of the lines in my chart. Why is the line jumping to the right as soon as you use the zoom function? How can I stop the line from appearing over the y-axis?
Here is the JS of my version if you'd prefer to read it here:
function drawTimeline() {
var margin = {top: 10, right: 0, bottom: 50, left: 60}
var width = d3.select('#timeline').node().getBoundingClientRect().width/3*2;
var height = 300;
var svg = d3.select("#timeline").append("svg")
.attr("width", width)
.attr("height", height+margin.top+margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("width", width - margin.left - margin.right)
.call(d3.zoom()
// .extent()
.scaleExtent([1, 10])
.translateExtent([[0, -Infinity], [width - margin.left - margin.right, Infinity]])
.on("zoom", zoom)
);
var view = svg.append("rect")
.attr("class", "view")
.attr("width", width - margin.left - margin.right)
.attr("height", height)
.attr("fill", "white");
var x = d3.scaleTime()
.domain([
d3.min(poll_data[0].avgpolls, function(p) { return p.date; }),
d3.max(poll_data[0].avgpolls, function(p) { return p.date; })
])
.range([0, width - margin.left - margin.right]);
var y = d3.scaleLinear()
.domain([50, 0])
.range([0, height]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var gX = svg.append("g")
.attr("class", "axis xaxis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
var gY = svg.append("g")
.attr("class", "axis yaxis")
.call(yAxis);
//All lines are drawn in the same way (x and y points)
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.poll); });
//selectAll allows us to create and manipulate multiple groups at once
var party = svg.selectAll(".party")
.data(poll_data)
.enter()
.append("g")
.attr("class", "party");
//Add path to every country group at once
var pollPaths = party.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.avgpolls); })
.attr("fill", "none")
.attr("stroke-width", 2)
.style("stroke", function(d) { return returnPartyColour(d.party); });
function zoom() {
console.log("zooming: " + d3.event.transform);
var new_x = d3.event.transform.rescaleX(x);
gX.call(d3.axisBottom(new_x));
//view.attr("transform", d3.event.transform);
//Redraw lines
//pollPaths.select(".line").attr("d", function(d) { return line(d.avgpolls); });
var newline = d3.line()
.x(function(d) { return new_x(d.date); })
.y(function(d) { return y(d.poll); });
pollPaths.attr("d", function(d) { return line(d.avgpolls); });
}
}
The data is formatted like this in my version but not the JSFiddle:
poll_data = [
{
'party' : 'Party 1',
'avgpolls' : [
{'date' : new Date(year, month, day), 'poll' : 0, },
],
]
Thanks
Two things required to fix these issues.
The reason the line was jumping on zoom was because the zoom extent was not set. This was set and the value of translateExtent updated:
d3.zoom()
.scaleExtent([1, 10])
.translateExtent([[0, 0], [width - margin.left - margin.right, Infinity]])
.extent([[0, 0], [width - margin.left - margin.right, height]])
.on("zoom", zoom)
To prevent the paths from overflowing a clipping path is required. After creating the svg, before other elements are added, I added a clip-path as follows:
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
//Same dimensions as the area for the lines to appear
.attr("width", width - margin.left - margin.right)
.attr("height", height);
Then this had to be added to each path.
var pollPaths = party.append("path")
...
.attr("clip-path", "url(#clip)");
The original JSFiddle has been updated to reflect these changes.
I'm trying to implement a chart framework which is able to create line and area charts with multiple axes for each orientation, i.e 2-y-axis left, 1-yaxis right and 1-x-axis bottom.
That I got to work. My next task would be to implement zoom behaviour to the chart. I indented to have a global zoom behaviour, which is trigged if the user uses his mouse within the plot area. The displayed series would get rescaled and it would be possible to pan the plot. This one I got to work too.
In addition I wanted an independent zoom/scaling for each axis. I got the scaling, but I still have problems with the global zooming and panning. If I scale one axis, the associated series in the plot area gets rescaled but the panning does not work. And after the independent scaling of an axis, if I use the global rescale, the scaling gets reset and then gets scaled by the global zoom behaviour.
On the d3.js page I found an simple example for independent and global scaling and panning, but written with d3v3 .
I changed the example in such a way, so that it displays my problem jsfiddle demo. Use you mouse on the axes and in the plot area.
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Independent Axis Zooms on x, y, or xy</title>
<script src="//d3js.org/d3.v4.min.js"></script>
<style>
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<div id="chart"></div>
<script>
var data = [];
for (var i = 0; i < 100; i++) {
data.push([Math.random(), Math.random()]);
}
var svg = d3.select('#chart')
.append("svg")
.attr("width", window.innerWidth).attr("height", window.innerHeight);
function example(svg, data) {
var svg;
var margin = {
top: 60,
bottom: 80,
left: 60,
right: 0
};
var width = 500;
var height = 400;
var xaxis = d3.axisBottom();
var yaxis = d3.axisLeft();
var xscale = d3.scaleLinear();
var yscale = d3.scaleLinear();
var xcopyScale, ycopyScale;
var xyzoom, xzoom, yzoom;
updateZooms();
function update() {
var gs = svg.select("g.scatter");
var circle = gs.selectAll("circle")
.data(data);
circle.enter().append("svg:circle")
.attr("class", "points")
.style("fill", "steelblue")
.attr("cx", function (d) {
return X(d);
})
.attr("cy", function (d) {
return Y(d);
})
.attr("r", 4);
circle.attr("cx", function (d) {
return X(d);
})
.attr("cy", function (d) {
return Y(d);
});
circle.exit().remove();
}
function updateZooms() {
xyzoom = d3.zoom()
.on("zoom", function () {
xaxis.scale(d3.event.transform.rescaleX(xscale));
yaxis.scale(d3.event.transform.rescaleY(yscale));
draw();
});
xzoom = d3.zoom()
.on("zoom", function () {
xaxis.scale(d3.event.transform.rescaleX(xscale));
draw();
});
yzoom = d3.zoom()
.on("zoom", function () {
yaxis.scale(d3.event.transform.rescaleY(yscale));
draw();
});
}
function draw() {
svg.select('g.x.axis').call(xaxis);
svg.select('g.y.axis').call(yaxis);
update();
// After every draw, we reinitialize zoom. After every zoom, we reexecute draw, which will reinitialize zoom.
// This is how we can apply multiple independent zoom behaviors to the scales.
// (Note that the zoom behaviors will always end up with zoom at around 1.0, and translate at around [0,0])
svg.select('rect.zoom.xy.box').call(xyzoom);
svg.select('rect.zoom.x.box').call(xzoom);
svg.select('rect.zoom.y.box').call(yzoom);
}
// X value to scale
function X(d) {
return xaxis.scale() !== undefined && xaxis.scale() !== null
? xaxis.scale()(d[0])
: xscale(d[0]);
}
// Y value to scale
function Y(d) {
return yaxis.scale() !== undefined && yaxis.scale() !== null
? yaxis.scale()(d[1])
: yscale(d[1]);
}
var g = svg.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
g.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom);
g.append("svg:rect")
.attr("class", "border")
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom)
.style("stroke", "black")
.style("fill", "none");
g.append("g").attr("class", "x axis")
.attr("transform", "translate(" + 0 + "," + (height - margin.top - margin.bottom) + ")");
g.append("g").attr("class", "y axis");
g.append("g")
.attr("class", "scatter")
.attr("clip-path", "url(#clip)");
g
.append("svg:rect")
.attr("class", "zoom xy box")
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom)
.style("visibility", "hidden")
.attr("pointer-events", "all")
.call(xyzoom);
g
.append("svg:rect")
.attr("class", "zoom x box")
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom)
.attr("transform", "translate(" + 0 + "," + (height - margin.top - margin.bottom) + ")")
.style("visibility", "hidden")
.attr("pointer-events", "all")
.call(xzoom);
g
.append("svg:rect")
.attr("class", "zoom y box")
.attr("width", margin.left)
.attr("height", height - margin.top - margin.bottom)
.attr("transform", "translate(" + -margin.left + "," + 0 + ")")
.style("visibility", "hidden")
.attr("pointer-events", "all")
.call(yzoom);
// Update the x-axis
xscale.domain(d3.extent(data, function (d) {
return d[0];
})).range([0, width - margin.left - margin.right]);
xaxis.scale(xscale)
.tickPadding(10);
svg.select('g.x.axis').call(xaxis);
// Update the y-scale.
yscale.domain(d3.extent(data, function (d) {
return d[1];
})).range([height - margin.top - margin.bottom, 0]);
yaxis.scale(yscale)
.tickPadding(10);
svg.select('g.y.axis').call(yaxis);
draw();
}
var exampleChart = example(svg, data);
</script>
</body>
</html>
To put it briefly: How can I solve my problem by using d3v4 to create a chart with multiple axes that has global and independent scaling and panning behaviour ?
as of now the current release of d3v4 doesn't natively support multiple independent zoom behaviours.
A possible solution would be to reset the internal transform state stored inside the selection on which you called the appropriate zoom behaviour.
There is an already open issue on the argument and i encourage you to go and read it and offer your input as well.
Best of luck!
There seems to be no correct solution (see https://github.com/d3/d3-zoom/issues/48).
But if you clear the scale after each zoom it seems to work
(see https://jsfiddle.net/DaWa/dLmp8zk8/2/).
I'm trying to create a draggable element in D3 that doesn't drag beyond the page.
Like in this example:
http://jsfiddle.net/Lhwpff2r/1/
var margin = {top: 20, right: 10, bottom: 20, left: 10};
var width = 600;
var height = 600;
var zoom = d3.behavior.zoom()
.scaleExtent([0.5, 10])
.on("zoom", zoomed);
var svg = d3.select('body').append('svg')
.attr('class', 'chart')
.attr('width', width)
.attr('height', height)
.append('g')
.attr("transform", "translate(" + margin.left + "," + margin.right + ")")
.call(zoom);
rect = svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all");
svg.append("g")
.append("rect")
.attr("class", "rectangle")
.attr('width', width)
.attr('height', height);
function zoomed() {
console.log('1: ', zoom.translate());
if (zoom.translate()[0] > 0) {
//zoom.translate([0, zoom.translate()[1]]);
//console.log('2: ', zoom.translate());
}
svg.attr("transform", "translate(" + zoom.translate() + ")scale(" + zoom.scale() + ")");
}
In other words, I don't want to see the background behind the red rectangle.
For this I'm checking whether the zoom.translate()[0] is bigger than 0 and manually setting it to 0.
This works fine, but the problem is that when I drag to the left again the zoom.translate() x value is still the original one, not the one that I manually set (unless I release the mouse click before dragging to the left).
Does anyone know how I make the zoom.translate value persist?
Thanks.
I have a vertical bar chart that is grouped in pairs. I was trying to play around with how to flip it horizontally. In my case, the keywords would appear on the y axis, and the scale would appear on the x-axis.
I tried switching various x/y variables, but that of course just produced funky results. Which areas of my code do I need to focus on in order to switch it from vertical bars to horizontal ones?
My JSFiddle: Full Code
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([0, w], 0.05);
// ternary operator to determine if global or local has a larger scale
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) {
return (d.local > d.global) ? d.local : d.global;
})])
.range([h, 0]);
var xAxis = d3.svg.axis()
.scale(xScale)
.tickFormat(function (d) {
return dataset[d].keyword;
})
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
var commaFormat = d3.format(',');
//SVG element
var svg = d3.select("#searchVolume")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Graph Bars
var sets = svg.selectAll(".set")
.data(dataset)
.enter()
.append("g")
.attr("class", "set")
.attr("transform", function (d, i) {
return "translate(" + xScale(i) + ",0)";
});
sets.append("rect")
.attr("class", "local")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.local);
})
.attr("x", xScale.rangeBand() / 2)
.attr("height", function (d) {
return h - yScale(d.local);
})
.attr("fill", colors[0][1])
;
sets.append("rect")
.attr("class", "global")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.global);
})
.attr("height", function (d) {
return h - yScale(d.global);
})
.attr("fill", colors[1][1])
;
sets.append("rect")
.attr("class", "global")
.attr("width", xScale.rangeBand() / 2)
.attr("y", function (d) {
return yScale(d.global);
})
.attr("height", function (d) {
return h - yScale(d.global);
})
.attr("fill", colors[1][1])
;
I just did the same thing last night, and I basically ended up rewriting the code as it was quicker than fixing all the bugs but here's the tips I can give you.
The biggest issues with flipping the x and y axis will be with things like return h - yScale(d.global) because height is calculated from the "top" of the page not the bottom.
Another key thing to remember is that when you set .attr("x", ..) make sure you set it to 0 (plus any padding for the left side) so = .attr("x", 0)"
I used this tutorial to help me think about my own code in terms of horizontal bars instead - it really helped
http://hdnrnzk.me/2012/07/04/creating-a-bar-graph-using-d3js/
here's my own code making it horizontal if it helps:
var w = 600;
var h = 600;
var padding = 30;
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d){
return d.values[0]; })]) //note I'm using an array here to grab the value hence the [0]
.range([padding, w - (padding*2)]);
var yScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([padding, h- padding], 0.05);
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0 + padding)
.attr("y", function(d, i){
return yScale(i);
})
.attr("width", function(d) {
return xScale(d.values[0]);
})
.attr("height", yScale.rangeBand())
An alternative is to rotate the chart (see this). This is a bit hacky as then you need to maintain the swapped axes in your head (the height is actually the width etc), but it is arguably simpler if you already have a working vertical chart.
An example of rotating the chart is below. You might need to rotate the text as well to make it nice.
_chart.select('g').attr("transform","rotate(90 200 200)");
Here is the procedure I use in this case:
1) Inverse all Xs and Ys
2) Remember that the 0 for y is on top, thus you will have to inverse lots of values as previous values for y will be inversed (you don't want your x axis to go from left to right) and the new y axis will be inversed too.
3) Make sure the bars display correctly
4) Adapt legends if there are problems
This question may help in the sense that it shows how to go from horizontal bar charts to vertical: d3.js histogram with positive and negative values