最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - d3 graph with limited zoom - Stack Overflow

programmeradmin0浏览0评论

I have implemented a d3 line graph which reads data from a CSV file, and then plots multiple lines which react to mouseover events. It works fine with pan and zoom using the following code (sorry that it is so long and slightly untidy but I felt it best to display the full code):

    d3.csv("ijisb.csv", function(error, data) {

    var margin = {top: 50, right: 120, bottom: 50, left: 70},
    width = 1200 - margin.left - margin.right,
    height = 800 - margin.top - margin.bottom;

    var parseDate = d3.time.format("%m%d").parse;

    var color = d3.scale.category20();

    color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));

    data.forEach(function(d) {
        d.date = parseDate(d.date);
    });

    var sources = color.domain().map(function(name) {
        return {
            name: name,
            values: data.map(function(d) {
                return {date: d.date, temperature: +d[name]};
            })
        };
    });

    var x = d3.time.scale()
        .range([0, width])
        .domain([
        d3.min(data, function(d) { return d.date; }),
        d3.max(data, function(d) { return d.date; })
    ]);

    var y = d3.scale.linear()
        .range([height, 0])
        .domain([
        0,
        d3.max(sources, function(c) { return d3.max(c.values, function(v) { return v.temperature; }); })
    ]);

    var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom")
        .ticks(12)
        .tickFormat(d3.time.format("%b %d"));

    var yAxis = d3.svg.axis()
        .scale(y)
        .orient("left")
        .ticks(5);

    var line = d3.svg.line()
        .defined(function(d) { return d.temperature >= 0; })
        .x(function(d) { return x(d.date); })
        .y(function(d) { return y(d.temperature); });

    var zoom = d3.behavior.zoom()
    .x(x)
    .y(y)
    .scaleExtent([1, 8])
    .on("zoom", zoomed);

    var svg = d3.select("body").append("svg")
    .attr("width", "100%")
    .attr("height", "100%")
    .attr("viewBox", "0 0 1200 800")
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
    .call(zoom);

    var rect = svg.append("svg:rect")
    .attr("width", width)
    .attr("height", height)
    .attr("class", "plot");

    var make_x_axis = function () {
        return d3.svg.axis()
            .scale(x)
            .orient("bottom")
            .ticks(12)
            .tickFormat(d3.time.format("%b %d"));
    };

    var make_y_axis = function () {
        return d3.svg.axis()
            .scale(y)
            .orient("left")
            .ticks(5);
    };

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

    svg.append("g")
        .attr("class", "x grid")
        .attr("transform", "translate(0," + height + ")")
        .call(make_x_axis()
        .tickSize(-height, 0, 0)
        .tickFormat(""));

    svg.append("g")
        .attr("class", "y grid")
        .call(make_y_axis()
        .tickSize(-width, 0, 0)
        .tickFormat(""));

    var source = svg.selectAll(".source")
    .data(sources)
    .enter().append("g")
    .attr("class", "source");

    var clip = svg.append("clipPath")
    .attr("id", "clip")
    .append("rect")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", width)
    .attr("height", height)
    .append("text");

    source.append("path")
        .data(sources)
        .attr("class", "line")
        .attr("clip-path", "url(#clip)")
        .attr("d", function(d) { return line(d.values); })
        .style("stroke", function(d) {return color(d.name);})
        .style("opacity", 0.8)
        .on("mouseover", function(d){
            d3.select(this)
                .style("stroke",function(d) {return color(d.name);})
                .style("opacity", 1.0)
                .style("stroke-width", 2.5);
                this.parentNode.parentNode.appendChild(this.parentNode);
            d3.select('#text-' + d.name)
                .style("fill",function(d) {return color(d.name);})
                .style("font-weight", 700);
        })
        .on("mouseout", function(d) {
            d3.select(this)
                .transition()
                .duration(250)
                .style("stroke", function(d) {return color(d.name);})
                .style("stroke-width", 1.5)
                .style("opacity", 0.8);
            d3.select('#text-' + d.name)
                .transition()
                .duration(250)
                .style("fill", function(d) {return color(d.name);})
                .style("font-weight", 400);
        })
        .attr("id", function(d, i) { return "path-" + d.name; }); 

    source.append("text")
        .datum(function(d) { return {name: d.name}; })
        .attr("x", function(d, i) { return width+10; })
        .attr("y", function(d, i) { return (i*(height/16)); })
        .style("fill", function(d) {return color(d.name);})
        .on("mouseover", function(d){
            d3.select('#path-' + d.name)
                .style("stroke",function(d) {return color(d.name);})
                .style("opacity", 1.0)
                .style("stroke-width", 2.5);
                this.parentNode.parentNode.appendChild(this.parentNode);
            d3.select(this)
                .style("fill",function(d) {return color(d.name);})
                .style("font-weight", 700);
        })
        .on("mouseout", function(d) {
            d3.select('#path-' + d.name)
                .transition()
                .duration(250)
                .style("stroke", function(d) {return color(d.name);})
                .style("stroke-width", 1.5)
                .style("opacity", 0.8);
            d3.select(this)
                .transition()
                .duration(250)
                .style("fill", function(d) {return color(d.name);})
                .style("font-weight", 400);
        })
        .text(function(d) { return d.name; })
        .attr("font-family","sans-serif")
        .attr("font-size","14px")
        .attr("id", function(d, i) { return "text-" + d.name; }
        ); 

    var minT = new Date('01/01/1900'), maxT = new Date('01/01/2002'), w = $(window).width();

function zoomed() {
        d3.event.translate;
        d3.event.scale;

    svg.select(".x.axis")
        .call(xAxis);
    svg.select(".y.axis").call(yAxis);
    svg.select(".x.grid")
        .call(make_x_axis()
        .tickSize(-height, 0, 0)
        .tickFormat(""));
    svg.select(".y.grid")
        .call(make_y_axis()
        .tickSize(-width, 0, 0)
        .tickFormat(""));
    source.select(".line")
        .attr("d", function(d) { return line(d.values); })
        .style("stroke", function(d) {return color(d.name);});  
    }

    });

The problem I have is that I want to limit the panning and zooming so that it is not possible for the graph to be zoomed or panned out offscreen. I can do this by setting the scaleExtent on the zoom (which is implemented in the above example) and changing the zoomed function to:

    function zoomed() {

    var t = zoom.translate(),

    tx = t[0];
    ty = t[1];

    tx = Math.min(tx, 0);
    zoom.translate([tx, ty]);

    d3.event.translate;
    d3.event.scale;

    svg.select(".x.axis")
        .call(xAxis);
    svg.select(".y.axis").call(yAxis);
    svg.select(".x.grid")
        .call(make_x_axis()
        .tickSize(-height, 0, 0)
        .tickFormat(""));
    svg.select(".y.grid")
        .call(make_y_axis()
        .tickSize(-width, 0, 0)
        .tickFormat(""));
    source.select(".line")
        .attr("d", function(d) { return line(d.values); })
        .style("stroke", function(d) {return color(d.name);});
}

This limits the x-axis minimum to zero. However hard I try however, I cannot limit the maximum value of the x-axis, which means the graph can still pan too far to the right.

Any help? I hope this makes sense!

Nick

I have implemented a d3 line graph which reads data from a CSV file, and then plots multiple lines which react to mouseover events. It works fine with pan and zoom using the following code (sorry that it is so long and slightly untidy but I felt it best to display the full code):

    d3.csv("ijisb.csv", function(error, data) {

    var margin = {top: 50, right: 120, bottom: 50, left: 70},
    width = 1200 - margin.left - margin.right,
    height = 800 - margin.top - margin.bottom;

    var parseDate = d3.time.format("%m%d").parse;

    var color = d3.scale.category20();

    color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));

    data.forEach(function(d) {
        d.date = parseDate(d.date);
    });

    var sources = color.domain().map(function(name) {
        return {
            name: name,
            values: data.map(function(d) {
                return {date: d.date, temperature: +d[name]};
            })
        };
    });

    var x = d3.time.scale()
        .range([0, width])
        .domain([
        d3.min(data, function(d) { return d.date; }),
        d3.max(data, function(d) { return d.date; })
    ]);

    var y = d3.scale.linear()
        .range([height, 0])
        .domain([
        0,
        d3.max(sources, function(c) { return d3.max(c.values, function(v) { return v.temperature; }); })
    ]);

    var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom")
        .ticks(12)
        .tickFormat(d3.time.format("%b %d"));

    var yAxis = d3.svg.axis()
        .scale(y)
        .orient("left")
        .ticks(5);

    var line = d3.svg.line()
        .defined(function(d) { return d.temperature >= 0; })
        .x(function(d) { return x(d.date); })
        .y(function(d) { return y(d.temperature); });

    var zoom = d3.behavior.zoom()
    .x(x)
    .y(y)
    .scaleExtent([1, 8])
    .on("zoom", zoomed);

    var svg = d3.select("body").append("svg")
    .attr("width", "100%")
    .attr("height", "100%")
    .attr("viewBox", "0 0 1200 800")
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
    .call(zoom);

    var rect = svg.append("svg:rect")
    .attr("width", width)
    .attr("height", height)
    .attr("class", "plot");

    var make_x_axis = function () {
        return d3.svg.axis()
            .scale(x)
            .orient("bottom")
            .ticks(12)
            .tickFormat(d3.time.format("%b %d"));
    };

    var make_y_axis = function () {
        return d3.svg.axis()
            .scale(y)
            .orient("left")
            .ticks(5);
    };

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

    svg.append("g")
        .attr("class", "x grid")
        .attr("transform", "translate(0," + height + ")")
        .call(make_x_axis()
        .tickSize(-height, 0, 0)
        .tickFormat(""));

    svg.append("g")
        .attr("class", "y grid")
        .call(make_y_axis()
        .tickSize(-width, 0, 0)
        .tickFormat(""));

    var source = svg.selectAll(".source")
    .data(sources)
    .enter().append("g")
    .attr("class", "source");

    var clip = svg.append("clipPath")
    .attr("id", "clip")
    .append("rect")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", width)
    .attr("height", height)
    .append("text");

    source.append("path")
        .data(sources)
        .attr("class", "line")
        .attr("clip-path", "url(#clip)")
        .attr("d", function(d) { return line(d.values); })
        .style("stroke", function(d) {return color(d.name);})
        .style("opacity", 0.8)
        .on("mouseover", function(d){
            d3.select(this)
                .style("stroke",function(d) {return color(d.name);})
                .style("opacity", 1.0)
                .style("stroke-width", 2.5);
                this.parentNode.parentNode.appendChild(this.parentNode);
            d3.select('#text-' + d.name)
                .style("fill",function(d) {return color(d.name);})
                .style("font-weight", 700);
        })
        .on("mouseout", function(d) {
            d3.select(this)
                .transition()
                .duration(250)
                .style("stroke", function(d) {return color(d.name);})
                .style("stroke-width", 1.5)
                .style("opacity", 0.8);
            d3.select('#text-' + d.name)
                .transition()
                .duration(250)
                .style("fill", function(d) {return color(d.name);})
                .style("font-weight", 400);
        })
        .attr("id", function(d, i) { return "path-" + d.name; }); 

    source.append("text")
        .datum(function(d) { return {name: d.name}; })
        .attr("x", function(d, i) { return width+10; })
        .attr("y", function(d, i) { return (i*(height/16)); })
        .style("fill", function(d) {return color(d.name);})
        .on("mouseover", function(d){
            d3.select('#path-' + d.name)
                .style("stroke",function(d) {return color(d.name);})
                .style("opacity", 1.0)
                .style("stroke-width", 2.5);
                this.parentNode.parentNode.appendChild(this.parentNode);
            d3.select(this)
                .style("fill",function(d) {return color(d.name);})
                .style("font-weight", 700);
        })
        .on("mouseout", function(d) {
            d3.select('#path-' + d.name)
                .transition()
                .duration(250)
                .style("stroke", function(d) {return color(d.name);})
                .style("stroke-width", 1.5)
                .style("opacity", 0.8);
            d3.select(this)
                .transition()
                .duration(250)
                .style("fill", function(d) {return color(d.name);})
                .style("font-weight", 400);
        })
        .text(function(d) { return d.name; })
        .attr("font-family","sans-serif")
        .attr("font-size","14px")
        .attr("id", function(d, i) { return "text-" + d.name; }
        ); 

    var minT = new Date('01/01/1900'), maxT = new Date('01/01/2002'), w = $(window).width();

function zoomed() {
        d3.event.translate;
        d3.event.scale;

    svg.select(".x.axis")
        .call(xAxis);
    svg.select(".y.axis").call(yAxis);
    svg.select(".x.grid")
        .call(make_x_axis()
        .tickSize(-height, 0, 0)
        .tickFormat(""));
    svg.select(".y.grid")
        .call(make_y_axis()
        .tickSize(-width, 0, 0)
        .tickFormat(""));
    source.select(".line")
        .attr("d", function(d) { return line(d.values); })
        .style("stroke", function(d) {return color(d.name);});  
    }

    });

The problem I have is that I want to limit the panning and zooming so that it is not possible for the graph to be zoomed or panned out offscreen. I can do this by setting the scaleExtent on the zoom (which is implemented in the above example) and changing the zoomed function to:

    function zoomed() {

    var t = zoom.translate(),

    tx = t[0];
    ty = t[1];

    tx = Math.min(tx, 0);
    zoom.translate([tx, ty]);

    d3.event.translate;
    d3.event.scale;

    svg.select(".x.axis")
        .call(xAxis);
    svg.select(".y.axis").call(yAxis);
    svg.select(".x.grid")
        .call(make_x_axis()
        .tickSize(-height, 0, 0)
        .tickFormat(""));
    svg.select(".y.grid")
        .call(make_y_axis()
        .tickSize(-width, 0, 0)
        .tickFormat(""));
    source.select(".line")
        .attr("d", function(d) { return line(d.values); })
        .style("stroke", function(d) {return color(d.name);});
}

This limits the x-axis minimum to zero. However hard I try however, I cannot limit the maximum value of the x-axis, which means the graph can still pan too far to the right.

Any help? I hope this makes sense!

Nick

Share Improve this question asked May 5, 2013 at 14:25 NickFNickF 1691 gold badge3 silver badges13 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 5

Thanks for the help, I did it by the following code in the end:

var t = zoom.translate(),
s = zoom.scale();

tx = Math.min(0, Math.max(width * (1 - s), t[0]));
ty = Math.min(0, Math.max(height * (1 - s), t[1]));

zoom.translate([tx, ty]);

The difficulty was in bounding the graph at various zoom levels, which this now solves. The d3.event.translate and d3.event.scale statements were also unnecessary, and should have been removed.

You can simply check the value manually and reset it if it is too high:

if(tx > threshold) { tx = threshold; }

Also, the statements

d3.event.translate;
d3.event.scale;

in your code have no effect.

发布评论

评论列表(0)

  1. 暂无评论