首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用可重用模式的D3语义缩放

使用可重用模式的D3语义缩放
EN

Stack Overflow用户
提问于 2018-05-02 21:30:03
回答 1查看 368关注 0票数 3

我试图在使用Mike的走向可重用图表模式(其中图表表示为函数)时实现语义缩放。在我的缩放处理程序中,我想使用transform.rescaleX更新我的缩放,然后简单地再次调用这个函数。

它几乎工作,但重标度似乎积累,变焦,转换越来越快。这是我的小提琴:

代码语言:javascript
复制
function chart() {
  let aspectRatio = 10.33;
  let margin = { top: 0, right: 0, bottom: 5, left: 0 };
  let current = new Date();
  let scaleBand = d3.scaleBand().padding(.2);
  let scaleTime = d3.scaleTime().domain([d3.timeDay(current), d3.timeDay.ceil(current)]);
  let axis = d3.axisBottom(scaleTime);
  let daysThisMonth = d3.timeDay.count(d3.timeMonth(current), d3.timeMonth.ceil(current));
  let clipTypes = [ClipType.Scheduled, ClipType.Alarm, ClipType.Motion];
  let zoom = d3.zoom().scaleExtent([1 / daysThisMonth, 1440]);
  let result = function(selection) {
    selection.each(function(data) {
      let selection = d3.select(this);
      let outerWidth = this.getBoundingClientRect().width;
      let outerHeight = outerWidth / aspectRatio;
      let width = outerWidth - margin.left - margin.right;
      let height = outerHeight - margin.top - margin.bottom;
      scaleBand.domain(d3.range(data.length)).range([0, height * .8]);
      scaleTime.range([0, width]);
      zoom.on('zoom', _ => {
        scaleTime = d3.event.transform.rescaleX(scaleTime);
        selection.call(result);
      });
      let svg = selection.selectAll('svg').data([data]);
      let svgEnter = svg.enter().append('svg').attr('viewBox', '0 0 ' + outerWidth + ' ' + outerHeight);//.attr('preserveAspectRatio', 'xMidYMin slice');
      svg = svg.merge(svgEnter);
      	let defsEnter = svgEnter.append('defs');
      	let defs = svg.select('defs');
      	let gMainEnter = svgEnter.append('g').attr('id', 'main');
      	let gMain = svg.select('g#main').attr('transform', 'translate(' + margin.left + ' ' + margin.top + ')');
          let gAxisEnter = gMainEnter.append('g').attr('id', 'axis');
          let gAxis = gMain.select('g#axis').call(axis.scale(scaleTime));
          let gCameraContainerEnter = gMainEnter.append('g').attr('id', 'camera-container');
          let gCameraContainer = gMain.select('g#camera-container').attr('transform', 'translate(' + 0 + ' ' + height * .2 + ')').call(zoom);
      			let gCameraRowsEnter = gCameraContainerEnter.append('g').attr('id', 'camera-rows');
            let gCameraRows = gCameraContainer.select('g#camera-rows');
              let gCameras = gCameraRows.selectAll('g.camera').data(d => {
                return d;
              });
              let gCamerasEnter = gCameras.enter().append('g').attr('class', 'camera');
              gCameras = gCameras.merge(gCamerasEnter);
              gCameras.exit().remove();
                let rectClips = gCameras.selectAll('rect.clip').data(d => {
                  return d.clips.filter(clip => {
                    return clipTypes.indexOf(clip.type) !== -1;
                  });
                });
                let rectClipsEnter = rectClips.enter().append('rect').attr('class', 'clip').attr('height', _ => {
                  return scaleBand.bandwidth();
                }).attr('y', (d, i, g) => {
                  return scaleBand(Array.prototype.indexOf.call(g[i].parentNode.parentNode.childNodes, g[i].parentNode)); //TODO: sloppy
                }).style('fill', d => {
                  switch(d.type) {
                    case ClipType.Scheduled:
                      return '#0F0';
                    case ClipType.Alarm:
                      return '#FF0';
                    case ClipType.Motion:
                      return '#F00';
                  };
                });
                rectClips = rectClips.merge(rectClipsEnter).attr('width', d => {
                  return scaleTime(d.endTime) - scaleTime(d.startTime);
                }).attr('x', d => {
                  return scaleTime(d.startTime);
                });
                rectClips.exit().remove();
      			let rectBehaviorEnter = gCameraContainerEnter.append('rect').attr('id', 'behavior').style('fill', '#000').style('opacity', 0);
          	let rectBehavior = gCameraContainer.select('rect#behavior').attr('width', width).attr('height', height * .8);//.call(zoom);
    });
  };
  return result;
}

// data model

let ClipType = {
  Scheduled: 0,
  Alarm: 1,
  Motion: 2
};
let data = [{
  id: 1,
  src: "assets/1.jpg",
  name: "Camera 1",
  server: 1
}, {
  id: 2,
  src: "assets/2.jpg",
  name: "Camera 2",
  server: 1
}, {
  id: 3,
  src: "assets/1.jpg",
  name: "Camera 3",
  server: 2
}, {
  id: 4,
  src: "assets/1.jpg",
  name: "Camera 4",
  server: 2
}].map((_ => {
  let current = new Date();
  let randomClips = d3.randomUniform(24);
  let randomTimeSkew = d3.randomUniform(-30, 30);
  let randomType = d3.randomUniform(3);
  return camera => {
    camera.clips = d3.timeHour.every(Math.ceil(24 / randomClips())).range(d3.timeDay.offset(current, -30), d3.timeDay(d3.timeDay.offset(current, 1))).map((d, indexEndTime, g) => {
      return {
        startTime: indexEndTime === 0 ? d : d3.timeMinute.offset(d, randomTimeSkew()),
        endTime: indexEndTime === g.length - 1 ? d3.timeDay(d3.timeDay.offset(current, 1)) : null,
        type: Math.floor(randomType())
      };
    }).map((d, indexStartTime, g) => {
      if(d.endTime === null)
        d.endTime = g[indexStartTime + 1].startTime;
      return d;
    });
    return camera;
  };
})());
let myChart = chart();
let selection = d3.select('div#container');
selection.datum(data).call(myChart);
代码语言:javascript
复制
<div id="container"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

编辑:下面的缩放处理程序工作得很好,但是我想要一个更通用的解决方案:

代码语言:javascript
复制
let newScaleTime = d3.event.transform.rescaleX(scaleTime);
d3.select('g#axis').call(axis.scale(newScaleTime));
d3.selectAll('rect.clip').attr('width', d => {
  return newScaleTime(d.endTime) - newScaleTime(d.startTime);
}).attr('x', d => {
  return newScaleTime(d.startTime);
});
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-05-05 02:07:40

简单地说,您需要实现一个参考比例尺,以指示当不被缩放操作时,秤的基本状态是什么。否则,您将遇到您描述的问题:“它几乎工作,但重标度似乎积累,变焦越来越快。”

要了解为什么需要参考比例尺,请放大图表,然后在不移动鼠标的情况下(每一次)缩小一次。放大时,轴会发生变化。当你放大轴时,没有。注意初始放大和第一次放大时的缩放因子:放大后的1.6471820345351462,缩放的1。这个数字代表了放大/缩小我们正在放大的任何东西的程度。在初始放大时,我们放大了~1.65倍。在前面的放大图中,我们缩小了1的倍数,即:一点也不。另一方面,如果你先放大,你缩小了大约0.6倍,然后放大1倍。我已经建立了一个简化的例子来说明这一点:

代码语言:javascript
复制
function chart() {
  let zoom = d3.zoom().scaleExtent([0.25,20]);
  let scale = d3.scaleLinear().domain([0,1000]).range([0,550]);
  let axis = d3.axisBottom;
  
  let result = function(selection) {
    selection.each(function() {
      
      let selection = d3.select(this);
         
      selection.call(axis(scale));
      selection.call(zoom);
      
      zoom.on('zoom', function() {
         scale = d3.event.transform.rescaleX(scale);
         console.log(d3.event.transform.k);
         selection.call(result);
      });
    
    })
  }
  return result;
}

d3.select("svg").call(chart());
代码语言:javascript
复制
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="550" height="200"></svg>

缩放应该相对于初始缩放因子,通常是1。换句话说,缩放是累积的,它将放大/缩小作为初始缩放的一个因子,而不是最后一步(否则,转换k值只会是三个值中的一个:一个值用于放大,另一个值用于放大,另一个值用于保持不变,并且与当前标度相比)。这就是为什么重新缩放初始标度不起作用的原因--你失去了缩放所引用的初始标度的参考点。

从文档中,如果您用d3.event.transform.rescaleX重新定义一个刻度,我们将得到一个反映缩放(累积)转换的刻度:

因此,rescaleX方法不修改输入刻度x;x表示未转换的刻度,而返回的刻度表示其转换的视图。(文档)

在此基础上,如果我们连续放大两次,当我们第一次放大时,我们第一次看到的值是~1.6x,第二次是~2.7x。但是,由于我们重新调整了刻度,我们在一个已经放大到1.6x的比例上进行了2.7x的缩放,给出了~4.5倍的缩放因子,而不是2.7x。更糟糕的是,如果我们放大两次,然后再缩小一次,缩放(退出)事件给出的缩放值仍然大于1(第一次放大~1.6,第二次放大~2.7,缩小到1.6),因此,尽管滚动,我们仍然在放大:

代码语言:javascript
复制
function chart() {
  let zoom = d3.zoom().scaleExtent([0.25,20]);
  let scale = d3.scaleLinear().domain([0,1000]).range([0,550]);
  let axis = d3.axisBottom;
  
  let result = function(selection) {
    selection.each(function() {
      
      let selection = d3.select(this);
         
      selection.call(axis(scale));
      selection.call(zoom);
      
      zoom.on('zoom', function() {
         scale = d3.event.transform.rescaleX(scale);
         var magnification = 1000/(scale.domain()[1] - scale.domain()[0]);
         console.log("Actual magnification: "+magnification+"x");
         console.log("Intended magnification: "+d3.event.transform.k+"x")
         console.log("---");         
         selection.call(result);
      });
    
    })
  }
  return result;
}

d3.select("svg").call(chart());
代码语言:javascript
复制
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="550" height="200"></svg>

我还没有讨论缩放的x偏移部分,但是您可以想象会发生类似的问题-缩放是累积的,但是您失去了那些累积更改所引用的初始参考点。

惯用的解决方案是使用参考标度,使用缩放创建用于绘制矩形/轴/等等的工作刻度。工作标度最初与参考标度相同(一般),并设置为:每次缩放的workingScale = d3.event.transform.rescaleX(referenceScale)

代码语言:javascript
复制
function chart() {
  let zoom = d3.zoom().scaleExtent([0.25,20]);
  let workingScale = d3.scaleLinear().domain([0,1000]).range([0,550]);
  let referenceScale = d3.scaleLinear().domain([0,1000]).range([0,550]);
  let axis = d3.axisBottom;
  
  let result = function(selection) {
    selection.each(function() {
      
      let selection = d3.select(this);
      
      selection.call(axis(workingScale));
      selection.call(zoom);
      
      zoom.on('zoom', function() {
         workingScale = d3.event.transform.rescaleX(referenceScale);
         var magnification = 1000/(workingScale.domain()[1] - workingScale.domain()[0]);
         console.log("Actual magnification: "+magnification+"x");
         console.log("Intended magnification: "+d3.event.transform.k+"x")
         console.log("---");         
         selection.call(result);
      });
    
    })
  }
  return result;
}

d3.select("svg").call(chart());
代码语言:javascript
复制
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="550" height="200"></svg>

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50143819

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档