FormidableLabs / victory-native-xl

A charting library for React Native with a focus on performance and customization.
https://commerce.nearform.com/open-source/victory-native
644 stars 47 forks source link

Add text above bar #318

Closed LESANF closed 3 days ago

LESANF commented 1 month ago

How to Using

스크린샷 2024-07-18 오후 4 34 23

{({ points, chartBounds }) => {
    return points.drnkwtrVal.map((p, i) => {
        return (
            <Bar
                barCount={points.drnkwtrVal.length}
                key={i}
                points={[p]}
                barWidth={30}
                chartBounds={chartBounds}
                animate={{ type: 'spring' }}
                innerPadding={innerPadding}
                textOffsetX={(p.yValue + '').length > 3 ? -1 : -2.5}
                textOffsetY={-5}
                roundedCorners={{
                    topLeft: roundedCorner,
                    topRight: roundedCorner,
                    bottomLeft: roundedCorner,
                    bottomRight: roundedCorner,
                }}
                color={p.yValue >= goalWaterCup ? '#07B419' : '#E5E8EB'}
            >
                <SkiaText
                    color={p.yValue >= goalWaterCup ? '#07B419' : '#8B95A1'}
                    font={fontSemiBold}
                    text={p.yValue === null ? '0' : p.yValue.toLocaleString('ko-KR') + ''}
                />
            </Bar>
        );
    });
}}
// bar.tsx

import * as React from "react";
import {
  Path,
  Rect,
  Text as SkiaText,
  type PathProps,
} from "@shopify/react-native-skia";
import type { PropsWithChildren, ReactNode } from "react";
import type { ChartBounds, PointsArray } from "../../types";
import { AnimatedPath } from "./AnimatedPath";
import { type PathAnimationConfig } from "../../hooks/useAnimatedPath";
import { useBarPath } from "../hooks/useBarPath";
import type { RoundedCorners } from "../../utils/createRoundedRectPath";

type CartesianBarProps = {
  points: PointsArray;
  chartBounds: ChartBounds;
  innerPadding?: number;
  animate?: PathAnimationConfig;
  roundedCorners?: RoundedCorners;
  barWidth?: number;
  barCount?: number;
  textOffsetX?: number;
  textOffsetY?: number;
  children?: ReactNode;
} & Partial<Pick<PathProps, "color" | "blendMode" | "opacity" | "antiAlias">>;

export const Bar = ({
  points,
  chartBounds,
  animate,
  innerPadding = 0.25,
  roundedCorners,
  barWidth,
  barCount,
  textOffsetX = 0,
  textOffsetY = 0,
  children,
  ...ops
}: PropsWithChildren<CartesianBarProps>) => {
  const { path, barPositions } = useBarPath(
    points,
    chartBounds,
    innerPadding,
    roundedCorners,
    barWidth,
    barCount,
  );

  const PathComponent = animate ? AnimatedPath : Path;

  return (
    <>
      <PathComponent
        path={path}
        color={ops.color || "black"}
        {...(Boolean(animate) && { animate })}
        {...ops}
      />
      {barPositions.map((barPosition, index) =>
        React.Children.map(children, (child) => {
          if (React.isValidElement(child)) {
            const textX =
              barPosition.x - barWidth! / 2 + textOffsetX + barWidth! / 2;
            const textY = barPosition.y - 20 + textOffsetY + 10;

            const textWidth = child.props.text.length * 6;
            const textHeight = 12;

            return (
              <>
                {React.cloneElement(child, {
                  x: textX - textWidth / 2,
                  y: textY + textHeight / 2,
                } as any)}
              </>
            );
          }
          return child;
        }),
      )}
    </>
  );
};
// useBarPath.ts

import * as React from "react";
import { Skia } from "@shopify/react-native-skia";
import {
  createRoundedRectPath,
  type RoundedCorners,
} from "../../utils/createRoundedRectPath";
import type { ChartBounds, PointsArray } from "../../types";
import { useCartesianChartContext } from "../contexts/CartesianChartContext";

type BarPosition = {
  x: number;
  y: number;
};

export const useBarPath = (
  points: PointsArray,
  chartBounds: ChartBounds,
  innerPadding = 0.2,
  roundedCorners?: RoundedCorners,
  customBarWidth?: number,
  barCount?: number,
) => {
  const { yScale } = useCartesianChartContext();
  const barWidth = React.useMemo(() => {
    if (customBarWidth) return customBarWidth;
    const domainWidth = chartBounds.right - chartBounds.left;

    const numerator = (1 - innerPadding) * domainWidth;

    const denominator = barCount
      ? barCount
      : points.length - 1 <= 0
        ? // don't divide by 0 if there's only one data point
          points.length
        : points.length - 1;

    const barWidth = numerator / denominator;

    return barWidth;
  }, [
    customBarWidth,
    chartBounds.left,
    chartBounds.right,
    innerPadding,
    points.length,
    barCount,
  ]);

  const { path, barPositions } = React.useMemo(() => {
    const path = Skia.Path.Make();
    const barPositions: BarPosition[] = [];

    points.forEach(({ x, y, yValue }) => {
      if (typeof y !== "number") return;

      const barHeight = yScale(0) - y;
      if (roundedCorners) {
        const nonUniformRoundedRect = createRoundedRectPath(
          x,
          y,
          barWidth,
          barHeight,
          roundedCorners,
          Number(yValue),
        );
        path.addRRect(nonUniformRoundedRect);
      } else {
        path.addRect(Skia.XYWHRect(x - barWidth / 2, y, barWidth, barHeight));
      }

      barPositions.push({ x: x - barWidth / 2 + barWidth / 2, y: y });
    });

    return { path, barPositions };
  }, [barWidth, points, roundedCorners, yScale]);

  return { path, barPositions, barWidth };
};
zibs commented 1 month ago

Thanks @LESANF! Is this meant to show users how to accomplish this? Would you be up for making a PR for the example app where you add a route/chart with this so that we can point to it in the future?

LESANF commented 1 month ago

@zibs

Yeap ! This code is an example of a code I modified and made like the above image PR will add when I have time

mMarcos208 commented 1 month ago

@LESANF where is the complete code?

LESANF commented 1 month ago

@LESANF where is the complete code?

You have to modify the codes I posted yourself

However, this is due to a collision in a regular bar and needs more correction.

If you only want to write a bar containing text, the code is valid