2015-10-05 4 views
2

Я использую D3 для создания диаграммы, ориентированной на время, которая использует масштабируемое поведение. Я следую шаблону, изложенному в статье Майка Бостока «На пути к многоразовым диаграммам», и, кроме того, пытается сделать отзывчивым.Отзывчивое поведение масштабирования D3

Многоразовый узор диаграммы позволяет мне просто вызвать мою диаграмму в setInterval, чтобы обработать отзывчивость. Некоторые значения, такие как ширина, обновляются каждый вызов, другие инкапсулируются в закрытие и устанавливаются только при создании начальной диаграммы. Одним из значений, требующих потенциального обновления каждого вызова, является диапазон шкалы.

Кроме того, в соответствии с https://github.com/mbostock/d3/wiki/Zoom-Behavior, изменяя домен или диапазон шкалы, которая автоматически корректируется с помощью масштабирования, необходимо (re) указать масштаб на поведение масштабирования (кроме того, масштаб масштабирования и значения перевода будут сбросить).

Однако следующий результат я получаю от respecifying шкалы на поведение масштабирования всякий раз, когда я изменить диапазон весов (а также обновление трансфокатора с последней шкалы и переводить значения):

http://jsfiddle.net/xf3fk8hu/

function test(config) { 
 
    var aspectRatio = 10/3; 
 
    var margin = { top: 0, right: 0, bottom: 30, left: 0 }; 
 
    var current = new Date(); 
 
    var xScale = d3.time.scale().domain([d3.time.year.offset(current, -1), current]); 
 
    var xAxis = d3.svg.axis().scale(xScale).ticks(5); 
 
    var currentScale = 1; 
 
    var currentTranslate = [0, 0]; 
 
    var zoom = d3.behavior.zoom().x(xScale).on('zoom', function() { 
 
     currentScale = d3.event.scale; 
 
     currentTranslate = d3.event.translate; 
 
     d3.select(this.parentNode.parentNode.parentNode).call(result); 
 
    }); 
 
    var result = function(selection) { 
 
     selection.each(function(data) { 
 
      var outerWidth = $(this).width(); 
 
      var outerHeight = outerWidth/aspectRatio; 
 
      var width = outerWidth - margin.left - margin.right; 
 
      var height = outerHeight - margin.top - margin.bottom; 
 
      xScale.range([0, width]); 
 
      zoom.x(xScale).scale(currentScale).translate(currentTranslate); 
 

 
      var svg = d3.select(this).selectAll('svg').data([data]); 
 
      var svgEnter = svg.enter().append('svg'); 
 
      svg.attr('width', outerWidth).attr('height', outerHeight); 
 
\t    var gEnter = svgEnter.append('g'); 
 
\t    var g = svg.select('g').attr('transform', 'translate(' + margin.left + ' ' + margin.top + ')'); 
 
        gEnter.append('rect').attr('class', 'background').style('fill', '#F4F4F4').call(zoom); 
 
        g.select('rect.background').attr('width', width).attr('height', height); 
 

 
      \t \t var rectItem = g.selectAll('rect.item').data(function(d) { 
 
         return d; 
 
        }); 
 
      \t \t rectItem.enter().append('rect').attr('class', 'item').style('fill', '#00F'); 
 
\t \t \t \t \t rectItem.attr('x', function(d) { 
 
         return xScale(d); 
 
        }).attr('width', xScale(d3.time.day.offset(xScale.invert(0), 7))).attr('height', height); 
 

 
        gEnter.append('g'); 
 
        g.select('g').attr('transform', 'translate(0 ' + height + ')').call(xAxis); 
 
     }); 
 
    }; 
 
    return result; 
 
} 
 

 
setInterval(function() { 
 
    var selection = d3.select('#main').datum(d3.range(5).map(function() { 
 
     var current = new Date(); 
 
     var mean = -d3.time.minute.range(current, d3.time.month.offset(current, 6)).length; 
 
     var deviation = d3.time.minute.range(current, d3.time.month.offset(current, 1)).length; 
 
     var random = d3.random.normal(mean, deviation); 
 
     return function() { 
 
      return d3.time.minute.offset(current, random()); 
 
     }; 
 
    }())); 
 
    var myTest = test(); 
 
    return function() { 
 
    \t selection.call(myTest); 
 
\t }; 
 
}(), 1000/60);
<div id="main"></div> 
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

я могу получить очень близко, пытаясь минимально respecify масштаба на поведение масштабирования. Следующие работы, пока не изменять размер окна, то фокус зум не выстроены с помощью мыши больше:

http://jsfiddle.net/xf3fk8hu/1/

function test(config) { 
 
    var aspectRatio = 10/3; 
 
    var margin = { top: 0, right: 0, bottom: 30, left: 0 }; 
 
    var current = new Date(); 
 
    var xScale = d3.time.scale().domain([d3.time.year.offset(current, -1), current]); 
 
    var isZoomControllingScale = false; 
 
    var xAxis = d3.svg.axis().scale(xScale).ticks(5); 
 
    var currentScale = 1; 
 
    var currentTranslate = [0, 0]; 
 
    var zoom = d3.behavior.zoom().on('zoom', function() { 
 
     currentScale = d3.event.scale; 
 
     currentTranslate = d3.event.translate; 
 
     d3.select(this.parentNode.parentNode.parentNode).call(result); 
 
    }); 
 
    var result = function(selection) { 
 
     selection.each(function(data) { 
 
      var outerWidth = $(this).width(); 
 
      var outerHeight = outerWidth/aspectRatio; 
 
      var width = outerWidth - margin.left - margin.right; 
 
      var height = outerHeight - margin.top - margin.bottom; 
 
      xScale.range([0, width]); 
 
      if(!isZoomControllingScale) { 
 
       isZoomControllingScale = true; 
 
      \t zoom.x(xScale).scale(currentScale).translate(currentTranslate); 
 
      } 
 

 
      var svg = d3.select(this).selectAll('svg').data([data]); 
 
      var svgEnter = svg.enter().append('svg'); 
 
      svg.attr('width', outerWidth).attr('height', outerHeight); 
 
\t    var gEnter = svgEnter.append('g'); 
 
\t    var g = svg.select('g').attr('transform', 'translate(' + margin.left + ' ' + margin.top + ')'); 
 
        gEnter.append('rect').attr('class', 'background').style('fill', '#F4F4F4').call(zoom); 
 
        g.select('rect.background').attr('width', width).attr('height', height); 
 

 
      \t \t var rectItem = g.selectAll('rect.item').data(function(d) { 
 
         return d; 
 
        }); 
 
      \t \t rectItem.enter().append('rect').attr('class', 'item').style('fill', '#00F'); 
 
\t \t \t \t \t rectItem.attr('x', function(d) { 
 
         return xScale(d); 
 
        }).attr('width', xScale(d3.time.day.offset(xScale.invert(0), 7))).attr('height', height); 
 

 
        gEnter.append('g'); 
 
        g.select('g').attr('transform', 'translate(0 ' + height + ')').call(xAxis); 
 
     }); 
 
    }; 
 
    return result; 
 
} 
 

 
setInterval(function() { 
 
    var selection = d3.select('#main').datum(d3.range(5).map(function() { 
 
     var current = new Date(); 
 
     var mean = -d3.time.minute.range(current, d3.time.month.offset(current, 6)).length; 
 
     var deviation = d3.time.minute.range(current, d3.time.month.offset(current, 1)).length; 
 
     var random = d3.random.normal(mean, deviation); 
 
     return function() { 
 
      return d3.time.minute.offset(current, random()); 
 
     }; 
 
    }())); 
 
    var myTest = test(); 
 
    return function() { 
 
    \t selection.call(myTest); 
 
\t }; 
 
}(), 1000/60);
<div id="main"></div> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> 
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Как можно использовать поведение масштабирования в отзывчивым и многоразового использования?

ответ

3

Я решил проблему фокусировки зума, не выстраивающуюся под мышь после изменения размера, путем изменения шкалы на поведение масштабирования только после обнаружения изменения ширины. Однако по-прежнему возникала проблема с новой шириной, вызывающей ошибочный перевод во время изменения размера. Я нашел ответ на этот вопрос в d3 Preserve scale/translate after resetting range, что приводит меня к решению:

http://jsfiddle.net/xf3fk8hu/5/

function test(config) { 
 
    var aspectRatio = 10/3; 
 
    var margin = { top: 0, right: 0, bottom: 30, left: 0 }; 
 
    var current = new Date(); 
 
    var xScale = d3.time.scale(); 
 
    var xAxis = d3.svg.axis().scale(xScale).ticks(5); 
 
    var zoom = d3.behavior.zoom().x(xScale).on('zoom', function() { 
 
     currentScale = d3.event.scale; 
 
     currentTranslate = d3.event.translate; 
 
     d3.select(this.parentNode.parentNode.parentNode).call(result); 
 
    }); 
 
    var currentScale = zoom.scale(); 
 
    var currentTranslate = zoom.translate(); 
 
    var oldWidth; 
 
    var result = function(selection) { 
 
     selection.each(function(data) { 
 
      var outerWidth = $(this).width(); 
 
      var outerHeight = outerWidth/aspectRatio; 
 
      var width = outerWidth - margin.left - margin.right; 
 
      var height = outerHeight - margin.top - margin.bottom; 
 
      if(oldWidth !== width) { 
 
       if(oldWidth === undefined) oldWidth = width; 
 
       currentTranslate[0] *= width/oldWidth; 
 
       xScale.domain([d3.time.year.offset(current, -1), current]).range([0, width]); 
 
       zoom.x(xScale).scale(currentScale).translate(currentTranslate); 
 
      } 
 
      oldWidth = width; 
 

 
      var svg = d3.select(this).selectAll('svg').data([data]); 
 
      var svgEnter = svg.enter().append('svg'); 
 
      svg.attr('width', outerWidth).attr('height', outerHeight); 
 
      var gEnter = svgEnter.append('g'); 
 
      var g = svg.select('g').attr('transform', 'translate(' + margin.left + ' ' + margin.top + ')'); 
 
      gEnter.append('rect').attr('class', 'background').style('fill', '#F4F4F4').call(zoom); 
 
      g.select('rect.background').attr('width', width).attr('height', height); 
 

 
      var rectItem = g.selectAll('rect.item').data(function(d) { 
 
       return d; 
 
      }); 
 
      rectItem.enter().append('rect').attr('class', 'item').style('fill', '#00F'); 
 
      rectItem.attr('x', function(d) { 
 
       return xScale(d); 
 
      }).attr('width', xScale(d3.time.day.offset(xScale.invert(0), 7))).attr('height', height); 
 

 
      gEnter.append('g'); 
 
      g.select('g').attr('transform', 'translate(0 ' + height + ')').call(xAxis); 
 
     }); 
 
    }; 
 
    return result; 
 
} 
 

 
setInterval(function() { 
 
    var selection = d3.select('#main').datum(d3.range(5).map(function() { 
 
     var current = new Date(); 
 
     var mean = -d3.time.minute.range(current, d3.time.month.offset(current, 6)).length; 
 
     var deviation = d3.time.minute.range(current, d3.time.month.offset(current, 1)).length; 
 
     var random = d3.random.normal(mean, deviation); 
 
     return function() { 
 
      return d3.time.minute.offset(current, random()); 
 
     }; 
 
    }())); 
 
    var myTest = test(); 
 
    return function() { 
 
    \t selection.call(myTest); 
 
    }; 
 
}(), 1000/60);
<div id="main"></div> 
 
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script> 
 
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>