I am working on a setup, where it is possible for a user to draw an arrow at a certain canvas_size
on top of e.g. a picture with a fixed aspect ratio. If watching the picture on a different screen_size
the arrow should still have the same thickness, and still be pointing at the same thing (if e.g. resizing the screen the arrow_marker_size
is recomputed (not included in code-snippet)).
Currently I have set this up by having a transparent line with an arrow head marker with markerUnits="strokeWidth"
and then I have a line with the wanted color with vectorEffect="non-scaling-stroke"
(I have been able to create a more simple solution with a fixed arrow_marker_size
and a single line, but that doesn't work in Safari, where the arrow head e.g. gets very small when lowering the screen_size
). This seems to work, but I am open for better solutions - but my actual problem is that I can't figure out how to make sure that the arrow head has rounded corners???
class SvgWithArrow extends React.Component {
constructor(props) {
super(props);
this.state = {
arrow_marker_size: 5
};
}
render = () => {
const { x1, y1, x2, y2, color } = this.props.arrow;
const canvasPositionStyle = {
width: this.props.screen_size.width 'px',
height: this.props.screen_size.height 'px',
background: "blue"
};
return (
<div style={canvasPositionStyle}>
<svg viewBox={'0 0 ' this.props.canvas_size.width ' ' this.props.canvas_size.height}>
<line
x1={x1}
y1={y1}
x2={x2}
y2={y2}
markerEnd="url(#arrow-1)"
stroke="transparent"
strokeWidth="3px"
/>
<marker
id="arrow-1"
markerWidth={this.state.arrow_marker_size}
markerHeight={this.state.arrow_marker_size}
refX={this.state.arrow_marker_size}
refY={this.state.arrow_marker_size / 2}
orient="auto"
markerUnits="strokeWidth"
>
<path
fill="none"
stroke={color}
vectorEffect="non-scaling-stroke"
// TODO rounded corners?!! This isn't working...
strokeLinejoin="round"
strokeLinecap="round"
d={'M0, ' this.state.arrow_marker_size
' L' this.state.arrow_marker_size ','
(this.state.arrow_marker_size / 2) ' 0, 0'}
/>
</marker>
<line
x1={x1}
y1={y1}
x2={x2}
y2={y2}
stroke={color}
strokeWidth="3px"
strokeLinecap="round"
vectorEffect="non-scaling-stroke"
/>
</svg>
</div>
);
}
}
// Render it
ReactDOM.render(
<SvgWithArrow
screen_size={{ width: 100*(16/9), height: 100 }}
canvas_size={{ width: 150*(16/9), height: 150 }}
arrow={{x1: 100, y1: 110, x2: 50, y2: 40, color: "red" }} />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
CodePudding user response:
The path line of the marker is cut off, so the marker "box" needs to be larger than the line – or the line needs to be smaller. So, the linecaps are there, but they are outside the marker element. I changed the path so that it has that margin stating with – something like: d="M 1 5 L 5 3 L 1 1"
and adding 1 to this.state.arrow_marker_size
some places.
class SvgWithArrow extends React.Component {
constructor(props) {
super(props);
this.state = {
arrow_marker_size: 5
};
}
render = () => {
const { x1, y1, x2, y2, color } = this.props.arrow;
const canvasPositionStyle = {
width: this.props.screen_size.width 'px',
height: this.props.screen_size.height 'px',
background: "blue"
};
return (
<div style={canvasPositionStyle}>
<svg viewBox={'0 0 ' this.props.canvas_size.width ' ' this.props.canvas_size.height}>
<line
x1={x1}
y1={y1}
x2={x2}
y2={y2}
markerEnd="url(#arrow-1)"
stroke="transparent"
strokeWidth="3"
/>
<marker
id="arrow-1"
markerWidth={this.state.arrow_marker_size 1}
markerHeight={this.state.arrow_marker_size 1}
refX={this.state.arrow_marker_size}
refY={(this.state.arrow_marker_size 1) / 2}
orient="auto"
markerUnits="strokeWidth"
>
<path
fill="none"
stroke={color}
vectorEffect="non-scaling-stroke"
// TODO rounded corners?!! This isn't working...
strokeLinejoin="round"
strokeLinecap="round"
d={`M 1 ${this.state.arrow_marker_size}
L ${this.state.arrow_marker_size} ${((this.state.arrow_marker_size 1) / 2)}
L 1 1`}
/>
</marker>
<line
x1={x1}
y1={y1}
x2={x2}
y2={y2}
stroke={color}
strokeWidth="3px"
strokeLinecap="round"
vectorEffect="non-scaling-stroke"
/>
</svg>
</div>
);
}
}
// Render it
ReactDOM.render(
<SvgWithArrow
screen_size={{ width: 100*(16/9), height: 100 }}
canvas_size={{ width: 150*(16/9), height: 150 }}
arrow={{x1: 100, y1: 110, x2: 50, y2: 40, color: "red" }} />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>