I am creating a simple compass with North, East, South, West directions. I created a TextPath so that my Text can curve along the circular compass.
I am facing one major problem: the beginning of the Text
is being cut-off. I cannot see the complete "North" label because of the textAnchor
being set to middle (which is what I prefer).
How can I make the text fully visible? After it is visible, then I can space the directions accordingly (25%) along the TextPath.
/* eslint-disable react/jsx-key */
import React, { Fragment, useState } from 'react'
import { Dimensions, View, Text } from 'react-native'
import {
Line,
Circle,
Text as SVGText,
Defs,
Path,
Use,
Svg,
G,
TextPath,
ForeignObject,
TSpan,
} from 'react-native-svg'
import { ReactNativeZoomableView } from '@openspacelabs/react-native-zoomable-view'
const getPoint = (R = 0, degree = 0) => {
// Convert the angle to a value between 0 and 360 degrees
const angle = ((degree % 360) + 360) % 360
// Calculate the coordinates using Math.sin and Math.cos
const x = R * Math.cos(angle * (Math.PI / 180))
const y = R * Math.sin(angle * (Math.PI / 180))
return [x, y]
}
const Compass = (props) => {
const [X, setW] = useState(1000)
const [Y, setH] = useState(1000)
const CoreRadius = 100
const PartRadius = 50
const [slider, setSlider] = useState(0)
const [point, setPoint] = useState([0, CoreRadius])
const rings = [
{
offset: -45,
degree: 90,
parts: [{ text: 'North' }, { text: 'East' }, { text: 'South' }, { text: 'West' }],
},
]
return (
<View style={{ flex: 1 }}>
<View style={{ position: 'absolute', padding: 12, top: 0, left: 0, right: 0, zIndex: 1 }}></View>
<ReactNativeZoomableView maxZoom={30} bindToBorders={false}>
<Svg
style={{ backgroundColor: '#141414', width: X, height: Y }}
viewBox={`-${X / 2} -${Y / 2} ${X} ${Y}`}
onLayout={(event) => {
const { layout } = event.nativeEvent
}}
>
{/* Inside Ring */}
<Circle cx="0" cy="0" r={CoreRadius} fill="none" strokeWidth={2} stroke={'white'} />
{rings.map((ring, ringIndex) => {
//Begin at [0, 1] (bottom of the circle)
let totalDegrees = 0
const prevRing = CoreRadius + ringIndex * PartRadius
const newRing = prevRing + (ringIndex + 1 * PartRadius)
//This is the Path for the Text
const pathRadius = prevRing + (newRing - prevRing) / 2
const centerX = 0
const centerY = 0
return (
<Fragment>
<Circle cx="0" cy="0" r={newRing} fill="none" strokeWidth={2} stroke={'white'} />
<Defs>
{/* This path is used for text to be displayed along the circular path. */}
<Path
d={`M ${centerX} ${centerY + pathRadius}
A ${pathRadius} ${pathRadius} 0 0 1 ${centerX} ${centerY - pathRadius}
A ${pathRadius} ${pathRadius} 0 0 1 ${centerX} ${centerY + pathRadius}
`}
fill="none"
stroke="green"
strokeWidth={1}
id={`path-${ringIndex}`}
/>
</Defs>
<Use href={`#path-${ringIndex}`} />
{ring.parts.map((part, partIndex) => {
const partDegree = part.degree || ring.degree
totalDegrees = totalDegrees + partDegree
const innerStartPoint = getPoint(prevRing, totalDegrees - ring.offset)
const outerStartPoint = getPoint(newRing, totalDegrees - ring.offset)
return (
<Fragment>
<Line
x1={innerStartPoint[0]}
y1={innerStartPoint[1]}
x2={outerStartPoint[0]}
y2={outerStartPoint[1]}
stroke={'white'}
strokeWidth={2}
/>
{/* Fix startOffset so that each Direction begins at a different point. */}
<TextPath href={`#path-${ringIndex}`}>
<TSpan
fill={'white'}
textAnchor="middle"
alignmentBaseline="middle"
verticalAlign={'middle'}
>
{part.text}
</TSpan>
</TextPath>
</Fragment>
)
})}
</Fragment>
)
})}
</Svg>
</ReactNativeZoomableView>
</View>
)
}
export default Compass