reactjsreact-nativetooltipreact-native-svgreact-native-chart-kit

Positioning icon in SVG in React Native


Current Status

Background: I am trying to add a tooltip to a react-native-svg chart following this tutorial. The link to the tutorial: Link

Current Code Implementation:

import React, {useState} from 'react';
import {View, Text, Dimensions} from 'react-native';
import {LineChart} from 'react-native-chart-kit';
import {Rect, Text as TextSVG, Svg} from 'react-native-svg';

const Charts = () => {
  let [tooltipPos, setTooltipPos] = useState({
    x: 0,
    y: 0,
    visible: false,
    value: 0,
  });

  return (
    <View>
      <LineChart
        data={{
          labels: ['January', 'February', 'March', 'April', 'May', 'June'],
          datasets: [
            {
              data: [100, 110, 90, 130, 80, 103],
            },
          ],
        }}
        width={Dimensions.get('window').width}
        height={250}
        yAxisLabel="$"
        yAxisSuffix="k"
        yAxisInterval={1}
        chartConfig={{
          backgroundColor: 'white',
          backgroundGradientFrom: '#fbfbfb',
          backgroundGradientTo: '#fbfbfb',
          decimalPlaces: 2,
          color: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
          labelColor: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
          style: {
            borderRadius: 0,
          },
          propsForDots: {
            r: '6',
            strokeWidth: '0',
            stroke: '#fbfbfb',
          },
        }}
        bezier
        style={{
          marginVertical: 8,
          borderRadius: 6,
        }}
        decorator={() => {
          return tooltipPos.visible ? (
            <View>
              <Svg>
                <Rect
                  x={tooltipPos.x - 15}
                  y={tooltipPos.y + 10}
                  width="40"
                  height="30"
                  fill="black"
                />
                <MaterialCommunityIcons
                  name="run"
                  size={32}
                  color="rgb(67, 67, 67)"
                />
                <TextSVG
                  x={tooltipPos.x + 5}
                  y={tooltipPos.y + 30}
                  fill="white"
                  fontSize="16"
                  fontWeight="bold"
                  textAnchor="middle">
                  {tooltipPos.value}
                </TextSVG>
              </Svg>
            </View>
          ) : null;
        }}
        onDataPointClick={(data) => {
          let isSamePoint = tooltipPos.x === data.x && tooltipPos.y === data.y;

          isSamePoint
            ? setTooltipPos((previousState) => {
                return {
                  ...previousState,
                  value: data.value,
                  visible: !previousState.visible,
                };
              })
            : setTooltipPos({
                x: data.x,
                value: data.value,
                y: data.y,
                visible: true,
              });
        }}
      />
    </View>
  );
};

Question: I want to add the icon(Running icon) as seen on the image above, to be next to the tool-tip text. The Icon, then the text inside the rectangle filled in Black. When I try to position it,, it shows up on the extreme top left for some reason. How do I position it?


Solution

  • You can use the ForeignObject component from react-native-svg and change your decorator to something like this:

    decorator={() => {
      return tooltipPos.visible ? (
        <ForeignObject x={tooltipPos.x} y={tooltipPos.y}>
          <View
            style={{
              width: 70,
              flexDirection: 'row',
              backgroundColor: 'black',
            }}>
            <MaterialCommunityIcons
              name="run"
              size={32}
              color="rgb(67, 67, 67)"
            />
            <Text
              style={{
                color: 'white',
                fontSize: 16,
                fontWeight: 'bold',
                textAnchor: 'middle',
              }}>
              {tooltipPos.value}
            </Text>
          </View>
        </ForeignObject>
      ) : null;
    }}
    

    The problem with what you had before is that the react-native-svg Text and Rect components use x and y coordinates and your icon doesn't, so the positioning will be off.

    The benefit of the approach shown above is that you only have to specify the x and y props of the ForeignObject. Everything inside the ForeignObject can be regular views, positioned as you normally would (https://github.com/react-native-community/react-native-svg#foreignobject).

    I've chosen the tooltipPos.x and tooltipPos.y for the x and y prop values of the ForeignObject respectively, but you could add an offset if necessary.


    Be sure to import ForeignObject from react-native-svg.