ปรับขนาด svg เมื่อปรับขนาดหน้าต่างเป็น d3.js


183

ฉันกำลังวาด scatterplot ด้วย d3.js. ด้วยความช่วยเหลือของคำถามนี้:
รับขนาดของหน้าจอหน้าเว็บปัจจุบันและหน้าต่างเบราว์เซอร์

ฉันใช้คำตอบนี้:

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 ในรหัสของฉัน


คำตอบ:


295

มองหา 'responsive 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


8
นี่เป็นสิ่งที่สวยงามกว่ามากและยังใช้วิธีการปรับขนาดคอนเทนเนอร์ที่ควรจะอยู่ในชุดเครื่องมือของนักพัฒนาส่วนหน้า (สามารถใช้ในการปรับขนาดสิ่งต่าง ๆ ในอัตราส่วนภาพเฉพาะ) ควรเป็นคำตอบที่ดีที่สุด
kontur

13
เพียงเพิ่มลงในนี้: viewBoxแอตทริบิวต์ควรถูกตั้งค่าเป็นความสูงและความกว้างที่เกี่ยวข้องของพล็อต SVG ของคุณ: .attr("viewBox","0 0 " + width + " " + height) ที่ใดwidthและheightถูกกำหนดไว้ที่อื่น นั่นคือยกเว้นว่าคุณต้องการให้ SVG ของคุณถูกตัดโดยกล่องมุมมอง คุณอาจต้องการอัปเดตpadding-bottomพารามิเตอร์ใน css ของคุณเพื่อให้ตรงกับอัตราส่วนกว้างยาวขององค์ประกอบ svg ของคุณ แม้ว่าการตอบสนองที่ดีเยี่ยม - มีประโยชน์มากและทำงานได้ดี ขอบคุณ!
Andrew Guy

13
ตามแนวคิดเดียวกันมันน่าสนใจที่จะให้คำตอบที่ไม่เกี่ยวข้องกับการรักษาอัตราส่วนกว้างยาว คำถามคือเกี่ยวกับการมีองค์ประกอบ svg ที่ช่วยให้ครอบคลุมหน้าต่างเต็มในการปรับขนาดซึ่งในกรณีส่วนใหญ่หมายถึงอัตราส่วนกว้างยาวที่แตกต่างกัน
Nicolas Le Thierry d'Ennequin

37
ข้อควรทราบเกี่ยวกับ UX: สำหรับบางกรณีการใช้งานการใช้แผนภูมิ d3 ที่ใช้ SVG ของคุณเหมือนกับสินทรัพย์ที่มีอัตราส่วนกว้างคงไม่เหมาะ แผนภูมิที่แสดงข้อความหรือเป็นแบบไดนามิกหรือโดยทั่วไปรู้สึกเหมือนอินเทอร์เฟซผู้ใช้ที่เหมาะสมมากกว่าเนื้อหาจะได้รับมากขึ้นโดยการเรนเดอร์ใหม่ด้วยมิติข้อมูลที่อัปเดตใหม่ ตัวอย่างเช่น viewBox บังคับให้แบบอักษรของแกนและเห็บเพื่อขยายขึ้น / ลงซึ่งอาจดูเคอะเขินเมื่อเทียบกับข้อความ html ในทางกลับกันการสร้างการแสดงผลอีกครั้งทำให้แผนภูมิสามารถรักษาขนาดของป้ายกำกับให้สอดคล้องกันรวมถึงให้โอกาสในการเพิ่มหรือลบเห็บ
Karim Hernandez

1
ทำไมtop: 10px;? ดูเหมือนว่าไม่ถูกต้องสำหรับฉันและให้การชดเชย การนำไปที่ 0px ทำงานได้ดี
TimZaman

43

ใช้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/


11
นี่ไม่ใช่คำตอบที่ดีเนื่องจากเกี่ยวข้องกับการประมูลเหตุการณ์ DOM ... โซลูชันที่ดีกว่ามากคือการใช้เฉพาะ d3 และ css
alem0lars

@ alem0lars สนุกพอรุ่น css / d3 ใช้งานไม่ได้สำหรับฉันและอันนี้ทำ ...
Christian

32

อัปเดตเพียงใช้วิธีใหม่จาก @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 สูตรรวมถึงประเภทของการบรรจุซ้ำที่คุณพูดถึงและไม่สามารถใช้งานได้กับหลาย ๆ แปลงเช่นประเภทที่ใช้ในคิวบิสม์
Eamonn Kenny

1
@EamonnKenny ฉันไม่เห็นด้วยกับคุณมากขึ้น คำตอบอื่น ๆ 7 เดือนในอนาคตจะดีกว่า :)
SLF

สำหรับวิธีนี้: "d3.select (หน้าต่าง) .on" ฉันหาไม่พบ "d3.select (หน้าต่าง) .off" หรือ "d3.select (หน้าต่าง) .unbind"
sea-kg


คำตอบนี้ไม่ได้ด้อยกว่า คำตอบของพวกเขาจะปรับขนาดทุกด้านของกราฟ (รวมถึงเห็บแกนเส้นและคำอธิบายประกอบแบบข้อความ) ซึ่งอาจดูเคอะเขินในขนาดหน้าจอบางขนาดไม่ดีในที่อื่น ๆ และอ่านไม่ได้ในขนาดสุดขีด ฉันคิดว่าการปรับขนาดโดยใช้วิธีนี้ (หรือสำหรับโซลูชันที่ทันสมัยกว่านั้นคือ @Angu Agarwal)
Rúnar Berg

12

มันเป็นเรื่องที่น่าเกลียดถ้ารหัสการปรับขนาดเกือบจะตราบเท่าที่รหัสสำหรับการสร้างกราฟในตอนแรก ดังนั้นแทนที่จะปรับขนาดองค์ประกอบทั้งหมดของแผนภูมิที่มีอยู่แล้วทำไมไม่โหลดใหม่เพียงอย่างเดียว นี่คือวิธีการทำงานสำหรับฉัน:

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 อีกหนึ่งรายการด้านล่างองค์ประกอบก่อนหน้า


นี่จะเป็นการฆ่าประสิทธิภาพหากคุณวาดองค์ประกอบทั้งหมดในทุกหน้าต่างเพื่อปรับขนาดเหตุการณ์
sdemurjian

2
แต่สิ่งนี้ถูกต้อง: ในขณะที่คุณแสดงผลคุณสามารถตรวจสอบว่าคุณควรมีแกนแทรกอยู่ซ่อนคำอธิบายภาพทำสิ่งอื่น ๆ ตามเนื้อหาที่มีอยู่หรือไม่ การใช้โซลูชัน CSS / viewbox อย่างหมดจดสิ่งที่ทำก็คือทำให้การมองเห็นภาพถูกบีบอัด ดีสำหรับรูปภาพไม่ดีต่อข้อมูล
Chris Knoll

8

ในรูปแบบบังคับเพียงแค่ตั้งค่าคุณลักษณะ 'ความสูง' และ 'ความกว้าง' จะไม่ทำงานเพื่อจัดกึ่งกลาง / ย้ายพล็อตไปยังคอนเทนเนอร์ svg อีกครั้ง แต่มีคำตอบง่ายมากว่างานสำหรับกองทัพเค้าโครงพบที่นี่ สรุป:

ใช้ eventing ใด ๆ ที่คุณต้องการ

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 จะคำนวณสิ่งต่าง ๆ ตามความจำเป็น อย่างน้อยก็ใช้งานได้เมื่อคุณใช้แรงโน้มถ่วง ฉันไม่แน่ใจว่านี่เป็นข้อกำหนดเบื้องต้นสำหรับโซลูชันนี้หรือไม่ ทุกคนสามารถยืนยันหรือปฏิเสธได้ไหม

ไชโย


สิ่งนี้ทำงานได้อย่างสมบูรณ์ในรูปแบบแรงของฉันซึ่งมีการเชื่อมต่อประมาณ 100 ครั้ง ขอบคุณ
tribe84

1

สำหรับผู้ใช้กราฟบังคับโดยตรงใน D3 v4 / v5 sizeวิธีการไม่มีอยู่อีกต่อไป สิ่งต่อไปนี้ใช้งานได้สำหรับฉัน (จากปัญหา GitHub นี้ ):

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();

1

หากคุณต้องการผูกตรรกะที่กำหนดเองเพื่อปรับขนาดเหตุการณ์ทุกวันนี้คุณอาจเริ่มใช้ResizeObserver เบราว์เซอร์ 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()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.