Calculating SVG bounding rectangles with React?

I am writing a visualization application using React generating SVG. One of the parts I need is a label, that is, text surrounded by a closing box, with variable text, possibly rotated and styled.

So, I have a component for NodeLabel, currently with fixed sizes:

render() {
        return <g>
            <rect className="label" x={this.props.x} y={this.props.y-10} width={20} height={40}></rect>
            <text className="labelText" x={this.props.x} y={this.props.y}>{this.props.children}</text>
        </g>
    }

And I found some information about this in the DOM, here: The rectangular border around the SVG text

But I don’t quite understand how to translate this into a React component - there are no DOM elements to view inside the render () method. Can I just use it document.createElement()instead and expect the dimensions of the SVG elements to correctly lead (and respect CSS)? Also, is there a way to avoid essentially two copies of the creation code: one in JSX and one immediately before that for sizing? (for example, to evaluate the JSX fragment for DOM elements for this temporary copy off-screen)

Update: January 2018, and I will come back to this again :-) The actual application is an open source charting tool currently using GD and PHP, but hopefully move on to JS, React and SVG.

- , , node , SVG.

The bandwidth shortcuts here are what I'm trying to reproduce, although the node labels use the same function in the current version other than SVG.

:

// MyLabel should be centred at x,y, rotated by angle, 
// and have a bounding box around it, 2px from the text.
class MyLabel extends React.Component {
  render() {
    const label = <text x={this.props.x} y={this.props.y} textAnchor="middle" alignmentBaseline="central">{this.props.children}</text>;
        
    // label isn't a DOM element, so you can't call label.getBoundingClientRect() or getBBox()

    // (Magic happens here to find bbox of label..)        
    // make up a static one for now
    let bb = {x: this.props.x-20, y: this.props.y-6, width: 40, height: 12};
    
    // add margin
    const margin = 2;
    bb.width += margin * 2;
    bb.height += margin * 2;
    bb.x -= margin;
    bb.y -= margin;
    
    // rect uses bbox to decide its size and position
    const outline = <rect x={bb.x} y={bb.y} width={bb.width} height={bb.height} className="labeloutline"></rect>;
    
    const rot = `rotate(${this.props.angle} ${this.props.x} ${this.props.y})`;
    // build the final label (plus an x,y spot for now)
    return <g transform={rot}>{outline}{label}<circle cx={this.props.x} cy={this.props.y} r="2" fill="red" /></g>;
  }
}

class Application extends React.Component {
  render() {
    return <svg width={300} height={300}>
      <MyLabel x={100} y={100} angle={0}>Dalmation</MyLabel>
      <MyLabel x={200} y={100} angle={45}>Cocker Spaniel</MyLabel>
      <MyLabel x={100} y={200} angle={145}>Pug</MyLabel>
      <MyLabel x={200} y={200} angle={315}>Pomeranian</MyLabel>
    </svg>;
  }
}

/*
 * Render the above component into the div#app
 */
ReactDOM.render(<Application />, document.getElementById('app'));
body { background: gray; }
svg {background: lightgray;}
.labeloutline { fill: white; stroke: black;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="app"></div>
+4
2

/ , ( , , , , ) :

dom ref, mount , , , , :

class MyLabel extends React.Component {
  constructor(props){
    super(props);
    this.state = {text_extents:null};
  }
  componentDidMount() {
   const box = this.text.getBBox();

   this.setState({text_extents:[box.width,box.height]});
  }
 render() {
   const margin = 2;
   const extents = this.state.text_extents;
   const label = <text ref={(t) => { this.text = t; }} textAnchor="middle" dy={extents?(extents[1]/4):0} >{this.props.children}</text>;
   const outline = extents ?
         <rect x={-extents[0]/2-margin} y={-extents[1]/2-margin} width={extents[0]+2*margin} height={extents[1]+2*margin} className="labeloutline"></rect>
         : null;

   return <g transform={`translate(${this.props.x},${this.props.y}) rotate(${this.props.angle})`}>{outline}{label}</g>;
 }
}

, :

componentDidMount(): setState() , , . , render() , . , . , , DOM node, -, .

, , ( - ), ( componentDidUpdate()).

+2

// MyLabel should be centred at x,y, rotated by angle, 
// and have a bounding box around it, 2px from the text.
class MyLabel extends React.Component {
state={
x:this.props.x,
y:this.props.y,
width: 40,
height: 12,
angle: this.props.angle,
}
componentDidMount() {
 var reactDomElem = this.label.getBBox()
 //console.log(reactDomElem)
 this.setState({
 width:reactDomElem.width,
 height:reactDomElem.height,
 x:reactDomElem.x,
 y:reactDomElem.y,
 angle: this.state.angle,
 })
}
  render() {
    const label = <text ref={(ref)=>this.label = ref} x={this.state.x} y={this.state.y} textAnchor="middle" alignmentBaseline="baseline">{this.props.children}</text>;
        
    // label isn't a DOM element, so you can't call label.getBoundingClientRect() or getBBox()

    // (Magic happens here to find bbox of label..)        
    // make up a static one for now
    let bb = {x: this.state.x, y: this.state.y, width:this.state.width, height: this.state.height};
    
    // add margin
    const margin = 2;
    bb.width += margin * 2;
    bb.height += margin * 2;
    bb.x -= this.state.width/2;
    bb.y -= this.state.height/2 + margin*2;
    
    // rect uses bbox to decide its size and position
    const outline = <rect x={bb.x} y={bb.y} width={bb.width} height={bb.height} className="labeloutline"></rect>;
    
    const rot = `rotate(${this.state.angle} ${this.state.x} ${this.state.y})`;
    // build the final label (plus an x,y spot for now)
    return <g  transform={rot}>{outline}{label}<circle cx={this.state.x} cy={this.state.y} r="2" fill="red" /></g>;
  }
}

class Application extends React.Component {
  render() {
    return <svg width={300} height={300}>
      <MyLabel x={100} y={100} angle={0}>Dalmation</MyLabel>
      <MyLabel x={200} y={100} angle={45}>Cocker Spaniel</MyLabel>
      <MyLabel x={100} y={200} angle={145}>Pug</MyLabel>
      <MyLabel x={200} y={200} angle={315}>Pomeranian</MyLabel>
    </svg>;
  }
}

/*
 * Render the above component into the div#app
 */
ReactDOM.render(<Application />, document.getElementById('app'));
body { background: gray; }
svg {background: lightgray;}
.labeloutline { fill: white; stroke: black;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="app"></div>
0

Source: https://habr.com/ru/post/1680070/


All Articles