I am writing a visualisation application with React generating SVG. One of the parts I need is a label - that is, text, surrounded by an enclosing box, with variable text, possibly rotated and styled.
So I have a component for the NodeLabel
, currently with fixed dimensions:
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've found some info about doing this in the DOM, here: Rectangle border around SVG text
But I don't quite see how to translate this into a React component - inside the render() method, there's no DOM elements to look at. Can I just use document.createElement()
instead and expect an SVG element's dimensions to behave properly (and honour CSS)? Also, is there a way to avoid having essentially two copies of the creation code, one in JSX and one just before that to figure out dimensions? (like, for example, evaluating a snippet of JSX to DOM elements for this temporary off-screen copy)
Update: Jan 2018 and I'm back at this again :-) The actual application is an open source network diagramming tool, currently using GD and PHP, but moving to JS, React and SVG, I hope.
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.
Here is my new minimal example:
// 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>