【问题标题】:Resize svg when window is resized in d3.js在 d3.js 中调整窗口大小时调整 svg 大小
【发布时间】:2013-04-22 07:42:15
【问题描述】:

我正在使用 d3.js 绘制散点图。借助这个问题:
Get the size of the screen, current web page and browser window

我正在使用这个答案:

var w = window,
    d = document,
    e = d.documentElement,
    g = d.getElementsByTagName('body')[0],
    x = w.innerWidth || e.clientWidth || g.clientWidth,
    y = w.innerHeight|| e.clientHeight|| g.clientHeight;

所以我可以像这样将我的情节适合用户的窗口:

var svg = d3.select("body").append("svg")
        .attr("width", x)
        .attr("height", y)
        .append("g");

现在我希望在用户调整窗口大小时能够调整绘图大小。

PS : 我的代码中没有使用 jQuery。

【问题讨论】:

标签: javascript d3.js


【解决方案1】:

d3.select("div#chartId")
   .append("div")
   // Container class to make it responsive.
   .classed("svg-container", true) 
   .append("svg")
   // Responsive SVG needs these 2 attributes and no width and height attr.
   .attr("preserveAspectRatio", "xMinYMin meet")
   .attr("viewBox", "0 0 600 400")
   // Class to make it responsive.
   .classed("svg-content-responsive", true)
   // Fill with a rectangle for visualization.
   .append("rect")
   .classed("rect", true)
   .attr("width", 600)
   .attr("height", 400);
.svg-container {
  display: inline-block;
  position: relative;
  width: 100%;
  padding-bottom: 100%; /* aspect ratio */
  vertical-align: top;
  overflow: hidden;
}
.svg-content-responsive {
  display: inline-block;
  position: absolute;
  top: 10px;
  left: 0;
}

svg .rect {
  fill: gold;
  stroke: steelblue;
  stroke-width: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<div id="chartId"></div>

【讨论】:

  • 这看起来像是 cminatti 答案的副本
【解决方案2】:

如果您想绑定自定义逻辑来调整事件大小,nowadays 您可以开始使用ResizeObserver browser API 作为 SVGElement 的边界框。
这也将处理由于附近元素大小更改而调整容器大小的情况。
有一个 polyfill 以获得更广泛的浏览器支持。

这是它在 UI 组件中的工作方式:

function redrawGraph(container, { width, height }) {
  d3
    .select(container)
    .select('svg')
    .attr('height', height)
    .attr('width', width)
    .select('rect')
    .attr('height', height)
    .attr('width', width);
}

// Setup observer in constructor
const resizeObserver = new ResizeObserver((entries, observer) => {
  for (const entry of entries) {
    // on resize logic specific to this component
    redrawGraph(entry.target, entry.contentRect);
  }
})

// Observe the container
const container = document.querySelector('.graph-container');
resizeObserver.observe(container)
.graph-container {
  height: 75vh;
  width: 75vw;
}

.graph-container svg rect {
  fill: gold;
  stroke: steelblue;
  stroke-width: 3px;
}
<script src="https://unpkg.com/resize-observer-polyfill@1.5.1/dist/ResizeObserver.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<figure class="graph-container">
  <svg width="100" height="100">
    <rect x="0" y="0" width="100" height="100" />
  </svg>
</figure>
// unobserve in component destroy method
this.resizeObserver.disconnect()

【讨论】:

    【解决方案3】:

    寻找“响应式 SVG” 让 SVG 响应式非常简单,您不必再担心尺寸问题。

    我是这样做的:

    d3.select("div#chartId")
       .append("div")
       // Container class to make it responsive.
       .classed("svg-container", true) 
       .append("svg")
       // Responsive SVG needs these 2 attributes and no width and height attr.
       .attr("preserveAspectRatio", "xMinYMin meet")
       .attr("viewBox", "0 0 600 400")
       // Class to make it responsive.
       .classed("svg-content-responsive", true)
       // Fill with a rectangle for visualization.
       .append("rect")
       .classed("rect", true)
       .attr("width", 600)
       .attr("height", 400);
    .svg-container {
      display: inline-block;
      position: relative;
      width: 100%;
      padding-bottom: 100%; /* aspect ratio */
      vertical-align: top;
      overflow: hidden;
    }
    .svg-content-responsive {
      display: inline-block;
      position: absolute;
      top: 10px;
      left: 0;
    }
    
    svg .rect {
      fill: gold;
      stroke: steelblue;
      stroke-width: 5px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
    
    <div id="chartId"></div>

    注意: SVG 图像中的所有内容都将随窗口宽度缩放。这包括笔画宽度和字体大小(即使是用 CSS 设置的)。如果不希望这样做,下面还有更多涉及的替代解决方案。

    更多信息/教程:

    http://thenewcode.com/744/Make-SVG-Responsive

    http://soqr.fr/testsvg/embed-svg-liquid-layout-responsive-web-design.php

    【讨论】:

    • 这更加优雅,并且还使用了每个前端开发人员工具包中应该包含的容器缩放方法(可用于缩放特定纵横比的任何东西)。应该是最佳答案。
    • 只是补充一点:viewBox 属性应该设置为 SVG 绘图的相关高度和宽度:.attr("viewBox","0 0 " + width + " " + height) 其中widthheight 在别处定义。也就是说,除非您希望您的 SVG 被视图框剪裁。您可能还想更新 css 中的 padding-bottom 参数以匹配 svg 元素的纵横比。虽然反应很好 - 非常有帮助,而且很受欢迎。谢谢!
    • 基于相同的想法,给出一个不涉及保留纵横比的答案会很有趣。问题是关于让 svg 元素在调整大小时不断覆盖整个窗口,这在大多数情况下意味着不同的纵横比。
    • 只是关于 UX 的说明:对于某些用例,将基于 SVG 的 d3 图表视为具有固定纵横比的资产并不理想。显示文本的图表,或者是动态的,或者通常感觉更像是一个适当的用户界面而不是资产,通过使用更新的维度重新渲染可以获得更多收益。例如,viewBox 强制轴字体和刻度按比例放大/缩小,与 html 文本相比可能看起来很尴尬。相比之下,重新渲染使图表能够保持其标签大小一致,而且它提供了添加或删除刻度的机会。
    • 为什么是top: 10px;?对我来说似乎不正确并提供偏移量。设置为 0px 效果很好。
    【解决方案4】:

    对于那些在 D3 v4/v5 中使用力有向图的人,size 方法不再存在。以下内容对我有用(基于this github issue):

    simulation
        .force("center", d3.forceCenter(width / 2, height / 2))
        .force("x", d3.forceX(width / 2))
        .force("y", d3.forceY(height / 2))
        .alpha(0.1).restart();
    

    【讨论】:

      【解决方案5】:

      使用window.onresize:

      function updateWindow(){
          x = w.innerWidth || e.clientWidth || g.clientWidth;
          y = w.innerHeight|| e.clientHeight|| g.clientHeight;
      
          svg.attr("width", x).attr("height", y);
      }
      d3.select(window).on('resize.updatesvg', updateWindow);
      

      http://jsfiddle.net/Zb85u/1/

      【讨论】:

      • 这不是一个好的答案,因为它涉及对 DOM 事件的出价...更好的解决方案是仅使用 d3 和 css
      • @alem0lars 有趣的是,css/d3 版本对我不起作用,而这个版本却...
      • 我知道它出现在 JSFiddle 中,但看到 weg 不知从何而来以及 xy 被初始化而没有任何关键字在你的答案。
      【解决方案6】:

      更新只需使用@cminatti 的新方法


      用于历史目的的旧答案

      IMO 最好使用 select() 和 on(),因为这样你就可以拥有多个调整大小的事件处理程序......只是不要太疯狂

      d3.select(window).on('resize', resize); 
      
      function resize() {
          // update width
          width = parseInt(d3.select('#chart').style('width'), 10);
          width = width - margin.left - margin.right;
      
          // resize the chart
          x.range([0, width]);
          d3.select(chart.node().parentNode)
              .style('height', (y.rangeExtent()[1] + margin.top + margin.bottom) + 'px')
              .style('width', (width + margin.left + margin.right) + 'px');
      
          chart.selectAll('rect.background')
              .attr('width', width);
      
          chart.selectAll('rect.percent')
              .attr('width', function(d) { return x(d.percent); });
      
          // update median ticks
          var median = d3.median(chart.selectAll('.bar').data(), 
              function(d) { return d.percent; });
      
          chart.selectAll('line.median')
              .attr('x1', x(median))
              .attr('x2', x(median));
      
      
          // update axes
          chart.select('.x.axis.top').call(xAxis.orient('top'));
          chart.select('.x.axis.bottom').call(xAxis.orient('bottom'));
      
      }
      

      http://eyeseast.github.io/visible-data/2013/08/28/responsive-charts-with-d3/

      【讨论】:

      • 我不能同意。 cminatti 的方法将适用于我们处理的所有 d3js 文件。这种通过调整每个图表大小进行转换的方法是矫枉过正的。此外,我们需要为我们能想到的每一个可能的图表重写它。上面提到的方法适用于一切。我已经尝试了 4 种方法,包括您提到的重新加载类型,但它们都不适用于立体主义中使用的多区域图。
      • @EamonnKenny 我完全同意你的看法。未来7个月的另一个答案更好:)
      • 对于这个方法:“d3.select(window).on”我找不到“d3.select(window).off”或“d3.select(window).unbind”
      • 这个答案绝不逊色。他们的答案将缩放图表的各个方面(包括刻度、轴、线和文本注释),这些方面在某些屏幕尺寸下看起来很尴尬,在其他尺寸下看起来很糟糕,在极端尺寸下看起来不可读。我发现使用这种(或者,对于更现代的解决方案,@Angu Agarwal)方法调整大小会更好。
      【解决方案7】:

      在强制布局中,简单地设置“高度”和“宽度”属性将无法将绘图重新居中/移动到 svg 容器中。但是,有一个非常简单的答案适用于找到 here 的 Force Layouts。总结:

      使用您喜欢的相同(任何)事件。

      window.on('resize', resize);
      

      然后假设你有 svg & force 变量:

      var svg = /* D3 Code */;
      var force = /* D3 Code */;    
      
      function resize(e){
          // get width/height with container selector (body also works)
          // or use other method of calculating desired values
          var width = $('#myselector').width(); 
          var height = $('#myselector').height(); 
      
          // set attrs and 'resume' force 
          svg.attr('width', width);
          svg.attr('height', height);
          force.size([width, height]).resume();
      }
      

      这样,您不会完全重新渲染图形,我们设置属性,d3 会根据需要重新计算。这至少在您使用重力点时有效。我不确定这是否是此解决方案的先决条件。任何人都可以确认或否认吗?

      干杯,g

      【讨论】:

      • 这在我有大约 100 个连接的力布局上非常有效。谢谢。
      【解决方案8】:

      如果调整大小的代码几乎与最初构建图形的代码一样长,那将是一种丑陋。因此,与其调整现有图表的每个元素的大小,不如简单地重新加载它?以下是它对我的工作方式:

      function data_display(data){
         e = document.getElementById('data-div');
         var w = e.clientWidth;
         // remove old svg if any -- otherwise resizing adds a second one
         d3.select('svg').remove();
         // create canvas
         var svg = d3.select('#data-div').append('svg')
                                         .attr('height', 100)
                                         .attr('width', w);
         // now add lots of beautiful elements to your graph
         // ...
      }
      
      data_display(my_data); // call on page load
      
      window.addEventListener('resize', function(event){
          data_display(my_data); // just call it again...
      }
      

      关键行是d3.select('svg').remove();。否则,每次调整大小都会在前一个元素的下方添加另一个 SVG 元素。

      【讨论】:

      • 如果您在每个窗口调整大小事件中重绘整个元素,这绝对会降低性能
      • 但这是正确的:在渲染时,您可以确定是否应该有插入轴,隐藏图例,根据可用内容执行其他操作。使用纯粹的 CSS/viewbox 解决方案,它所做的只是让可视化看起来被挤压。对图像有利,对数据不利。
      猜你喜欢
      • 2019-05-15
      • 1970-01-01
      • 2012-08-02
      • 1970-01-01
      • 1970-01-01
      • 2021-04-09
      • 1970-01-01
      • 2022-06-10
      • 1970-01-01
      相关资源
      最近更新 更多