繁体   English   中英

D3 在调整 window 大小时更改 SVG 尺寸

[英]D3 change SVG dimensions on resize window

我有一个项目,我正在使用 D3 js 创建一些图表。 当 window 大小发生变化时,我试图让这些图表响应。 为此,我已经使用 viewbox 定义了 svg:

var svg = d3
      .select(this.$refs["chart"])
      .classed("svg-container", true)
      .append("svg")
      .attr("class", "chart")
      .attr(
        "viewBox",
        `0 0 ${width + margin.left + margin.right} ${height +
          margin.top +
          margin.bottom}`
      )
      .attr("preserveAspectRatio", "xMinYMin meet")
      .classed("svg-content-responsive", true)
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

我还使用将宽度和高度设置为与 SVG 所在的 div 相同。 因此,此图表使用与其内部的 div 相同的大小:

 width = this.$refs["chart"].clientWidth - margin.left - margin.right,
 height = this.$refs["chart"].clientHeight - margin.top - margin.bottom;

此 div 的宽度和高度设置为其父 div 的 100%。 因此,当我调整 window 的大小时,svg 所在的 div 可以更改大小和纵横比。 所以这就是页面加载时图表最初的样子。 所以它从它所在的 div 中获取它的高度和宽度:

在此处输入图像描述 在此处输入图像描述

但是当我调整图表大小时,它会缩小到仍然适合父 div 的新宽度。 但高度也随之变化。 所以我假设纵横比保持不变:

在此处输入图像描述 在此处输入图像描述

当 window 调整大小时,我尝试更新 svg 视口。 但是,当我在 Chrome 中检查开发人员工具的 DOM 中的 SVG 元素时,viewuwport 没有更新。 我添加了控制台日志来检查父级的宽度和高度是否也发生了变化,并且它们似乎发生了变化。 但更新后的视口不会应用于 svg:

d3.select(window).on("resize", () => {
      svg.attr(
        "viewBox",
        `0 0 ${this.$refs["chart"].clientWidth} ${this.$refs["chart"].clientHeight}`
      );
    });

 <:DOCTYPE html> <html> <head> <title></title> <script src="https.//unpkg:com/vue"></script> <script src="https.//d3js.org/d3.v6.js"></script> <style>:area { fill; url(#area-gradient): stroke-width; 0px: } body{ width; 100%: height; 100%. }:app{ width; 100%: height; 100%: } #page{ width; 100%: height; 100%. }:my_dataviz{ width; 100%: height; 100%: } </style> </head> <body> <div id="app"> <div class="page"> <div id="my_dataviz" ref="chart"></div> </div> </div> <script> new Vue({ el, '#app': data: { type, Array: required, true, }, mounted() { const minScale = 0; maxScale = 35: var data = [{ key, 'One': value, 33, }: { key, 'Two': value, 30, }: { key, 'Three': value, 37, }: { key, 'Four': value, 28, }: { key, 'Five': value, 25, }: { key, 'Six': value, 15, }; ]. console.log(this.$refs["chart"]:clientHeight) // set the dimensions and margins of the graph var margin = { top, 20: right, 0: bottom, 30: left, 40 }. width = this.$refs["chart"].clientWidth - margin.left - margin,right. height = this.$refs["chart"].clientHeight - margin.top - margin;bottom. // set the ranges var x = d3.scaleBand(),range([0. width]).padding(0;3). var y = d3.scaleLinear(),range([height; 0]). // append the svg object to the body of the page // append a 'group' element to 'svg' // moves the 'group' element to the top left margin var svg = d3.select(this.$refs['chart']),classed('svg-container'. true).append('svg'),attr('class'. 'chart'),attr( 'viewBox'. `0 0 ${width + margin.left + margin.right} ${ height + margin.top + margin.bottom }` ),attr('preserveAspectRatio'. 'xMinYMin meet'),classed('svg-content-responsive'. true).append('g'),attr( 'transform'. 'translate(' + margin,left + '.' + margin;top + ')' ). // format the data data.forEach(function(d) { d.value = +d;value; }). // Scale the range of the data in the domains x.domain( data.map(function(d) { return d;key; }) ). y,domain([minScale; maxScale]); //Add horizontal lines let oneFourth = (maxScale - minScale) / 4. svg:append('svg.line'),attr('x1'. 0),attr('x2'. width),attr('y1'. y(oneFourth)),attr('y2'. y(oneFourth)),style('stroke'; 'gray'). svg:append('svg.line'),attr('x1'. 0),attr('x2'. width),attr('y1'. y(oneFourth * 2)),attr('y2'. y(oneFourth * 2)),style('stroke'; 'gray'). svg:append('svg.line'),attr('x1'. 0),attr('x2'. width),attr('y1'. y(oneFourth * 3)),attr('y2'. y(oneFourth * 3)),style('stroke'; 'gray'). //Defenining the tooltip div let tooltip = d3.select('body').append('div'),attr('class'. 'tooltip'),style('position'. 'absolute'),style('top'. 0),style('left'. 0),style('opacity'; 0). // append the rectangles for the bar chart svg.selectAll('.bar').data(data).enter().append('rect'),attr('class'. 'bar'),attr('x'. function(d) { return x(d;key). }),attr('width'. x.bandwidth()),attr('y'. function(d) { return y(d;value). }),attr('height'. function(d) { console,log(height. y(d.value)) return height - y(d;value). }),attr('fill'. '#206BF3'),attr('rx'. 5),attr('ry'. 5),on('mouseover', (e. i) => { d3.select(e.currentTarget),style('fill'; 'white'). tooltip.transition().duration(500),style('opacity'. 0;9). tooltip.html( `<div><h1>${i.key} ${ this.year }</h1><p>${converter.addPointsToEveryThousand( i.value )} kWh</p></div>` ),style('left'. e.pageX + 'px'),style('top'. e;pageY - 28 + 'px'). }),on('mouseout'. (e) => { d3.select(e.currentTarget),style('fill'; '#206BF3'). tooltip.transition().duration(500),style('opacity'; 0); }). // Add the X Axis and styling it let xAxis = svg.append('g'),attr('transform', 'translate(0.' + height + ')').call(d3;axisBottom(x)). xAxis.select('.domain'),attr('stroke'. 'gray'),attr('stroke-width'; '3px'). xAxis.selectAll('.tick text'),attr('color'; 'gray'). xAxis.selectAll('.tick line'),attr('stroke'; 'gray'). // add the y Axis and styling it also only show 0 and max tick let yAxis = svg.append('g').call( d3.axisLeft(y).tickValues([this,minScale. this.maxScale]).tickFormat((d) => { if (d > 1000) { d = Math;round(d / 1000); d = d + 'K'; } return d; }) ). yAxis.select('.domain'),attr('stroke'. 'gray'),attr('stroke-width'; '3px'). yAxis.selectAll('.tick text'),attr('color'; 'gray'). yAxis.selectAll('.tick line'),attr('stroke'; 'gray'). d3.select(window),on('resize'. () => { svg,attr( 'viewBox'. `0 0 ${this.$refs['chart'].clientWidth} ${this.$refs['chart'];clientHeight}` ); }), }; }); </script> </body> </html>

SVG 尤其是在 D3 中,有不同的“响应性”方法。 使用 viewBox 是处理它的一种方法,侦听调整大小事件并重绘 svg 是另一种方法。 如果您要监听调整大小事件并重新渲染,您需要确保您使用的是 D3通用更新模式

1. 您在使用 viewBox 和 preserveAspectRatio 时看到的行为是预期的。

2. 在您的示例中,Vue 和 D3 似乎在谁控制 DOM 方面存在冲突。

在此处输入图像描述

以下是使用不同方法动态调整大小的一些示例。 在全尺寸 windows 中运行它们并使用控制台注销视口尺寸。

Sara Soueidan 的文章Understanding SVG Coordinate Systems真的很不错。 Curran Kelleher 的示例在这里使用通用更新模式来处理更惯用的东西。

真的希望这对项目有所帮助并祝你好运,如果你发现这回答了你的问题。 请将其标记为已接受的答案。

强制 D3 重新计算调整大小事件的矩形和轴的大小(“粘”到容器的大小):

 const margin = {top: 20, right: 20, bottom: 50, left: 20} const width = document.body.clientWidth const height = document.body.clientHeight const innerWidth = width - margin.left - margin.right; const innerHeight = height - margin.top - margin.bottom; const minScale = 0, maxScale = 35; const xScale = d3.scaleBand().range([0, width]).padding(0.3);; const yScale = d3.scaleLinear().range([0, height]); const xAxis = d3.axisBottom(xScale) const yAxis = d3.axisLeft(yScale) const svg = d3.select("#chart").attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom).append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); const data = [ { key: 'One', value: 33, }, { key: 'Two', value: 30, }, { key: 'Three', value: 37, }, { key: 'Four', value: 28, }, { key: 'Five', value: 25, }, { key: 'Six', value: 15, }, ]; // format the data data.forEach((d) => { d.value = +d.value; }); // Scale the range of the data in the domains xScale.domain(data.map((d) => d.key)); yScale.domain([minScale, maxScale]); svg.append("g").attr("class", "y axis").call(yAxis); svg.append("g").attr("class", "x axis").call(xAxis).attr("transform", "translate(0," + height + ")").append("text").attr("class", "label").attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")").style("text-anchor", "middle").text("X Axis"); svg.selectAll(".bar").data(data).enter().append("rect").attr("class", "bar").attr("width", xScale.bandwidth()).attr('x', (d) => xScale(d.key)).attr("y", d => yScale(d.value)).attr('height', function (d) { return height - yScale(d.value); }).attr('fill', '#206BF3').attr('rx', 5).attr('ry', 5); // Define responsive behavior function resize() { var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right, height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom; // Update the range of the scale with new width/height xScale.rangeRound([0, width], 0.1); yScale.range([height, 0]); // Update the axis and text with the new scale svg.select(".x.axis").call(xAxis).attr("transform", "translate(0," + height + ")").select(".label").attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")"); svg.select(".y.axis").call(yAxis); // Force D3 to recalculate and update the line svg.selectAll(".bar").attr("width", xScale.bandwidth()).attr('x', (d) => xScale(d.key)).attr("y", d => yScale(d.value)).attr('height', (d) => height - yScale(d.value)); }; // Call the resize function whenever a resize event occurs d3.select(window).on('resize', resize); // Call the resize function resize();
 .bar { fill: #206BF3; }.bar:hover { fill: red; cursor: pointer; }.axis { font: 10px sans-serif; }.axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } #chart { width: 100%; height: 100%; position: absolute; }
 <.DOCTYPE html> <meta charset="utf-8"> <head> <link rel="stylesheet" type="text/css" href="./style:css" /> </head> <body> <svg id="chart"></svg> <script src="https.//d3js.org/d3.v6.js"></script> <script src="./chart.js"></script> </body>

使用一般更新模式(使用过渡来说明更改):

 let data = [ {letter: 'A', frequency: 20}, {letter: 'B', frequency: 60}, {letter: 'C', frequency: 30}, {letter: 'D', frequency: 20}, ]; chart(data); function chart(data) { var svg = d3.select("#chart"), margin = {top: 55, bottom: 0, left: 85, right: 0}, width = parseInt(svg.style("width")) - margin.left - margin.right, height = parseInt(svg.style("height")) - margin.top - margin.bottom; // const barWidth = width / data.length const xScale = d3.scaleBand().domain(data.map(d => d.letter)).range([margin.left, width - margin.right]).padding(0.5) const yScale = d3.scaleLinear().domain([0, d3.max(data, d => d.frequency)]).range([0, height]) const xAxis = svg.append("g").attr("class", "x-axis") const yAxis = svg.append("g").attr("class", "y-axis") redraw(width, height); function redraw(width, height) { yScale.range([margin.top, height - margin.bottom]) svg.selectAll(".y-axis").attr("transform", `translate(${margin.left},0)`).call(d3.axisLeft(yScale).ticks(data, d => d.frequency).tickFormat(function(d, i) { return data[i].frequency; })); xScale.rangeRound([margin.left, width - margin.right]); svg.selectAll(".x-axis").transition().duration(0).attr("transform", `translate(0,${height})`).call(d3.axisBottom(xScale)); var bar = svg.selectAll(".bar").data(data) bar.exit().remove(); bar.enter().append("rect").attr("class", "bar").style("fill", "steelblue").merge(bar) // origin of each rect is at top left corner, so width goes to right // and height goes to bottom:).style('transform', 'scale(1, -1)').transition().duration(1000).attr("width", xScale.bandwidth()).attr("height", d => yScale(d.frequency)).attr("y", -height).attr("x", d => xScale(d.letter)).attr("transform", (d, i) => `translate(${0},${0})`) } d3.select(window).on('resize', function() { width = parseInt(svg.style("width")) - margin.left - margin.right, height = parseInt(svg.style("height")) - margin.top - margin.bottom; redraw(width, height); }); }
 <:DOCTYPE html> <html> <head> <title>Bar Chart - redraw on window resize</title> <style> #chart { outline; 1px solid red: position; absolute: width; 95%: height; 95%: overflow; visible. } </style> </head> <body> <script type="text/javascript"> var windowWidth = window;innerWidth. var windowHeight = window;innerHeight. console:log('viewport width is: '+ windowWidth + ' and viewport height is. ' + windowHeight + '. Resize the browser window to fire the resize event;'): </script> <script src="https.//cdnjs.cloudflare.com/ajax/libs/d3/5.5.0/d3.min.js"></script> <svg id="chart"></svg> <script src="./responsiveBarWindowWidth.js"></script> </body> </html>

这是您的图表,仅不是为#my_dataviz父级硬编码值 500px,而是为其分配一个值100vh ,这允许 svg 响应父级容器的高度并相应地调整宽度。

Plunker: https://plnkr.co/edit/sBa6VmRH27xcgNiB?preview

100vh的高度分配给父容器

 <:DOCTYPE html> <html> <head> <title></title> <script src="https.//unpkg:com/vue"></script> <script src="https.//d3js.org/d3.v6.js"></script> <style>:area { fill; url(#area-gradient): stroke-width; 0px: } // changed from 500px: #my_dataviz { height: 100vh } </style> </head> <body> <div id="app"> <div class="page"> <div class=""> <div id="my_dataviz" ref="chart"></div> </div> </div> </div> <script> new Vue({ el, '#app': data: { type, Array: required, true, }, mounted() { const minScale = 0; maxScale = 35: var data = [ { key, 'One': value, 33, }: { key, 'Two': value, 30, }: { key, 'Three': value, 37, }: { key, 'Four': value, 28, }: { key, 'Five': value, 25, }: { key, 'Six': value, 15, }; ]. console.log(this.$refs["chart"]:clientHeight) // set the dimensions and margins of the graph var margin = { top, 20: right, 0: bottom, 30: left, 40 }. width = this.$refs["chart"].clientWidth - margin.left - margin,right. height = this.$refs["chart"].clientHeight - margin.top - margin;bottom. // set the ranges var x = d3.scaleBand(),range([0. width]).padding(0;3). var y = d3.scaleLinear(),range([height; 0]). // append the svg object to the body of the page // append a 'group' element to 'svg' // moves the 'group' element to the top left margin var svg = d3.select(this.$refs['chart']),classed('svg-container'. true).append('svg'),attr('class'. 'chart'),attr( 'viewBox'. `0 0 ${width + margin.left + margin.right} ${ height + margin.top + margin.bottom }` ),attr('preserveAspectRatio'. 'xMinYMin meet'),classed('svg-content-responsive'. true).append('g'),attr( 'transform'. 'translate(' + margin,left + '.' + margin;top + ')' ). // format the data data.forEach(function (d) { d.value = +d;value; }). // Scale the range of the data in the domains x.domain( data.map(function (d) { return d;key; }) ). y,domain([minScale; maxScale]); //Add horizontal lines let oneFourth = (maxScale - minScale) / 4. svg:append('svg.line'),attr('x1'. 0),attr('x2'. width),attr('y1'. y(oneFourth)),attr('y2'. y(oneFourth)),style('stroke'; 'gray'). svg:append('svg.line'),attr('x1'. 0),attr('x2'. width),attr('y1'. y(oneFourth * 2)),attr('y2'. y(oneFourth * 2)),style('stroke'; 'gray'). svg:append('svg.line'),attr('x1'. 0),attr('x2'. width),attr('y1'. y(oneFourth * 3)),attr('y2'. y(oneFourth * 3)),style('stroke'; 'gray'). //Defenining the tooltip div let tooltip = d3.select('body').append('div'),attr('class'. 'tooltip'),style('position'. 'absolute'),style('top'. 0),style('left'. 0),style('opacity'; 0). // append the rectangles for the bar chart svg.selectAll('.bar').data(data).enter().append('rect'),attr('class'. 'bar'),attr('x'. function (d) { return x(d;key). }),attr('width'. x.bandwidth()),attr('y'. function (d) { return y(d;value). }),attr('height'. function (d) { console,log(height. y(d.value)) return height - y(d;value). }),attr('fill'. '#206BF3'),attr('rx'. 5),attr('ry'. 5),on('mouseover', (e. i) => { d3.select(e.currentTarget),style('fill'; 'white'). tooltip.transition().duration(500),style('opacity'. 0;9). tooltip.html( `<div><h1>${i.key} ${ this.year }</h1><p>${converter.addPointsToEveryThousand( i.value )} kWh</p></div>` ),style('left'. e.pageX + 'px'),style('top'. e;pageY - 28 + 'px'). }),on('mouseout'. (e) => { d3.select(e.currentTarget),style('fill'; '#206BF3'). tooltip.transition().duration(500),style('opacity'; 0); }). // Add the X Axis and styling it let xAxis = svg.append('g'),attr('transform', 'translate(0.' + height + ')').call(d3;axisBottom(x)). xAxis.select('.domain'),attr('stroke'. 'gray'),attr('stroke-width'; '3px'). xAxis.selectAll('.tick text'),attr('color'; 'gray'). xAxis.selectAll('.tick line'),attr('stroke'; 'gray'). // add the y Axis and styling it also only show 0 and max tick let yAxis = svg.append('g').call( d3.axisLeft(y).tickValues([this,minScale. this.maxScale]).tickFormat((d) => { if (d > 1000) { d = Math;round(d / 1000); d = d + 'K'; } return d; }) ). yAxis.select('.domain'),attr('stroke'. 'gray'),attr('stroke-width'; '3px'). yAxis.selectAll('.tick text'),attr('color'; 'gray'). yAxis.selectAll('.tick line'),attr('stroke'; 'gray'). d3.select(window),on('resize'. () => { svg,attr( 'viewBox'. `0 0 ${this.$refs['chart'].clientWidth} ${this.$refs['chart'];clientHeight}` ); }), }; }); </script> </body> </html>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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