SVG 顶部反应单选按钮组的高级定位和大小调整

Posted

技术标签:

【中文标题】SVG 顶部反应单选按钮组的高级定位和大小调整【英文标题】:Advanced positioning and sizing of a react radio button group on top of an SVG 【发布时间】:2018-11-16 18:59:32 【问题描述】:

编辑:here is a link to my app 当前正在显示我所指的问题。

我已尽力使帖子简洁彻底,但如果有点长,请提前道歉。不过我认为长度是有帮助的。

我觉得这是一项具有挑战性的任务,包括将 React 单选按钮组放置在显示图形的 SVG 顶部。我创建了以下代码 sn-p,其中包括两个组件(一个图形和一个容器组件),以帮助突出我遇到的问题。

代替实际图形(散点图、条形图、w/e),我只制作了一个带有 3 个彩色矩形的矩形 SVG。在 SVG 组件上,我还在右侧添加了 6 个黑框,我将其用作使用 D3 直接构建到 SVG 上的单选按钮组。

由于各种状态原因(主要是因为我希望我的容器组件保持图形的状态,因为容器组件会有其他需要按钮值的部分),我正在构建一组 React单选按钮,以替换 D3 SVG 单选按钮,但在按钮的定位方面遇到了困难。鉴于我制作 SVG 的方式(使用 Viewbox),直接构建在 SVG 上的 React 单选按钮会随着浏览器窗口宽度的变化而重新缩放。这很重要,不是因为我网站的浏览者会主动改变浏览器窗口的大小(他们不会),而是因为我不知道用户浏览器的宽度,而这种通过调整 SVG 大小来调整按钮大小意味着按钮总是看起来不错。

但是,React 单选按钮无法缩放。正如你在这里看到的:

<div style="width":"85%", "margin":"0 auto", "marginTop":"75px", "padding":"0", "position":"relative">
  <div style="width":"450px", "position":"absolute", "bottom":"32px", "left":"20px", "zIndex":"1">
      <h3>X-Axis Stat</h3>
      xStatButtons
  </div>

  <div style="position":"absolute", "top":"5px", "left":"8px", "zIndex":"1">
      <h3>Y-Axis Stat</h3>
      yStatButtons
  </div>
</div>

我使用 position = absolute|relative 以及 top|bottom|left 样式来将按钮定位在 SVG 的顶部。我还更改了 react 单选按钮 div 上的 z-index,以便它们位于 SVG 之上。

在查看代码之前,请运行代码sn -p并全屏打开,并增加和减少浏览器窗口的宽度。

class GraphComponent extends React.Component 

	constructor(props)  
		super(props);
    
		this.chartProps = 
			chartWidth: 400, // Dont Change These, For Viewbox 
			chartHeight: 200 // Dont Change These, For Viewbox 
		;
	

  // Lifecycle Components
	componentDidMount() 
    
		const  chartHeight, chartWidth, svgID  = this.chartProps;
		d3.select('#d3graph')
			.attr('width', '100%')
			.attr('height', '100%')
			.attr('viewBox', "0 0 " + chartWidth + " " + chartHeight)
			.attr('preserveAspectRatio', "xMaxYMax");
    
    const fakeButtons = d3.select('g.fakeButtons')
    for(var i = 0; i < 6; i++) 
      fakeButtons
        .append('rect')
        .attr('x', 370)
        .attr('y', 15 + i*25)
        .attr('width', 25)
        .attr('height', 20)
        .attr('fill', 'black')
        .attr('opacity', 1)
        .attr('stroke', 'black')
        .attr('stroke-width', 2) 
    
    
    d3.select('g.myRect')
      .append('rect')
        .attr('x', 0)
        .attr('y', 0)
        .attr('width', chartWidth)
        .attr('height', chartHeight)
        .attr('fill', 'red')
        .attr('opacity', 0.8)
        .attr('stroke', 'black')
        .attr('stroke-width', 5)
    
    d3.select('g.myRect')
      .append('rect')
        .attr('x', 35)
        .attr('y', 35)
        .attr('width', chartWidth - 70)
        .attr('height', chartHeight - 70)
        .attr('fill', 'blue')
        .attr('opacity', 0.8)
        .attr('stroke', 'black')
        .attr('stroke-width', 2)
    
    d3.select('g.myRect')
      .append('rect')
        .attr('x', 70)
        .attr('y', 70)
        .attr('width', chartWidth - 140)
        .attr('height', chartHeight - 140)
        .attr('fill', 'green')
        .attr('opacity', 0.8)
        .attr('stroke', 'black')
        .attr('stroke-width', 2)
	
  
	render() 
 		return (
			<div ref="scatter">
				<svg id="d3graph">
          <g className="myRect" />
          <g className="fakeButtons" />
				</svg>
			</div>
		)
	


class GraphContainer extends React.Component 

	constructor(props)  
		super(props);
    this.state = 
      statNameX: "AtBats",
      statNameY: "Saves"
    
	

	render() 
    
    const  statNameX, statNameY  = this.state;
    const xStats = [
       value: "GamesPlayed", label: "G" ,
       value: "AtBats", label: "AB" ,
       value: "Runs", label: "R" ,
       value: "Hits", label: "H" ,
       value: "SecondBaseHits", label: "2B" ,
       value: "ThirdBaseHits", label: "3B" ,
       value: "Homeruns", label: "HR" ,
       value: "RunsBattedIn", label: "RBI" ];
    const yStats = [
       value: "Wins", label: "W" ,
       value: "Losses", label: "L" ,
       value: "EarnedRunAvg", label: "ERA" ,
       value: "GamesPlayed", label: "G" ,
       value: "GamesStarted", label: "GS" ,
       value: "Saves", label: "SV" ,
       value: "RunsAllowed", label: "R"]; 
    
    const xStatButtons = 
          <form>
            <div className="qualify-radio-group scatter-group">
              xStats.map((d, i) => 
                return (
                  <label key='xstat-' + i>
                    <input
                      type="radio"
                      value=xStats[i].value
                    />
                    <span>xStats[i].label</span>
                  </label>
                )
              )
            </div>
          </form>;
    
    const yStatButtons = 
          <form>
            <div className="qualify-radio-group scatter-group">
              yStats.map((d, i) => 
                return (
                  <label  className="go-vert" key='ystat-' + i>
                    <input
                      type="radio"
                      value=yStats[i].value
                    />
                    <span>yStats[i].label</span>
                  </label>
                )
              )
            </div>
          </form>;
    
 		return (
			<div style="width":"85%", "margin":"0 auto", "marginTop":"75px", "padding":"0", "position":"relative">
        
        <div style="width":"450px", "position":"absolute", "bottom":"32px", "left":"20px", "zIndex":"1">
          <h3>X-Axis Stat</h3>
          xStatButtons
        </div>

        <div style="position":"absolute", "top":"5px", "left":"8px", "zIndex":"1">
          <h3>Y-Axis Stat</h3>
          yStatButtons
        </div>
        
        <GraphComponent />
      </div>
		)
	




ReactDOM.render(
  <GraphContainer />,
  document.getElementById('root')
);
.scatter-group input[type=radio] 
	visibility:hidden;
	width:0px;
	height:0px;
	overflow:hidden;


.scatter-group input[type=radio] + span 
	cursor: pointer;
	display: inline-block;
	vertical-align: top;
	line-height: 25px;
	padding: 2px 6px;
	border-radius: 2px;
	color: #333;
	background: #EEE;

	border-radius: 5px;
	border: 2px solid #333;

	margin-right: 2px;


.scatter-group input[type=radio]:not(:checked) + span 
	cursor: pointer;
	background-color: #EEE;
	color: #333;


.scatter-group input[type=radio]:not(:checked) + span:hover
	cursor: pointer;
	background: #888;


.scatter-group input[type=radio]:checked + span
	cursor: pointer;
	background-color: #333;
	color: #EEE;


.go-vert 
	display: block;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

<div id='root'>
  WORK
</div>

我的问题是这样的:我如何使用 React 单选按钮正确调整大小和定位,以便它们的行为(从大小和定位的角度来看)类似于构建单选按钮直接进入 SVG(黑框如何调整大小)?

感谢任何帮助。

【问题讨论】:

请创建一个minimal reproducible example 这太长了。 我可以尝试缩短代码,但我认为不会太长。它是 2 个短组件,其中一个有几个矩形,另一个有 2 个单选按钮组。 您是否尝试过使用 css scale() 功能? developer.mozilla.org/en-US/docs/Web/CSS/transform-function/… 【参考方案1】:

您的里程数可能会有所不同,但我建议您尝试使用vw 单位来调整单选按钮的大小。 1vw 等于视口的 1%,可用于单选按钮元素的 font-sizepaddingline-height 属性。

提取我对您的示例所做的更改:

    .scatter-group input[type=radio] + span 
        font-size: calc(6px + 0.8vw);
        line-height: 1;
        padding: 0.8vw;
    

首先,我将calcvw 结合起来,以获得在小视口上不低于6px 的流畅字体大小。 line-height 影响按钮的整体高度,因此我将其重置为 1,这意味着它将是 font-size 的 100%。最后,我使用vw 单位进行填充。您可以使用与以前相同的 calc 技巧来对尺寸进行细粒度控制。

示例并不完美,需要进行一些调整才能使其与实际条件下的 SVG 相匹配。按钮容器也应该根据视口进行调整,我没有触及那些,因为它不是问题范围。

所有主流浏览器都支持vwcalc()

参考: - https://www.sitepoint.com/css-viewport-units-quick-start/ - https://css-tricks.com/snippets/css/fluid-typography/ - https://www.smashingmagazine.com/2016/05/fluid-typography/

class GraphComponent extends React.Component 

	constructor(props)  
		super(props);
    
		this.chartProps = 
			chartWidth: 400, // Dont Change These, For Viewbox 
			chartHeight: 200 // Dont Change These, For Viewbox 
		;
	

  // Lifecycle Components
	componentDidMount() 
    
		const  chartHeight, chartWidth, svgID  = this.chartProps;
		d3.select('#d3graph')
			.attr('width', '100%')
			.attr('height', '100%')
			.attr('viewBox', "0 0 " + chartWidth + " " + chartHeight)
			.attr('preserveAspectRatio', "xMaxYMax");
    
    const fakeButtons = d3.select('g.fakeButtons')
    for(var i = 0; i < 6; i++) 
      fakeButtons
        .append('rect')
        .attr('x', 370)
        .attr('y', 15 + i*25)
        .attr('width', 25)
        .attr('height', 20)
        .attr('fill', 'black')
        .attr('opacity', 1)
        .attr('stroke', 'black')
        .attr('stroke-width', 2) 
    
    
    d3.select('g.myRect')
      .append('rect')
        .attr('x', 0)
        .attr('y', 0)
        .attr('width', chartWidth)
        .attr('height', chartHeight)
        .attr('fill', 'red')
        .attr('opacity', 0.8)
        .attr('stroke', 'black')
        .attr('stroke-width', 5)
    
    d3.select('g.myRect')
      .append('rect')
        .attr('x', 35)
        .attr('y', 35)
        .attr('width', chartWidth - 70)
        .attr('height', chartHeight - 70)
        .attr('fill', 'blue')
        .attr('opacity', 0.8)
        .attr('stroke', 'black')
        .attr('stroke-width', 2)
    
    d3.select('g.myRect')
      .append('rect')
        .attr('x', 70)
        .attr('y', 70)
        .attr('width', chartWidth - 140)
        .attr('height', chartHeight - 140)
        .attr('fill', 'green')
        .attr('opacity', 0.8)
        .attr('stroke', 'black')
        .attr('stroke-width', 2)
	
  
	render() 
 		return (
			<div ref="scatter">
				<svg id="d3graph">
          <g className="myRect" />
          <g className="fakeButtons" />
				</svg>
			</div>
		)
	


class GraphContainer extends React.Component 

	constructor(props)  
		super(props);
    this.state = 
      statNameX: "AtBats",
      statNameY: "Saves"
    
	

	render() 
    
    const  statNameX, statNameY  = this.state;
    const xStats = [
       value: "GamesPlayed", label: "G" ,
       value: "AtBats", label: "AB" ,
       value: "Runs", label: "R" ,
       value: "Hits", label: "H" ,
       value: "SecondBaseHits", label: "2B" ,
       value: "ThirdBaseHits", label: "3B" ,
       value: "Homeruns", label: "HR" ,
       value: "RunsBattedIn", label: "RBI" ];
    const yStats = [
       value: "Wins", label: "W" ,
       value: "Losses", label: "L" ,
       value: "EarnedRunAvg", label: "ERA" ,
       value: "GamesPlayed", label: "G" ,
       value: "GamesStarted", label: "GS" ,
       value: "Saves", label: "SV" ,
       value: "RunsAllowed", label: "R"]; 
    
    const xStatButtons = 
          <form>
            <div className="qualify-radio-group scatter-group">
              xStats.map((d, i) => 
                return (
                  <label key='xstat-' + i>
                    <input
                      type="radio"
                      value=xStats[i].value
                    />
                    <span>xStats[i].label</span>
                  </label>
                )
              )
            </div>
          </form>;
    
    const yStatButtons = 
          <form>
            <div className="qualify-radio-group scatter-group">
              yStats.map((d, i) => 
                return (
                  <label  className="go-vert" key='ystat-' + i>
                    <input
                      type="radio"
                      value=yStats[i].value
                    />
                    <span>yStats[i].label</span>
                  </label>
                )
              )
            </div>
          </form>;
    
 		return (
			<div style="width":"85%", "margin":"0 auto", "marginTop":"75px", "padding":"0", "position":"relative">
        
        <div style="width":"450px", "position":"absolute", "bottom":"32px", "left":"20px", "zIndex":"1">
          <h3>X-Axis Stat</h3>
          xStatButtons
        </div>

        <div style="position":"absolute", "top":"5px", "left":"8px", "zIndex":"1">
          <h3>Y-Axis Stat</h3>
          yStatButtons
        </div>
        
        <GraphComponent />
      </div>
		)
	




ReactDOM.render(
  <GraphContainer />,
  document.getElementById('root')
);
.scatter-group input[type=radio] 
	visibility:hidden;
	width:0px;
	height:0px;
	overflow:hidden;


.scatter-group input[type=radio] + span 
	cursor: pointer;
	display: inline-block;
	vertical-align: top;
	line-height: 1;
	padding: 0.8vw;
    font-size: calc(6px + 0.8vw);
	border-radius: 2px;
	color: #333;
	background: #EEE;

	border-radius: 5px;
	border: 2px solid #333;

	margin-right: 2px;


.scatter-group input[type=radio]:not(:checked) + span 
	cursor: pointer;
	background-color: #EEE;
	color: #333;


.scatter-group input[type=radio]:not(:checked) + span:hover
	cursor: pointer;
	background: #888;


.scatter-group input[type=radio]:checked + span
	cursor: pointer;
	background-color: #333;
	color: #EEE;


.go-vert 
	display: block;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>

<div id='root'>
  WORK
</div>

【讨论】:

我以前只使用过一次 vw 来处理一些字体文本 - 很高兴看到它也可以以这种方式工作。很快就会尝试一下,可能会接受作为答案。谢谢! 我希望我在问题的范围内包括按钮的定位 - 对此有何提示?除了使用相同的 calc() 和 vw() 方法吗? @Canovic 看这里:codepen.io/teodragovic/pen/zadoZE?editors=0110

以上是关于SVG 顶部反应单选按钮组的高级定位和大小调整的主要内容,如果未能解决你的问题,请参考以下文章

如何调整 matplotlib 单选按钮的大小和纵横比?

引导和反应,在单选按钮上分配 .active 类

如何在 GridBagLayout 中调整 JPanel 的大小?

如何以编程方式更改表格单元格中单选按钮组的单选按钮?

获取单选按钮组的值

在 ASP.NET 中设置 Twitter Bootstrap 按钮单选组的活动状态