简体   繁体   中英

Changing rectangle dimensions in d3 zoom

I am using d3 to build a piano roll editor (which looks kind of like this ). I need the rectangles to always be snapped onto the grid so when I pan or zoom the shapes will stay relative to the grid lines. It doesn't matter if the vertical grid lines redraw as I move in and out, but the number of horizontal grid lines should always stay the same, and the rectangle shapes are always locked on. An example of it not quite working can be seen here: http://jsfiddle.net/jgab3103/e05qj4hy/

I can see lots of d3 zoom type of examples around the place but I can't find anything to address this kind of issue. I think I am just not understanding how to scale shapes properly when working with the the zoom function. Also, in trying to get this to work I am noticing the panning and zooming seems to have become a bit unreliable, not sure why.

Anyway, if anyone had any ideas on how to solve this, it would be greatly appreciated. The code which is on the jsfiddle is below:

UPDATE: Just to (hopefully!) clarify - both horizontal and vertical axis need to zoom. The constraint is that the number of horizontal grid lines needs to stay the same and the shapes must be locked on to the grid lines so the dimensions never change. If a rectangle starts with a width and height of 1, this always needs to be retained when zooming.

//Data for note shapes
var noteData = [
          {frequency: 3, duration:1, startPoint: 1},
          {frequency: 6, duration:1, startPoint: 2},
          {frequency: 5, duration:1, startPoint: 3},
          {frequency: 4, duration:1, startPoint: 4}
          ];

margin = {
    top: 20,
    right: 20,
    bottom: 20,
    left: 45
};

width = 400 - margin.left - margin.right;
height = 200 - margin.top - margin.bottom;


//SCALES
var xScale = d3.scale.linear()
    .domain([0,width])
    .range([0, width])


var yScale = d3.scale.linear()
    .domain([0,width])
    .range([0, height]);

var heightScale = d3.scale.linear()
    .domain([0,100])
    .range([0,height]);

//Set up zoom 

var zoom = d3.behavior.zoom()
    .x(xScale)
    .y(yScale)
    .scaleExtent([1,100])
    .scale([50])
    .on("zoom", zoomed);

// Create SVG space and centre it
svg = d3.select('#chart')
    .append("svg:svg")
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom)
    .append("svg:g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
    .call(zoom);
// Append a rect on top
var rect = svg.append("svg:rect")
    .attr("width", width)
    .attr("height", height)
    .attr("class", "plot");



var noteRange = d3.range(0,88);
var measureRange = d3.range(0,16);


var make_x_axis = function () {
    return d3.svg.axis()
        .scale(xScale)
        .orient("bottom")
        .ticks(10);
};




var make_y_axis = function () {
    return d3.svg.axis()
        .scale(yScale)
        .orient("left")
        .tickValues(noteRange);
};

var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient("bottom")
    .ticks(10);
    //.tickValues([2,5,7,9]);

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

var yAxis = d3.svg.axis()
    .scale(yScale)
    .orient("left")
    .tickValues(noteRange);

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 clip = svg.append("svg:clipPath")
    .attr("id", "clip")
    .append("svg:rect")
    .attr("x", 0)
    .attr("y", 0)
    .attr("width", width)
    .attr("height", height);

var chartBody = svg.append("g")
    .attr("clip-path", "url(#clip)");


var rectGroup = svg.append("g")


var notes = rectGroup
        .selectAll("rect")
        .data(noteData)
        .enter()
            .append("rect")
            .attr("x",function(d){
              return xScale(d.startPoint)
            })
            .attr("y",function(d){
              return yScale(d.frequency)
            })
            .attr("width",function(d) {
              return 50;
            })
            .attr('class', 'rect')
            .attr("height", function(d) {
              return 23;
            })


function zoomed() {
    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(""));

    rectGroup.selectAll("rect")
         .attr('class', 'rect')
         .attr("x",function(d){
          return xScale(d.startPoint);
        })
         .attr("y",function(d){
          return yScale(d.frequency);
        })
         .attr('width', function(d) {
          return 50;
        })
        .attr("height", function(d) {
              return 23;
            })



}

If you don't want yScale to be updated by the zoom behavior, just remove the line .y(yScale) and you should be good to go.

The zoom behavior will be constructed simply:

var zoom = d3.behavior.zoom()
    .x(xScale)
    .scaleExtent([1,100])
    .scale([50])
    .on("zoom", zoomed);

and it will only update the xScale .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM