How to use markerEnd in react to draw an arrow - javascript

I want to draw an arrow with react. I found that maker-end is supported, see: React Docs. It is called markerEnd instead of marker-end. It expect a 'url(#markerArrow)' but it doesn't seem to work in react. How to make an arrow with svg and react?
Here is my approach in a component subclasses render function: It draws the line but not the arrow head.
render() {
return(
<div>
<svg className='Line' style={{ height:height, width:width, top:top, left:left }}>
<defs>
<marker id="markerArrow" markerWidth="13" markerHeight="13" refX="2" refY="6"
orient="auto">
<path d="M2,2 L2,11 L10,6 L2,2" style={{fill: '#000000'}} />
</marker>
</defs>
<line x1={lx1} y1={ly1} x2={lx2} y2={ly2} markerEnd={ 'url(#markerArrow)' } style={{ stroke: 'red', strokeWidth: 2 }} />
</svg>
</div>
);
}
There should be an arrow on the bottom left of the red line.

keep your <defs> tag inside svg element
<defs>
<marker id="markerArrow" markerWidth="13" markerHeight="13" refX="2" refY="6" orient="auto">
<path d="M2,2 L2,11 L10,6 L2,2" />
</marker>
</defs>
<svg width=200 height=200>
<line x1="10" y1="10" x2="100" y2="100" style="stroke:#006600; marker-end: url(#markerArrow)" />
</svg>
<svg width=200 height=200>
<defs>
<marker id="markerArrow1" markerWidth="13" markerHeight="13" refX="2" refY="6" orient="auto">
<path d="M2,2 L2,11 L10,6 L2,2" />
</marker>
</defs>
<line x1="10" y1="10" x2="100" y2="100" style="stroke:#006600; marker-end: url(#markerArrow1)" />
</svg>
if this didnt work,try moving <defs> outside of react component and place in seperate svg in the page.you can check the implementation here

Just to help others who are looking for an implementation of marker-end in React:
Your code is totally correct!
There is most likely another problem. Perhaps a different element is on top of your <line>. You could check that for example by giving all of your elements an opacity attribute.

Related

SVG connect two points with a line, and automatically update the line if a point is moved

I'd like to connect two points (circles) with a line:
window.onclick = () => {
document.getElementById('c2').setAttribute("cx", 150);
}
<svg>
<circle cx="10" cy="10" r="2" id="c1" />
<circle cx="90" cy="50" r="2" id="c2" />
<line x1="10" y1="10" x2="90" y2="50" stroke="black" />
</svg><br>
Click here to move a circle.
such that if I modify the center of any <circle> with setAttribute("cx", 150) then the line automatically follows the new circle position.
Is there a way to do this with <use>? Something like (pseudo-code):
<svg>
<circle cx="10" cy="10" r="2" id="c1" />
<circle cx="90" cy="50" r="2" id="c2" />
<use x1=xlink:c1:cx y1=xlink:c1:cy x2=xlink:c2:cx y2=xlink:c2:cy stroke="black" type="line" />
</svg>
Goal: I don't want to have to set the coordinates two times, in both the circle and line. Instead I would like to set the coordinates once, and that the line uses a reference to the circle elements.
Note: I have read SVG connect two points with a line but it did not help.
You can use a <marker> that can be placed on start, middle and end of an element.
window.onclick = () => {
document.getElementById('l1').setAttribute("x2", 150);
}
<svg viewBox="0 0 200 100" width="200">
<defs>
<marker id="circle" viewBox="0 0 4 4" refX="2"
refY="2" markerWidth="4" markerHeight="4">
<circle cx="2" cy="2" r="2" />
</marker>
</defs>
<line id="l1" x1="10" y1="10" x2="90" y2="50" stroke="black"
marker-start="url(#circle)" marker-end="url(#circle)"/>
</svg><br>
Click here to move a circle.

How to stroke SVG elements together, as a single element [duplicate]

I have two shapes: circle and rectangle. Want to convert them into one figure. Are there any ways to do that in SVG code?
<svg width="400" height="400">
<defs>
<g id="shape" fill="none" stroke="red">
<rect x="40" y="50" width="40" height="70" />
<circle cx="50" cy="50" r="50" />
</g>
</defs>
<use xlink:href="#shape" x="50" y="50" />
<use xlink:href="#shape" x="200" y="50" />
</svg>
Like this:
For anyone looking for the answer to the actual question of how to combine two outlined shapes into a single outlined shape (rather than putting a drop shadow on the combined shape), here is a possible solution:
<svg width="400" height="400">
<defs>
<rect id="canvas" width="100%" height="100%" fill="white" />
<rect id="shape1" x="40" y="50" width="40" height="70" />
<circle id="shape2" cx="50" cy="50" r="50" />
<mask id="shape1-cutout">
<use href="#canvas" />
<use href="#shape1" />
</mask>
<mask id="shape2-cutout">
<use href="#canvas" />
<use href="#shape2" />
</mask>
</defs>
<use href="#shape1" stroke="red" fill="none" mask="url(#shape2-cutout)" />
<use href="#shape2" stroke="red" fill="none" mask="url(#shape1-cutout)" />
</svg>
This essentially draws the circle with the rectangle shape cut out of it and draws the rectangle with the circle cut out of it. When you place these "punched out" shapes one on top of the other, you get what appears to be a single outlined shape.
Here's what the SVG actually does:
It defines a white rectangle called "canvas" that is the same size as the SVG.
It defines the two shapes that are to be combined ("shape1" and "shape2").
It defines a mask for each shape that combines the canvas (which has a fill of white) with the shape (which has a fill of black by default). Note that when you apply a mask to a shape, the part of the shape that corresponds to the white area of the mask is shown, while the part that corresponds with black part is hidden.
It draws each shape with the the mask of the other shape applied.
You can make a <mask> or a <clipPath> from the two shapes and then use that to mask a third shape. You can then apply your drop shadow to that.
<svg width="400" height="400">
<defs>
<clipPath id="shape">
<rect x="40" y="50" width="40" height="70" />
<circle cx="50" cy="50" r="50" />
</clipPath>
<filter id="shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
<feOffset dx="3" dy="3"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g filter="url(#shadow)">
<rect width="100%" height="100%" fill="red"
clip-path="url(#shape)"/>
</g>
</svg>
Note: if you are wondering why we are applying the drop shadow to a parent <g> here, it is because if we applied it directly to the <rect>, the drop shadow would be subject to the clip also.
What's wrong with just a dropshadow on a group around the shapes?
<svg width="400" height="400">
<defs>
<filter id="shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="3"/>
<feOffset dx="3" dy="3"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g filter="url(#shadow)">
<rect x="40" y="50" width="40" height="70" fill="red"/>
<circle cx="50" cy="50" r="50" fill="red"/>
</g>
</svg>
Note: Be aware that this solution is only giving you the outer part of the contour.
A little variation of #devuxer's answer. A shadow can be created by a mask that can then be applied to a black shape. In https://stackoverflow.com/a/50884092/275195 two masks are created with set theory (A-B)+(B-A). However in the rendering engine a pixel raster is used for masks and antialiasing is applied. This leads to half-transparent pixels in the mask at the boundary of A and B. This can be avoided by applying the shaped to one mask like this (basically outline of A+B minus A+B):
<svg>
<!-- Masks seem to be pre-filled with black -->
<mask id="union">
<!-- Whatever is white in the mask will be visible of the shape the mask is applied to. -->
<rect id="A" x="20" y="20" width="50" height="100" stroke="white" fill="none"></rect>
<!-- Putting a second (, third, ...) shape into the mask will draw the shape over any existing shapes. -->
<circle id="B" cx="80" cy="60" r="30" stroke="white" fill="none"></circle>
<!-- Now we delete the inner part of the shapes by drawing black over them. -->
<rect id="Ainner" x="20" y="20" width="50" height="100" fill="black"></rect>
<circle id="Binner" cx="80" cy="60" r="30" fill="black"></circle>
</mask>
<!-- Applied to a full-screen red rectangle it cuts out the strokes from the mask. -->
<rect id="A+B" x="0" y="0" width="100%" height="100%" fill="red" mask="url(#union)"></rect>
</svg>
For everyone who wants both fill and border, here is a combination of devuxers and pascals posts
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<defs>
<rect id="canvas" width="100%" height="100%" fill="white" />
<rect id="rect" x="50" y="60" width="40" height="70" />
<circle id="circle" cx="50" cy="50" r="40" />
<clipPath id="shape">
<use href="#rect" />
<use href="#circle" />
</clipPath>
<mask id="maskRect">
<use href="#canvas" />
<use href="#rect" />
</mask>
<mask id="maskCircle">
<use href="#canvas" />
<use href="#circle" />
</mask>
</defs>
<rect width="100%" height="100%" fill="red" clip-path="url(#shape)" />
<use href="#rect" stroke="green" stroke-width="2" fill="none" mask="url(#maskCircle)" />
<use href="#circle" stroke="green" stroke-width="2" fill="none" mask="url(#maskRect)" />
</svg>
With the feMorphology filter you can grow and shrink objects (MDN). Combining them, you can create a simply reusable outline: make an increased copy of the original, paint it, and place it under the shrunk original.
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400">
<defs>
<filter id="outline">
<!-- Make a copy of the original,
grow it by half the stroke width,
call it outer -->
<feMorphology
operator="dilate"
radius="1"
result="outer"
/>
<!-- Make another copy of the original,
shrink it by half the stroke width,
call it inner -->
<feMorphology
in="SourceGraphic"
operator="erode"
radius="1"
result="inner"
/>
<!-- Create a color layer... -->
<feFlood
flood-color="black"
flood-opacity="1"
result="outlineColor"
/>
<!-- ...and crop it by outer -->
<feComposite
operator="in"
in="outlineColor"
in2="outer"
result="coloredOuter"
/>
<!-- Place the shrunk original over it -->
<feComposite
operator="over"
in="inner"
in2="coloredOuter"
/>
</filter>
</defs>
<g filter="url(#outline)">
<rect x="50" y="60" width="40" height="70" fill="red" />
<circle cx="60" cy="60" r="50" fill="red" />
</g>
</svg>
If you want to have it transparent, replace the operator in the last feComposite with xor:
<!-- Knock off the shrunk one -->
<feComposite
operator="xor"
in="inner"
in2="coloredOuter"
/>
The only caveat: fatter strokes - ie larger dilate and erode radii - on curves may look distorted, with 1px + 1px it is still fine.

Convert svg to react-native svg

I have the following svg code and I want to convert it to react-native-svg. How is it done correctly?
<svg width=200 height=200>
<defs>
<marker id="markerArrow1" markerWidth="13" markerHeight="13" refX="2" refY="6" orient="auto">
<path d="M2,2 L2,11 L10,6 L2,2" />
</marker>
</defs>
<line x1="10" y1="10" x2="100" y2="100" style="stroke:#006600; marker-end: url(#markerArrow1)" />
</svg>
react-native-svg is an amazing package that has only a few differences. First you need to validate everything you're using is supported.
Going through the documentation you'll find that most things just have a difference in capitalization.
At present, Marker is not supported (check the ToDo's in the documentation). If you can be good to go.
You could use react-native-remote-svg, that would allow you to load any SVG code.
import React from 'react';
import Image from 'react-native-remote-svg';
class MyView extends React.Component {
render() {
const render =
"<svg width=200 height=200>" +
"<defs>" +
'<marker id="markerArrow1" markerWidth="13" markerHeight="13" refX="2" refY="6" orient="auto">' +
'<path d="M2,2 L2,11 L10,6 L2,2" />' +
"</marker>" +
"</defs>" +
'<line x1="10" y1="10" x2="100" y2="100" style="stroke:#006600; marker-end: url(#markerArrow1)" />' +
"</svg>";
return (
<Image
style={{ flex:1 }}
source={{
uri: "data:image/svg+xml;utf8," + render
}}
/>
);
}
Which gives me this image:

How to fill an SVG element half portion by a pattern

I have some SVG elements on which there are some pattern already applied. The pattern is applied as fill color. That means the pattern fills up whole SVG element. Basically i want to partially fill up my element using the pattern.
Example
After applying pattern:
I want this pattern to apply on bottom half of the circle. Is there any way to do that?
What you could do is create two circles and apply mask on one with fill
<svg width="105px" height="105px">
<mask x="0" y="0" id="half">
<rect y="50%" fill="white" width="100%" height="50%" />
</mask>
<circle fill="transparent" stroke="black" stroke-width="3px" cx="50%" cy="50%" r="50"/>
<circle fill="#C04C4C" mask="url(#half)" stroke="black" stroke-width="3px" cx="50%" cy="50%" r="50"/>
</svg>
use something like this
<svg height="300" width="300">
<defs>
<linearGradient id="Gradient-1" x1="50%" y1="0%" x2="50%" y2="100%">
<stop offset="50%" stop-color= "white" />
<stop offset="51%" stop-color= "red" />
</linearGradient>
<linearGradient id="repeat"xlink:href="#Gradient-1" spreadMethod="repeat" />
</defs>
<circle cx="100" cy="100" r="100"
fill= "url(#repeat)"
stroke="#000000"
stroke-width="1px" />
</svg>

in svg: translate vs position x and y

When I want to position simple objects such as rect or line should I use transform attribute or x and y attributes?
// this
d3.selectAll('rect')
.attr('x', d => d)
.attr('y', 0)
// or this?
d3.selectAll('rect')
.attr("transform", d => `translate(${d}, 0)`);
What is the performance difference?
In SVG transform is not hardware accelerated. They have around the same performance for single elements (in my experience). However, I use transform more to move thing around because in SVG not all elements have a x or y attributes, consider...
<line x1="0" y1="0" x2="100" y2="100" />
<circle cx="100" cy="100" r="100" />
<path d="M 0 0 L 100 100" />
<rect x="0" y="0" width="100" height="100" />
You must write a different implementation for each of these elements if you are not using transform. One area where transform is indeed faster is moving a large number of elements, if you have...
<g transform="translate(100, 100)">
<line x1="0" y1="0" x2="100" y2="100" />
<circle cx="100" cy="100" r="100" />
<path d="M 0 0 L 100 100" />
<rect x="0" y="0" width="100" height="100" />
</g>
It will be less processing intensive than moving each element individually

Categories

Resources