2017-06-25 18 views
4

私はReact generate SVGを使って可視化アプリケーションを作成しています。私が必要とする部分の1つは、ラベル(つまり、囲み枠で囲まれたテキスト、可変テキスト付き、可能であれば回転およびスタイル付き)です。SVGバウンディングボックスのReact?

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> 
    } 

そして、私はここで、DOMでこのことについていくつかの情報を見つけた:

は、だから私は、固定された寸法で、現在、NodeLabelのためのコンポーネントを持ってRectangle border around SVG text

をしかし、私は「ドンこれをReactコンポーネントに変換する方法をよく見てください。render()メソッドの内部では、見るべきDOM要素はありません。代わりにdocument.createElement()を使用して、SVG要素のディメンションが正しく動作する(そしてCSSに敬意を表する)ことを期待できますか?また、作成コードを本質的に2つコピーするのを避ける方法はありますか?JSXに1つ、直前に1つです。 (たとえば、この一時的なオフスクリーンコピーのJSXスニペットをDOM要素に評価するなど)

更新:2018年1月更新:実際のアプリケーションはオープンソースのネットワーク図です現在GDとPHPを使用していますが、JS、React、SVGに移行しています。

ここでの帯域幅ラベルは、私が再現しようとしているラベルですが、ノードラベルは現在の非SVGバージョンで同じ機能を使用しています。ここで

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

私の新しい最小限の例である:あなたのいずれかの事前計算/あなたの書体の形状を測定し、入力に基づいてテキストの大きさの合理的な見積りを取得

// 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>

+0

それだけでなく、質問内の最小限の例を含めることが良いでしょう。 – Jeroen

+0

本当です。私は今それを追加します。 – AnotherHowie

+0

bboxを入手するのに反応型リファレンスを使用しないのはなぜですか? –

答えて

2

文字列(これは最も簡単な解決策ですが、書体が変わった場合は明らかに壊れます)、または2段階のレンダリングを実行します:

あなたは、REFを経由してDOM要素を取得し、状態を更新することにより、マウントのボックスを取得し、最終的に再描画

、何かのように:最新ごとにドキュメントを反応させる、ことを

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()がこの場合に2回呼び出されても、ユーザーは中間状態を見ることができません。このパターンは、パフォーマンスの問題を引き起こすことがあるため、慎重に使用してください。しかし、サイズや位置に依存するものをレンダリングする前にDOMノードを測​​定する必要がある場合は、モーダルやツールチップのような場合には必要です。

最後に、ラベル文字列が(小道具などで)変更された場合は、それに応じてエクステントを更新する必要があります(componentDidUpdate())。

+0

幸いにも、テキスト自体は常に変化していません:-) これはDaniel Khoroshkoのコメントに基づいて、私がcodepenでちょっと考え出した解決策には十分です。私の任意の最初のものよりもあなたのヌルエクステントを好む以外は! – AnotherHowie

0

// 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

これらの動的名前付き参照はなぜ持っていますか? また、動作しません - ボックスはテキストを囲みません。 – AnotherHowie

+0

@AnotherHowie編集を一度確認して、これがあなたが望むものかどうかを確認してください。前に動的参照を追加してしまったのは間違いです。 – cauchy

関連する問題