Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: draw circular text around an image #972

Closed
rahamin1 opened this issue Mar 17, 2019 · 26 comments
Closed

Question: draw circular text around an image #972

rahamin1 opened this issue Mar 17, 2019 · 26 comments
Labels

Comments

@rahamin1
Copy link

Can anybody post an example of how to draw text around a circular image?

Something like the following:

image

Thanks in advance :)

@msand
Copy link
Collaborator

msand commented Mar 17, 2019

How about something like this: https://snack.expo.io/@msand/text-on-a-circle-path

import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { Constants, Svg } from 'expo';

const { Circle, Text, TextPath, TSpan, G, Path } = Svg;

const SvgComponent = props => (
  <Svg height="100%" width="100%" viewBox="0 0 300 300" {...props}>
    <G id="circle">
      <Circle
        r={100}
        x={150}
        y={150}
        fill="none"
        stroke="#00008b"
        strokeWidth={14}
      />
    </G>
    <G id="webCompatible">
      <Path
        d="M 64,0 A 64,64 0 0 1 -64,0 A 64,64 0 0 1 64,0"
        transform="translate(150,150)"
        stroke="darkblue"
        fill="none"
      />
    </G>
    <Text fill="#000" fontSize="14">
      <TextPath href="#circle">
        <TSpan dy={-14}>
          Text along a curved path... Text along a curved path... Text along a
          curved path...
        </TSpan>
      </TextPath>
    </Text>
    <Text fill="#000" fontSize="10">
      <TextPath href="#webCompatible">
        <TSpan>
          Text along a curved path... Text along a curved path... Text along a
          curved path...
        </TSpan>
      </TextPath>
    </Text>
  </Svg>
);

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <SvgComponent />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
});

@rahamin1
Copy link
Author

@msnad Thanks a lot!
So the following is the result of the code that is attached below. How would you place the text above the image so that it is centered (as in my example in the first post, above)?
image

const SvgComponent = props => (
  <Svg height="100%" width="100%" viewBox="0 0 300 300" {...props}>
    <G id="circle">
      <Circle
        r={100}
        x={150}
        y={150}
        fill="none"
        stroke="none"
        strokeWidth={0}
      />
    </G>
    <Image
      style={{ width: 220, height: 220, borderRadius: 110,
        marginLeft: 70, marginTop: 180 }}
      source={require('./doggy.jpg')}
    />
    <Text fill="#000" fontSize="14">
      <Text fill="#000" fontSize="14">
        <TextPath href="#circle">
          <TSpan dy={-14}>
            Text along a curved path...
          </TSpan>
        </TextPath>
      </Text>
    </Text>
  </Svg>
);

@msand
Copy link
Collaborator

msand commented Mar 18, 2019

Try using a rotate transform:

import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { Constants, Svg } from 'expo';

const { Circle, Text, TextPath, TSpan, G, Path } = Svg;

const SvgComponent = props => (
  <Svg height="100%" width="100%" viewBox="0 0 300 300" {...props}>
    <G id="circle">
      <Circle
        r={100}
        x={150}
        y={150}
        fill="none"
        stroke="#00008b"
        strokeWidth={14}
        transform="rotate(-90)"
      />
    </G>
    <G id="webCompatible">
      <Path
        d="M 64,0 A 64,64 0 0 1 -64,0 A 64,64 0 0 1 64,0"
        transform="translate(150,150)rotate(-90)"
        stroke="darkblue"
        fill="none"
      />
    </G>
    <Text fill="#000" fontSize="14">
      <TextPath href="#circle">
        <TSpan dx="0" dy={-14}>
          Text along a curved path...
        </TSpan>
      </TextPath>
    </Text>
    <Text fill="#000" fontSize="10">
      <TextPath href="#webCompatible">
        <TSpan>
          Text along a curved path...
        </TSpan>
      </TextPath>
    </Text>
  </Svg>
);

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <SvgComponent />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
});

@rahamin1
Copy link
Author

Thanks @msand
Is there any good tutorial reference for all this magic?

Also, any idea why the following happens?
I tried to include the same code (see below) in a non-expo react-native application, and this is what I get:
image

This is the code:

import React, { Component } from 'react';
import { Image, StyleSheet, View } from 'react-native';
import {
  Svg,
  G as SvgG,
  Circle as SvgCircle,
  Text as SvgText,
  TextPath as SvgTextPath,
  TSpan as SvgTSpan
} from 'react-native-svg';

const SvgComponent = props => (
  <View style={{ flex: 1 }}>
    <Svg height="500" width="500" viewBox="0 0 300 300" {...props}>
      <SvgG id="circle">
        <SvgCircle
          r={100}
          x={150}
          y={150}
          fill="none"
          stroke="none"
          strokeWidth={0}
          transform="rotate(-135)"
        />
      </SvgG>
      <Image
        style={{ width: 220, height: 220, borderRadius: 110,
          marginLeft: 60, marginTop: 175 }}
        source={require('./doggy.jpg')}
      />
      <SvgText fill="#000" fontSize="14">
        <SvgText fill="#000" fontSize="14">
          <SvgTextPath href="#circle">
            <SvgTSpan dy={0}>
              Text along a curved path...
            </SvgTSpan>
          </SvgTextPath>
        </SvgText>
      </SvgText>
    </Svg>
  </View>
);

export class App extends Component {
  render() {
    return (
      <SvgComponent />
    )
  }
}

@msand msand added the Question label Mar 18, 2019
@msand
Copy link
Collaborator

msand commented Mar 18, 2019

You should use the image component from react-native-svg rather than react-native:
N.B. This code requires v9.2.4 I published some minutes ago, giving support to use images with urls as plain strings.

import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import {  Circle, Text, TextPath, TSpan, G, Path, Svg, Image } from 'react-native-svg';

const SvgComponent = props => (
  <Svg height="100%" width="100%" viewBox="0 0 300 300" {...props}>
    <G id="circle">
      <Circle
        r={100}
        x={150}
        y={150}
        fill="none"
        stroke="#00008b"
        strokeWidth={14}
        transform="rotate(-90)"
      />
    </G>
    <G id="webCompatible">
      <Path
        d="M 64,0 A 64,64 0 0 1 -64,0 A 64,64 0 0 1 64,0"
        transform="translate(150,150)rotate(-90)"
        stroke="darkblue"
        fill="none"
      />
    </G>
    <Image href="https://facebook.github.io/react/logo-og.png" x={100} y={100} width={100} height={100} />
    <Text fill="#000" fontSize="14">
      <TextPath href="#circle">
        <TSpan dx="0" dy={-14}>
          Text along a curved path...
        </TSpan>
      </TextPath>
    </Text>
    <Text fill="#000" fontSize="10">
      <TextPath href="#webCompatible">
        <TSpan>
          Text along a curved path...
        </TSpan>
      </TextPath>
    </Text>
  </Svg>
);

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <SvgComponent />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: 50,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
});

@msand
Copy link
Collaborator

msand commented Mar 18, 2019

Simulator Screen Shot - iPhone XR - 2019-03-18 at 20 20 41

@msand
Copy link
Collaborator

msand commented Mar 18, 2019

I would recommend reading the svg spec, it's relatively well written. And then just google for specific tutorials/working examples if you get stuck.

@rahamin1
Copy link
Author

rahamin1 commented Mar 18, 2019

Thanks @msand , it works. Is it possible to display a circular image as in the following React-Native image?

<Image
        style={{ width: 220, height: 220, borderRadius: 110,
          marginLeft: 60, marginTop: 175 }}
        source={require('./doggy.jpg')}
      />

One more thing that I don't understand: why with expo I can embed a react-native Image within an SVG component, and without expo I can't? Expo is using react-native-svg, right?

@rahamin1
Copy link
Author

Since react-native-svg probably doesn't support circular images at this point, the following can be a possible solution:

import React, { Component } from 'react';
import { View, Image } from 'react-native';
import {  Circle, Text as SvgText, TextPath, TSpan, G, Svg }
  from 'react-native-svg';

export class ListScreen extends Component {
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center' }}>

        <Svg position="absolute" height="200" width="200"
          viewBox="0 0 300 300">
          <G id="circle">
            <Circle
              r={75}
              x={150}
              y={176}
              fill="none"
              stroke="#fff"
              strokeWidth={14}
              transform="rotate(-145)"
            />
          </G>
          <SvgText fill="#000" fontSize="14">
            <TextPath href="#circle">
              <TSpan dx="0" dy={-20}>
                Text along a curved path2
              </TSpan>
            </TextPath>
          </SvgText>
        </Svg>
        <View>
          <Image
            style={{ height: 120, width: 120, borderRadius: 60,
              marginTop: 70 }}
            source={require('./dog.jpg')}
          />
        </View>
      </View>
    )
  }
}

@msand
Copy link
Collaborator

msand commented Mar 23, 2019

How about the example from the readme? https://github.com/react-native-community/react-native-svg#image
Reduced form: https://snack.expo.io/@msand/sponaneous-cereal

import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { Constants, Svg } from 'expo';

const { Defs, ClipPath, Circle, Image } = Svg;

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Svg height="100%" width="100%" viewBox="0 0 100 100">
          <Defs>
            <ClipPath id="clip">
              <Circle cx="50" cy="50" r="50" />
            </ClipPath>
          </Defs>

          <Image
            x="0"
            y="0"
            width="100"
            height="100"
            opacity="0.5"
            preserveAspectRatio="xMidYMid slice"
            href={require('./assets/snack-icon.png')}
            clipPath="url(#clip)"
          />
        </Svg>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
});

With regards to using react-native View/Text/Image components, we don't have any official support for that, it would require implementing foreignObject, which shouldn't be too much work if you have a significant need for it, I can probably describe the changes you would need to make if you're interested in working on it.

@rahamin1
Copy link
Author

@msand
Thanks a lot Mikael, for the last post and for all the great help!

@msand
Copy link
Collaborator

msand commented Mar 24, 2019

You're welcome! Happy I could help

@rahamin1 rahamin1 reopened this Mar 24, 2019
@msand
Copy link
Collaborator

msand commented Mar 25, 2019

If you want to center the text along the middle of a path, you can use this: https://snack.expo.io/@msand/text-on-a-circle-path
I added startOffset="50%" to the TextPath, textAnchor="middle" to the TSpan, and rotated the Path/Circle 90degrees in the opposite direction instead.

import * as React from 'react';
import { View, StyleSheet } from 'react-native';
import { Constants, Svg } from 'expo';

const { Circle, Text, TextPath, TSpan, G, Path, Defs, ClipPath, Image } = Svg;

const SvgComponent = props => (
  <Svg height="100%" width="100%" viewBox="0 0 300 300" {...props}>
    <G id="circle">
      <Circle
        r={100}
        x={150}
        y={150}
        fill="none"
        stroke="#00008b"
        strokeWidth={14}
        transform="rotate(+90)"
      />
    </G>
    <G id="webCompatible">
      <Path
        d="M 64,0 A 64,64 0 0 1 -64,0 A 64,64 0 0 1 64,0"
        transform="translate(150,150)rotate(+90)"
        stroke="darkblue"
        fill="none"
      />
    </G>
    <Text fill="#000" fontSize="14">
      <TextPath href="#circle" startOffset="50%">
        <TSpan textAnchor="middle" dy={-14}>
          Text along a curved path...
        </TSpan>
      </TextPath>
    </Text>
    <Text fill="#000" fontSize="10">
      <TextPath href="#webCompatible" startOffset="50%">
        <TSpan textAnchor="middle">Text along a curved path...</TSpan>
      </TextPath>
    </Text>
    <Defs>
      <ClipPath id="clip">
        <Circle cx="50" cy="50" r="64" />
      </ClipPath>
    </Defs>
    <G transform="translate(100,100)">
      <Image
        x="3.5"
        y="7"
        width="100"
        height="100"
        opacity="0.5"
        preserveAspectRatio="xMidYMid slice"
        href={require('./assets/snack-icon.png')}
        clipPath="url(#clip)"
      />
    </G>
  </Svg>
);

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <SvgComponent />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    paddingTop: Constants.statusBarHeight,
    backgroundColor: '#ecf0f1',
    padding: 8,
  },
});

@rahamin1
Copy link
Author

Wow, Mikael! Thanks much, it works! (I deleted my question but you saw it... I decided that I shouldn't bother you with that...)

@rahamin1
Copy link
Author

Hey @msand ;

In all my tests I used version 5.5.1 of react-native-svg (I am using version 0.55.3 of react-native) and the code seemed to work fine.

When I tried to rebuild the repo, however, the circular text is not displayed at all.
Does it require a newer version? I tried version 6.0.0 and it didn't help. I even tried the latest version (9.3.6) which is not supposed to work with the version of react-native that I am using (according to https://github.com/react-native-community/react-native-svg#notice) but it doesn't work as well.

Any idea?

@msand
Copy link
Collaborator

msand commented Mar 27, 2019

Hmm, are you using the one with circle or the web compatible? I think the web-compatible version might work in v5, but text on a path with the other geometric primitives rather than just paths came later, not exactly sure what version right now. Latest version should work fine. Can you make a reproducible git repo using a clean project?

@rahamin1
Copy link
Author

rahamin1 commented Mar 27, 2019

It will take me some time creating a repo, since I have some urgent tasks for today and tomorrow. Meanwhile, here is the code I am using:

import {
  Circle as SvgCircle, Text as SvgText, TextPath as SvgTextPath,
  TSpan as SvgTSpan, G as SvgG, Svg
} from 'react-native-svg';

const SvgComponent = () => (
  <Svg position="absolute" height="200" width="200" viewBox="0 0 300 300">
    <SvgG id="circle">
      <SvgCircle r={75} x={150} y={140}
        fill="none" stroke="#fff" strokeWidth={14}
        transform="rotate(+90)"
      />
    </SvgG>
    <SvgText fill="#000" fontSize="20" fontFamily="Quicksand-Regular">
      <SvgTextPath href="#circle" startOffset="50%">
        <SvgTSpan dx="0" dy={-20} textAnchor="middle">
          {titleString}
        </SvgTSpan>
      </SvgTextPath>
    </SvgText>
  </Svg>
);

@msand
Copy link
Collaborator

msand commented Mar 27, 2019

This seems to work just fine for me in the latest version / development branch at least:

import React, { Component } from 'react';
import { View, StyleSheet } from 'react-native'
import Svg, { G, Text, TextPath, TSpan, Circle } from 'react-native-svg';

const SvgComponent = () => (
  <Svg position="absolute" height="200" width="200" viewBox="0 0 300 300">
    <G id="circle">
      <Circle r={75} x={150} y={140}
                 fill="none" stroke="#fff" strokeWidth={14}
                 transform="rotate(+90)"
      />
    </G>
    <Text fill="#000" fontSize="20" fontFamily="Quicksand-Regular">
      <TextPath href="#circle" startOffset="50%">
        <TSpan dx="0" dy={-20} textAnchor="middle">
          testing
        </TSpan>
      </TextPath>
    </Text>
  </Svg>
);

export default class App extends Component {
  render() {
    return (
      <View style={styles.container}>
        <SvgComponent />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: 'gray',
    flex: 1
  }
});

@rahamin1
Copy link
Author

Seems identical to my version... Did you change anything?

@msand
Copy link
Collaborator

msand commented Mar 27, 2019

Only the imports / component names.

@rahamin1
Copy link
Author

I believe that this should not make any difference...
Which react-native and react-native-svg are you using?

@msand
Copy link
Collaborator

msand commented Mar 27, 2019

react: 16.8.3
react-native: 0.59.1
react-native-svg: develop branch

@rahamin1
Copy link
Author

That's what you wrote in package.json? react-native-svg: develop branch?
And shouldn't I follow the guidance in https://github.com/react-native-community/react-native-svg#notice?

@msand
Copy link
Collaborator

msand commented Mar 27, 2019

I would recommend upgrading to the latest version of both react-native and react-native-svg. I'm just using the latest code I wrote when I pushed to the develop branch, you can check for specific commits from here: https://github.com/react-native-community/react-native-svg/tree/develop
And install by using e.g.

npm i react-native-svg@react-native-community/react-native-svg#9321aa5

or

yarn add react-native-svg@react-native-community/react-native-svg#9321aa5

@rahamin1
Copy link
Author

rahamin1 commented Mar 28, 2019

It is a large project. Upgrading may be a long process.
The strange thing is that at some point everything worked fine with version 5.5.1 of react native svg. However, when I pushed the changes to gitlab, cloned and rebuilt it, the circular text stopped to work.
Then I changed the version to the latest (9.3.6),ran npm install, and it worked. Pushed, cloned and rebuilt and again, stopped working.

@danstepanov
Copy link

@msand your snack was out of date so I updated it with respect to Expo SDK 47 https://snack.expo.dev/@danstepanov/text-on-a-circle-path?platform=ios

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants