I'm playing around with D3 and want tick lines to cut through a linear time graph across the vertical axis. The tick line elements are there, with the correct vectors, but they do not appear. What appears instead is the path element that runs horizontally with the tick labels.
JSFiddle Link
var width = 960;
var height = 200;
var container = d3.select(".timeTable");
var svg = container.append("svg")
.attr("width", width)
.attr("height", height);
var roomID = container.attr("data-room");
var times = [
{"from":"2012-12-27 00:00:00","until":"2012-12-27 12:00:00"},
{"from":"2012-12-27 00:00:00","until":"2012-12-27 23:59:00"},
{"from":"2012-12-27 02:00:00","until":"2012-12-27 04:00:00"},
{"from":"2012-12-27 03:00:00","until":"2012-12-27 21:00:00"},
{"from":"2012-12-27 03:30:00","until":"2012-12-27 04:50:00"},
{"from":"2012-12-27 05:00:00","until":"2012-12-27 12:00:00"},
{"from":"2012-12-27 09:00:00","until":"2012-12-27 15:00:00"},
{"from":"2012-12-27 13:00:00","until":"2012-12-27 23:00:00"},
{"from":"2012-12-27 13:00:00","until":"2012-12-27 23:30:00"},
{"from":"2012-12-27 20:00:00","until":"2012-12-27 23:59:00"},
{"from":"2012-12-27 20:00:00","until":"2012-12-27 22:00:00"},
{"from":"2012-12-27 23:00:00","until":"2012-12-27 23:30:00"},
{"from":"2012-12-28 01:00:00","until":"2012-12-28 13:00:00"}
];
function draw(times) {
// domain
var floor = d3.time.day.floor(d3.min(times, function (d) { return new Date(d.from); }));
var ceil = d3.time.day.ceil(d3.max(times, function (d) { return new Date(d.until); }));
// define linear time scale
var x = d3.time.scale()
.domain([floor, ceil])
.rangeRound([0, width]);
// define x axis
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom')
.ticks(d3.time.hours, 6)
.tickSize(4);
// draw time bars
svg.selectAll("rect")
.data(times)
.enter().append("rect")
.attr("class", "timeRange")
.attr("width", function (d, i) { return x(new Date(d.until)) - x(new Date(d.from)); })
.attr("height", "10px")
.attr("x", function (d, i) { return x(new Date(d.from)); })
.attr("y", function (d, i) { return i * 11; });
// draw x axis
svg.append("g")
.attr("class", "xAxis")
.attr("transform", "translate(0, " + (height - 23) + ")")
.call(xAxis);
}
draw(times);
The path element generated simply overlaps the ticks, but the ticks are not visible even with path removed.
The desired tick effect is shown here Population Pyramid - ticks on the vertical axis have a line that cuts through the rest of the graph.
Is there different behavior I need to be aware of for time scales?
Much appreciated.
Chrome 23, D3 v3
The trick to getting the tick lines into the plot area is to actually make a second axis and hide the labels. So your code plus the grid lines looks something like (fiddle):
// draw x axis
var xAxisLine = svg.append("g")
.attr("class", "xAxis")
.attr("transform", "translate(0, " + (height - 23) + ")")
.call(xAxis);
xAxisLine.selectAll("path.domain").attr("stroke","black").style('fill','none');
xAxisLine.selectAll("line.tick").style('stroke','black');
xAxis.tickSize(height-23);
var xAxisLineOver = svg.append("g")
.attr("class", "xAxis-overlay")
.attr("transform", "translate(0,0)")
.call(xAxis);
xAxisLineOver.selectAll("path.domain").attr("stroke-width",0).style('fill','none');
xAxisLineOver.selectAll("line.tick").style('stroke','red');
xAxisLineOver.selectAll("text").text("");
I'm not sure this is the exact same problem I had. What worked for me was:
.tick line{
stroke: black
}
Chrome is very strict regarding rendering SVG. So keep this in mind:
For axes specify their full RGB value (so #ff0000 instead of just #f00).
Path stroke widths are tricky. If they are less than 1 (px) and you have included
shape-rendering: crispEdges;
in CSS styles than any web browser might not display such path (or when the chart is resized to different size).
In that case comment or delete the "crispEdges" rendering and you should be fine (though in this case you leave the browser the smoothing decision).
Related
I have been able to make a scatter plot with zoom and pan functionality where the axes scale properly and everything works well. Now I am trying to figure out how to add gridlines, but running into some issues. I have started with only adding x-axis gridlines to figure things out. I have attached a fiddle with a working example to build from.
I commented out the initial gridlines when the graph is generated, because they would remain after zooming causing clutter, and I will add them back later when I get things working. When zooming the gridlines appear to be drawn correctly, but they do not match up with the x-axis labels, and the x-axis labels disappear after zooming or panning.
If you comment out line 163 and uncomment line 164 you can see the basic graph without any gridlines. Clicking the plot button will always generate a new graph. I have left behind some commented out code of different things that I have tried from searching through stackoverflow.
Example is using d3.js - 5.9.2
JSFiddle: https://jsfiddle.net/eysLvqkh/11/
HTML:
<div id="reg_plot"></div>
<button id="b" class="myButton">plot</button>
Javascript:
var theButton = document.getElementById("b");
theButton.onclick = createSvg;
function createSvg() {
// clear old chart when 'plot' is clicked
document.getElementById('reg_plot').innerHTML = ""
// dimensions
var margin = {top: 20, right: 20, bottom: 30, left: 55},
svg_dx = 1200,
svg_dy =600,
chart_dx = svg_dx - margin.right - margin.left,
chart_dy = svg_dy - margin.top - margin.bottom;
// data
var y = d3.randomNormal(400, 100);
var x_jitter = d3.randomUniform(-100, 1400);
var d = d3.range(1000)
.map(function() {
return [x_jitter(), y()];
});
// fill
var colorScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([0, 1]);
// y position
var yScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([chart_dy, margin.top]);
// x position
var xScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[0]; }))
.range([margin.right, chart_dx]);
// y-axis
var yAxis = d3.axisLeft(yScale);
// x-axis
var xAxis = d3.axisBottom(xScale);
// append svg to div element 'reg_plot' and set zoom to our function named 'zoom'
var svg = d3.select("#reg_plot")
.append("svg")
.attr("width", svg_dx)
.attr("height", svg_dy);
svg.call(d3.zoom().on("zoom", zoom));
// clip path - sets boundaries so points will not show outside of the axes when zooming/panning
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr('width', chart_dx)
.attr('height', chart_dy);
// plot data
var circles = svg.append("g")
.attr("id", "circles")
.attr("transform", "translate(75, 0)")
.attr("clip-path", "url(#clip)")
.selectAll("circle")
.data(d)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) { return xScale(d[0]); })
.attr("cy", function(d) { return yScale(d[1]); })
.style("fill", function(d) {
var norm_color = colorScale(d[1]);
return d3.interpolateInferno(norm_color)
});
// add y-axis
var y_axis = svg.append("g")
.attr("id", "y_axis")
.attr("transform", "translate(75,0)")
.call(yAxis).style("font-size", "10px")
// add x-axis
var x_axis = svg.append("g")
.attr("id", "x_axis")
.attr("transform", `translate(${margin.left}, ${svg_dy - margin.bottom - margin.top})`)
.call(xAxis).style("font-size", "10px")
// add x and y grid lines
x_axis.call(xAxis.scale(xScale).ticks(20).tickSize(-chart_dy));
y_axis.call(yAxis.scale(yScale).ticks(20).tickSize(-chart_dx));
function zoom(e) {
// re-scale y axis during zoom
y_axis.transition()
.duration(50)
.call(yAxis.scale(d3.event.transform.rescaleY(yScale)));
// re-scale x axis during zoom
x_axis.transition()
.duration(50)
.call(xAxis.scale(d3.event.transform.rescaleX(xScale)));
// re-draw circles using new scales
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
// re-scale axes and gridlines
x_axis.call(xAxis.scale(new_xScale).ticks(20).tickSize(-chart_dy));
y_axis.call(yAxis.scale(new_yScale).ticks(20).tickSize(-chart_dx));
circles.data(d)
.attr('cx', function(d) {return new_xScale(d[0])})
.attr('cy', function(d) {return new_yScale(d[1])});
}
}
For anyone looking, I have solved this problem. I have updated the javascript in the original post, and updated the jsfiddle. If you are copying this code to your local machine where you are using d3.js 7.4.4 or higher then you need to change the lines that say d3.event.transform.... to just e.transform.
I am creating a mapping application in d3 and want to tie some text to the top right corner of my view port. Additionally, I want the text to remain in the top right corner while I zoom and pan across the application.I think I can solve my problem by figuring out how to get the coordinates of the top right corner of my view. Knowing this information would allow me to then set the coordinates of my text element. I've tried manually setting the dimensions of the containing svg element and then moving the text to that location but interestingly this didn't work. I was hoping to be able to find the coordinates programatically rather than setting coordinates manually. How can I do this in d3/javascript?
EDIT:
My code is a modification of this code by Andy Barefoot: https://codepen.io/nb123456/pen/zLdqvM
My own zooming and panning code has essentially remained the same as the above example:
function zoomed() {
t = d3
.event
.transform
;
countriesGroup
.attr("transform","translate(" + [t.x, t.y] + ")scale(" + t.k + ")")
;
}
I'm trying to append the text at the very bottom of the code:
countriesGroup.append("text")
.attr("transform", "translate(" How do I get top right coordinates? ")")
.style("fill", "#ff0000")
.attr("font-size", "50px")
.text("This is a test");
My idea is to be able to get the top right coordinates of the view port through the code rather than setting it manually and then have the coordinates of the text update as the user zooms or pans.
To keep something in place while zooming and panning you could invert the zoom:
point == invertZoom(applyZoom(point))
This isn't particularly efficient, as we are using two operations to get to the original number. The zoom is applied here:
countriesGroup
.attr("transform","translate(" + [t.x, t.y] + ")scale(" + t.k + ")");
While the inversion would need to look something like:
text.attr("x", d3.zoom.transform.invert(point)[0])
.attr("y", d3.zoom.transform.invert(point)[1])
.attr("font-size", baseFontSize / d3.zoom.transform.k);
Where point and base font size are the original anchor point and font size. This means storing that data somewhere. In the example below I assign it as a datum to the text element:
var width = 500;
var height = 200;
var data = d3.range(100).map(function() {
return {x:Math.random()*width,y:Math.random()*height}
})
var zoom = d3.zoom()
.on("zoom",zoomed);
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
.call(zoom);
var g = svg.append("g")
var circles = g.selectAll()
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 5)
.attr("fill","steelblue")
var text = g.append("text")
.datum({x: width-10, y: 20, fontSize: 12})
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.style("text-anchor","end")
.attr("font-size",function(d) { return d.fontSize; })
.text("This is a test");
function zoomed() {
g.attr("transform", d3.event.transform);
var d = text.datum();
var p = d3.event.transform.invert([d.x,d.y]);
var x1 = p[0];
var y1 = p[1];
text.attr("x",x1)
.attr("y",y1)
.attr("font-size", d.fontSize / d3.event.transform.k)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Better Solution
The above is the solution to the approach you seem to be looking for. But the end result is best achieved by a different method. As I mention in my comment, the above approach goes through extra steps that can be avoided. There can also be some size/clarity changes in the text when zooming (quickly) using the above method
As noted above, you are applying the zoom here:
countriesGroup
.attr("transform","translate(" + [t.x, t.y] + ")scale(" + t.k + ")")
The zoom transform is applied only to countriesGroup, if your label happens to be in a different g (and not a child of countriesGroup), it won't be scaled or panned.
We wouldn't need to apply and invert the zoom, and we wouldn't need to update the position or font size of the text at all.
var width = 500;
var height = 200;
var data = d3.range(100).map(function() {
return {x:Math.random()*width,y:Math.random()*height}
})
var zoom = d3.zoom()
.on("zoom",zoomed);
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height)
.call(zoom);
var g = svg.append("g");
var g2 = svg.append("g"); // order does matter in layering
var circles = g.selectAll()
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 5)
.attr("fill","steelblue")
// position once and leave it alone:
var text = g2.append("text")
.attr("x", width - 10)
.attr("y", 20 )
.style("text-anchor","end")
.attr("font-size", 12)
.text("This is a test");
function zoomed() {
// apply the zoom to the g that has zoomable content:
g.attr("transform", d3.event.transform);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
I am using D3.js v4.
I have a minimum example working with zooming in and out on a single axis, with the following code:
// Create dummy data
var data = [];
for (var i = 0; i < 100; i++) {
data.push([Math.random(), Math.random()]);
}
// Set window parameters
var width = 330
var height = 200
// Append div, svg
d3.select('body').append('div')
.attr('id', 'div1')
d3.select('#div1')
.append("svg").attr("width", width).attr("height",height)
.attr('id','chart')
// Create scaling factors
var x = d3.scaleLinear()
.domain([0,1])
.range([0, (width - 30)])
var y = d3.scaleLinear()
.domain([0,1])
.range([0,height])
// Create group, then append circles
d3.select('#chart').append('g')
.attr('id','circlesplot')
d3.select('#circlesplot')
.selectAll('circles')
.data(data)
.enter().append('circle')
.attr('cx', function(d,i){ return x(d[0]); })
.attr('cy', function(d,i){ return y(d[1]); })
.attr('r', 4)
// Create y axis, append to chart
var yaxis = d3.axisRight(y)
.ticks(10)
var yaxis_g = d3.select('#chart').append('g')
.attr('id', 'yaxis_g')
.attr('transform','translate(' + (width - 30) +',0)')
.call(yaxis)
// Create zoom svg to the right
var svg = d3.select('#div1')
.append('svg')
.attr('width', 30)
.attr('height', height)
.attr('transform', 'translate('+ width + ',0)')
.call(d3.zoom()
.on('zoom', zoom))
function zoom() {
// Rescale axis during zoom
yaxis_g.transition()
.duration(50)
.call(yaxis.scale(d3.event.transform.rescaleY(y)))
// re-draw circles using new y-axis scale
var new_y = d3.event.transform.rescaleY(y);
d3.selectAll('circle').attr('cy', function(d) { return new_y(d[1])})
}
fiddle here: https://jsfiddle.net/v0aw9Ler/#&togetherjs=2wg7s8xfhC
Putting the mouse just to the right of the yaxis and scrolling gives the zooming function on the y axis.
What I'd like to happen is for the y axis maximum (in this case 1.0) to stay fixed, while zooming only in the other direction. You can kind of see what I mean by placing the mouse at the very bottom and just to the right of the y axis, and see the points cluster at the bottom of the graph.
I think it has to do with using zoom.extent(), but I'm just really not sure where to go from here; advice is greatly appreciated.
Source for this min working example:
http://bl.ocks.org/feyderm/03602b83146d69b1b6993e5f98123175
I am new to D3.js and have a problem with my vertical bar chart. For some reason, the distance between the axis and the bars is way too big when I use rangeRoundBands for scaling.
In the API, it is explained like this:
So the problem seems to be the outerPadding. But setting the outerPadding to zero does not help. However, when I use rangeBands instead, the problem disappears and the bars are positioned correctly, right below the axis. But then I will get these nasty antialiasing effects, so this is not really an option. Here is my code:
var margin = {top: 40, right: 40, bottom: 20, left: 20},
width = 900 - margin.left - margin.right,
height = x - margin.top - margin.bottom;
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.ordinal().rangeRoundBands([0, height], .15, 0);
var xAxis = d3.svg.axis()
.scale(x)
.orient("top");
var xAxis2 = d3.svg.axis()
.scale(x)
.orient("bottom");
var xAxis3 = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height, 0, 0)
.tickFormat("");
var svg = d3.select("#plotContainer").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 + ")");
x.domain(d3.extent(data, function(d) {
return d.size;
})).nice();
y.domain(data.map(function(d) {
return d.name;
}));
svg.append("g")
.attr("class", "x axis")
.call(xAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis2);
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(xAxis3);
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", function(d) {
return d.size < 0 ? "bar negative" : "bar positive";
})
.attr("x", function(d) {
return x(Math.min(0, d.size));
})
.attr("y", function(d) {
return y(d.name);
})
.attr("width", function(d) {
return Math.abs(x(d.size) - x(0));
})
.attr("height", y.rangeBand())
.append("title")
.text(function(d) {
return "This value is " + d.name;
});
;
svg.selectAll(".bar.positive")
.style("fill", "steelblue")
.on("mouseover", function(d) {
d3.select(this).style("fill", "yellow");
})
.on("mouseout", function(d) {
d3.select(this).style("fill", "steelblue");
});
svg.selectAll(".bar.negative")
.style("fill", "brown")
.on("mouseover", function(d) {
d3.select(this).style("fill", "yellow");
})
.on("mouseout", function(d) {
d3.select(this).style("fill", "brown");
});
svg.selectAll(".axis")
.style("fill", "none")
.style("shape-rendering", "crispEdges")
.style("stroke", "#000")
.style("font", "10px sans-serif");
svg.selectAll(".grid")
.style("fill", "none")
.style("stroke", "lightgrey")
.style("opacity", "0.7");
svg.selectAll(".grid.path")
.style("stroke-width", "0");
EDIT:
Please take a look at this fiddle:
http://jsfiddle.net/GUYZk/9/
My problem is reproducible there. You cannot alter the outerPadding with rangeRoundBands, whereas rangeBands behaves normal.
TL;DR: this is a consequence of the math. To work around, use rangeBands to lay out the bars, and use shape-rendering: crispEdges in CSS to align them to pixel boundaries.
Full explanation:
Because the rangeRoundBands function must distribute the bars evenly throughout the provided pixel range AND it must also provide an integer rangeBand, it uses Math.floor to chop off the fractional bit of each successive bar.
The reason this extra outer padding compounds with longer datasets is because all those fractional pixels have to end up somewhere. The author of this function chose to evenly split them between the beginning and the end of the range.
Because the fraction pixel of each rounded bar is on the interval (0, 1), the extra pixels glommed onto each end will span about 1/4 of the data bar count. With 10 bars, 2-3 extra pixels would never be noticed, but if you have 100 or more, the extra 25+ pixels become much more noticeable.
One possible solution that appears to work in Chrome for svg:rect: use rangeBands to lay out, but then apply shape-rendering: crispEdges as a CSS style to your bar paths/rects.
This then leaves the onus on the SVG renderer to nudge each bar to a pixel boundary, but they are more evenly spaced overall, with occasional variance in the spacing to account for the error over the whole chart.
Personally, I use shape-rendering: optimizeSpeed and let the rendering agent make whatever tradeoffs it must to quickly render the (potentially fractional) bar positions.
I know this is old, but I came to this answer with the same problem; Ben's answer suggests another simple (but likely slower) workaround; you could fix a padding by shifting all x values to the left by the first value in the computed range.
Example:
// set your desired gap between y-axis and first bar
var yGap = 5;
// create your scale
var xScale = d3.scale.ordinal()
.domain(dataArray)
.rangeRoundBands([0, width], .1);
// obtain the *computed* left edge of the first bar
var leftOuter = xScale.range()[0];
// when setting the x attribute of your rects:
.attr("x", function(d) { return xScale(d) - leftOuter + yGap; });
The last line shifts everything to the left by an amount such that the left outer padding is your chosen yGap and the right outer padding is whatever it needs to be to make up the difference. Essentially this overrides the creator's intention of splitting the excess padding between the left and right sides.
I hope someone else finds this useful!
I have the same issue, and no matter I used for inner padding or outer padding, I just could not get the ticks align at the center of my vertical bars on the X-Axis. So I have not used the inner or outer padding. I have used my own padding, say 0.1.
for the bar rect, I set the width as
width: (1.0 - padding) * xScale.rangeBand()
for the x I just add half of the padding like this.
x: padding * xScale.rangeBand() / 2
This made the ticks perfectly align with the vertical bands.
I was wondering if you could help me with the follwoing D3js Zoom and pan functionality in the following fiddle: http://jsfiddle.net/moosejaw/nUF6X/5/
I hope the code (although not great) is straight forward.
I have a chart that has total chromosome length by total chromosome length. The tick values are the individual lengths (totals) of each chromosome. The ticks are formatted to be the name of the chromosomes (to look nice to the end user).
The problems that I am having are:
The x-axis and y-axis labels are extending outside the graph area. When I do not supply the tick values explicitly, the labels "disappear" as they should. See:
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickValues(tickValues)
.tickFormat(function(d) {
var ret = bpToChrMBP(d);
return ret.chr;
});
How do I prevent the x axis to not pan to the left before the minimum value? Also not pan to the right past the maximum value? This happens whether or not I am zoomed in. (The same for y-axis, except top and bottom).
Is there a way to "center" the axis labels between the tick marks. The tick marks are not evenly spaced. I tried using subdivide for minor tick marks, but that doesn't subdivide between tick marks correctly.
Any help would be greatly appreciated!
Matt
This Fiddle solves most of your problems: http://jsfiddle.net/CtTkP/
The explanations are below:
I am not sure what you meant by extending beyond the graphs area. Should the labels be insde the chart-area? If you mean that on panning, the labels extend beyond the axis, the problem can be solved by using two more clip-paths judiciously, though this does not allow for graceful fading of values which svg.axis translations provide:
var clipX = svg.append("clipPath")
.attr('id', 'clip-x-axis')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', margin.bottom);
svg.append("g")
.attr("class", "x axis")
.attr('clip-path', 'url(#clip-x-axis)')
.attr("transform", "translate(0, " + height + ")")
.call(xAxis);
// ...
var clipY = svg.append("clipPath")
.attr('id', 'clip-y-axis')
.append('rect')
.attr('x', - margin.left)
.attr('y', 0)
.attr('height', height)
.attr('width', margin.left);
svg.append("g")
.attr("class", "y axis")
.attr('clip-path', 'url(#clip-y-axis)')
.call(yAxis);
To prevent the panning from extending beyond values, you will have to manually restrict the translate for the zoom:
function zoomed() {
var trans = zoom.translate(),
scale = zoom.scale();
tx = Math.min(0, Math.max(width * (1 - scale), trans[0]));
ty = Math.min(0, Math.max(height * (1 - scale), trans[1]));
zoom.translate([tx, ty]);
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
// ...
This will not allow the graph from panning beyond the limits.
As you are explicitly overriding the tickValues, you can tweak the values to center them:
var tickValues2 = [];
tickValues.forEach(function (t, idx) {
if (idx < tickValues.length - 1) {
tickValues2.push((t + tickValues[idx + 1]) / 2);
}
});
Then instead of using tickValues for xAxis and yAxis, use tickValues2.
The problem is that you are setting tickValues manually, instead of letting the x and y scale do it for you. Try commenting it out: // .tickValues(tickValues)
var x = d3.scale.linear().rangeRound([0, width]).domain(d3.extent(tickValues));
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
// .tickValues(tickValues)
.tickFormat(function(d) {
var ret = bpToChrMBP(d);
return ret.chr;
});
A quick and dirty fix to allow setting tickValues explicitly could be to define a clippingPath for each axis.
You also don't need the make_x_axis function (same for y axis). Check out this zoomable scatterplot example: http://bl.ocks.org/ameliagreenhall/raw/d30a9ceb68f5b0fc903c/
To prevent panning left/right past the cutoffs you would have to re-implement d3.behavior.zoom(). Right now there is a function called mousemove that calls translateTo and this function doesn't have a limit:
function translateTo(p, l) {
l = point(l);
translate[0] += p[0] - l[0];
translate[1] += p[1] - l[1];
}
You can try playing with the dx and dy attributes when you define the axes.