Skip to content

Commit

Permalink
BarChart 리팩토링 (#36)
Browse files Browse the repository at this point in the history
* ♻️ refactor(component): width, height 속성 필수로 설정 및 각 변에 축을 그리도록 설정

* 💄 style(css): label을 height 중앙에 오도록 수정, textAlign 삭제 및 tickAlign 추가

* 📝 docs(story): bandAxis Story 수정

* 🚚 chore(component): 주석 추가

* 🚚 chore(component): barChart에서 Axis 제거 및 animate 제거

* ♻️ refactor(component): bar 제거 및 방향 추가

* 💄 style(constant): orient를 BarChart와 동일하게 수정 및 불필요한 속성 css로 이동

* ✨ feat(component): label 및 showLabel props 추가

* 💄 style(css): labelOffset 및 style 조정

* 🚚 chore(type): barProps 삭제

* 📝 docs(story): padding Story 추가
  • Loading branch information
bh2980 authored May 27, 2024
1 parent 38dce2a commit c6fca13
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 204 deletions.
34 changes: 10 additions & 24 deletions src/components/charts/BandAxis/BandAxis.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,38 +29,24 @@ const xScale = scaleBand()
.range([0, length]);

export const Default: Story = {
render: () => (
<svg width={256} height={256}>
<BandAxis axisScale={xScale} transform="translate(0, 128)" />
</svg>
),
render: () => <BandAxis axisScale={xScale} width={256} height={48} />,
};

export const Direction: Story = {
export const Orient: Story = {
render: () => (
<svg width={512} height={256}>
<g transform="translate(128, 0)">
<BandAxis axisScale={xScale} orient="DOWN" transform="translate(0, 5)" />
<BandAxis axisScale={xScale} orient="UP" transform="translate(0, 245)" />
<BandAxis axisScale={xScale} orient="RIGHT" transform="translate(280, 0)" />
<BandAxis axisScale={xScale} orient="LEFT" transform="translate(-24, 0)" />
</g>
</svg>
<div className="flex gap-md items-center">
<BandAxis axisScale={xScale} orient="DOWN" width={256} height={48} />
<BandAxis axisScale={xScale} orient="UP" width={256} height={48} />
<BandAxis axisScale={xScale} orient="RIGHT" width={64} height={256} />
<BandAxis axisScale={xScale} orient="LEFT" width={64} height={256} />
</div>
),
};

export const LabelHide: Story = {
render: () => (
<svg width={256} height={256}>
<BandAxis axisScale={xScale} transform="translate(0, 128)" labelHide />
</svg>
),
render: () => <BandAxis width={256} height={6} axisScale={xScale} labelHide />,
};

export const LineHide: Story = {
render: () => (
<svg width={256} height={256}>
<BandAxis axisScale={xScale} transform="translate(0, 128)" lineHide />
</svg>
),
render: () => <BandAxis width={256} height={32} axisScale={xScale} lineHide />,
};
22 changes: 21 additions & 1 deletion src/components/charts/BandAxis/BandAxis.styles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
import { tv } from "@utils/customTV";

export const BandAxisVariants = tv({
base: "stroke-black",
slots: {
root: "stroke-surface-on",
axisLine: "fill-none",
labelText: "stroke-none",
},
variants: {
lineHide: {
true: {
axisLine: "hidden",
},
},
labelHide: {
true: {
labelText: "hidden",
},
},
},
defaultVariants: {
lineHide: false,
labelHide: false,
},
});
55 changes: 37 additions & 18 deletions src/components/charts/BandAxis/BandAxis.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { getAxisOrientConfig } from "@utils/getAxisOrientConfig";
import { isEven } from "@utils/isEven";
import { BandAxisVariants } from "./BandAxis.styles";
import { BandAxisProps } from "./BandAxis.types";

// TODO width, height가 없는 반응형 대응이 필요
// TODO 조립형으로 하면 각 컴포넌트 별로 props 분리 및 label 회전, 포맷팅도 가능할 듯 -> 꼭 필요한가? 싶긴 함
const BandAxis = ({
orient = "DOWN",
width,
height,
orient = "UP",
axisScale,
outerTickLength = 6,
innerTickLength = 6,
Expand All @@ -13,6 +16,8 @@ const BandAxis = ({
className,
...props
}: BandAxisProps) => {
const { root, axisLine, labelText } = BandAxisVariants();

const [startPoint, endPoint] = axisScale.range();
const tickCount = axisScale.domain().length;
const isVertical = orient === "LEFT" || orient === "RIGHT";
Expand All @@ -21,11 +26,23 @@ const BandAxis = ({
(endPoint - startPoint) / 2 -
axisScale.step() * (isEven(tickCount) ? tickCount / 2 - 0.5 : Math.floor(tickCount / 2));

const [textdX, textdY, path] = getAxisOrientConfig({ orient, startPoint, endPoint, outerTickLength });
const pathConfig: { [key: string]: string } = {
UP: `M${startPoint + 0.5},${outerTickLength}V0H${endPoint - 0.5}V${outerTickLength}`,
DOWN: `M${startPoint + 0.5},${height - outerTickLength}V${height}H${endPoint - 0.5}V${height - outerTickLength}`,
RIGHT: `M${outerTickLength},${startPoint + 0.5}H0V${endPoint - 0.5}H${outerTickLength}`,
LEFT: `M${width - outerTickLength},${startPoint + 0.5}H${width}V${endPoint - 0.5}H${width - outerTickLength}`,
};

const tickAlign = {
UP: `translate(0, 0)`,
DOWN: `translate(0, ${height - innerTickLength})`,
RIGHT: `translate(0, 0)`,
LEFT: `translate(${width - innerTickLength}, 0)`,
};

return (
<g className={BandAxisVariants({ className })} textAnchor="middle" {...props}>
{!lineHide && <path fill="none" d={path} />}
<svg width={width} height={height} className={root({ className })} {...props}>
<path d={pathConfig[orient]} className={axisLine({ lineHide })} />
{axisScale.domain().map((label, i) => (
<g
key={`tick-${i}`}
Expand All @@ -35,21 +52,23 @@ const BandAxis = ({
: `translate(${tickStartPoint + axisScale.step() * i}, 0)`
}
>
{!lineHide && (
<line
x2={isVertical ? innerTickLength : undefined}
y2={isVertical ? undefined : innerTickLength}
fill="none"
/>
)}
{!labelHide && (
<text dx={textdX} dy={textdY} stroke="none">
{label}
</text>
)}
<line
x2={isVertical ? innerTickLength : undefined}
y2={isVertical ? undefined : innerTickLength}
transform={tickAlign[orient]}
className={axisLine({ lineHide })}
/>
<text
textAnchor="middle"
dominantBaseline="central"
transform={isVertical ? `translate(${width / 2}, 0)` : `translate(0, ${height / 2})`}
className={labelText({ labelHide })}
>
{label}
</text>
</g>
))}
</g>
</svg>
);
};

Expand Down
4 changes: 3 additions & 1 deletion src/components/charts/BandAxis/BandAxis.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export type AxisOrient = "UP" | "DOWN" | "RIGHT" | "LEFT";

type BandAxisBaseProps = {
axisScale: ScaleBand<string>;
width: number;
height: number;
outerTickLength?: number;
innerTickLength?: number;
orient?: AxisOrient;
labelHide?: boolean;
lineHide?: boolean;
};

export type BandAxisProps = Omit<ComponentPropsWithInnerRef<"g">, "textAnchor"> &
export type BandAxisProps = Omit<ComponentPropsWithInnerRef<"svg">, "width" | "height" | "textAnchor"> &
VariantProps<typeof BandAxisVariants> &
BandAxisBaseProps;
46 changes: 16 additions & 30 deletions src/components/charts/BandAxis/__test__/BandAxis.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,60 +21,46 @@ describe("BandAxis", () => {
};

it("에러 없이 렌더링", () => {
render(<BandAxis {...defaultProps} />);
render(<BandAxis width={256} height={32} {...defaultProps} />);
});

it("data에 맞는 tick 개수", () => {
const { container } = render(<BandAxis {...defaultProps} />);
const ticks = container.querySelectorAll("g > g");
const { container } = render(<BandAxis width={256} height={32} {...defaultProps} />);
const ticks = container.querySelectorAll("svg > g");
expect(ticks.length).toBe(4);
});

it("lineHide일 때 path, line 태그 없음", () => {
const { container } = render(<BandAxis {...defaultProps} lineHide />);
it("lineHide일 때 path, line 태그 hidden", () => {
const { container } = render(<BandAxis width={256} height={32} {...defaultProps} lineHide />);
const path = container.querySelector("path");
const line = container.querySelector("line");

expect(path).toBe(null);
expect(line).toBe(null);
expect(path).toHaveClass("hidden");
expect(line).toHaveClass("hidden");
});

it("labelHide일 때 text 태그 없음", () => {
const { container } = render(<BandAxis {...defaultProps} labelHide />);
const texts = container.querySelectorAll("text");
expect(texts.length).toBe(0);
});

it("orient가 UP | DOWN 일 경우, transform 속성", () => {
const regex = /^translate\([\d.]+,\s*0\)$/;
["UP", "DOWN"].forEach((orient) => {
const { container } = render(<BandAxis {...defaultProps} orient={orient as AxisOrient} />);
const tick = container.querySelector("g > g");
expect(tick).toHaveAttribute("transform", expect.stringMatching(regex));
});
const { container } = render(<BandAxis width={256} height={32} {...defaultProps} labelHide />);
const text = container.querySelector("text");
expect(text).toHaveClass("hidden");
});

it("orient가 UP | DOWN 일 경우, line의 y2 존재 및 x2 속성 없음", () => {
["UP", "DOWN"].forEach((orient) => {
const { container } = render(<BandAxis {...defaultProps} orient={orient as AxisOrient} />);
const { container } = render(
<BandAxis width={256} height={32} {...defaultProps} orient={orient as AxisOrient} />,
);
const line = container.querySelector("line");
expect(line).toHaveAttribute("y2");
expect(line).not.toHaveAttribute("x2");
});
});

it("orient가 LEFT | RIGHT 일 경우, transform 속성", () => {
const regex = /^translate\(0,\s*[\d.]+\)$/;
["LEFT", "RIGHT"].forEach((orient) => {
const { container } = render(<BandAxis {...defaultProps} orient={orient as AxisOrient} />);
const tick = container.querySelector("g > g");
expect(tick).toHaveAttribute("transform", expect.stringMatching(regex));
});
});

it("orient가 LEFT | RIGHT 일 경우, line의 x2 존재 및 y2 속성 없음", () => {
["LEFT", "RIGHT"].forEach((orient) => {
const { container } = render(<BandAxis {...defaultProps} orient={orient as AxisOrient} />);
const { container } = render(
<BandAxis width={48} height={256} {...defaultProps} orient={orient as AxisOrient} />,
);
const line = container.querySelector("line");
expect(line).toHaveAttribute("x2");
expect(line).not.toHaveAttribute("y2");
Expand Down
27 changes: 26 additions & 1 deletion src/components/charts/BarChart/BarChart.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,30 @@ const data = [
];

export const Default: Story = {
render: () => <BarChart width={400} height={300} data={data} />,
render: () => <BarChart width={400} height={256} data={data} />,
};

export const Orient: Story = {
render: () => (
<div className="flex gap-md">
<BarChart width={128} height={128} data={data} orient="UP" />
<BarChart width={128} height={128} data={data} orient="DOWN" />
<BarChart width={128} height={128} data={data} orient="LEFT" />
<BarChart width={128} height={128} data={data} orient="RIGHT" />
</div>
),
};

export const ShowLabel: Story = {
render: () => <BarChart width={400} height={256} data={data} showLabel />,
};

export const Padding: Story = {
render: () => (
<div className="flex gap-md">
<BarChart width={256} height={256} data={data} padding={0.1} />
<BarChart width={256} height={256} data={data} padding={0.45} />
<BarChart width={256} height={256} data={data} padding={0.8} />
</div>
),
};
17 changes: 15 additions & 2 deletions src/components/charts/BarChart/BarChart.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@ import { tv } from "@utils/customTV";

export const barChartVariants = tv({
slots: {
bar: "stroke-secondary fill-secondary font-bold text-sm",
xAxis: "text-sm fill-surface-on-variant",
bar: "",
labelText: "",
},
variants: {
value: {
true: { bar: "fill-secondary" },
false: { bar: "stroke-secondary fill-none" },
},
showLabel: {
false: { labelText: "hidden" },
},
},
defaultVariants: {
value: true,
showLabel: false,
},
});
Loading

0 comments on commit c6fca13

Please sign in to comment.