
๋ด๊ฐ โ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌโ๋ฅผ ๋ง๋ค๋ฉฐ ๋ฐฐ์ด ๊ฒ๋ค
๋ด๊ฐ โ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌโ๋ฅผ ๋ง๋ค๋ฉฐ ๋ฐฐ์ด ๊ฒ๋ค ๊ด๋ จ
๋ด๊ฐ ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง๋ ์ด์
โ์ฃ์กํ์ง๋ง, ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ง์ํ์ง ์๋ ๊ธฐ๋ฅ์ด๋ผโฆโ
ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ผ๋ฉด ๋๊ตฌ๋ ํ ๋ฒ์ฏค ์ด๋ฐ ๋ง์ ํด๋ณธ ์ ์ด ์์ ๊ฒ๋๋ค. ํนํ ๋ฐ์ดํฐ ์๊ฐํ๊ฐ ์ค์ํ ํ๋ก์ ํธ๋ฅผ ์งํํ ๋๋ฉด ๋์ฑ ๊ทธ๋ ์ฃ . ์ ์ญ์ ์ฌ๋ฆฌ ์ง๋จ๊ฒ์ฌ ๋ณด๊ณ ์์ ์ฐจํธ ๊ฐ๋ฐ์ ๋ด๋นํ์ ๋ ์ด๋ฐ ๋ง์ ์์์ด ๋ฐ๋ณตํด์ผ ํ์ต๋๋ค.
โ์ด ์์ญ์ ์ ์๊ฐ ์๊ณ์น๋ฅผ ๋์ผ๋ฉด ์ํ๊ตฐ์ผ๋ก ๋ถ๋ฅ๋์ด ๋ถ์์์ผ๋ก ๊ฐ์กฐ๋์ด์ผ ํด์.โ
โ๊ฒ์ฌ ๊ฒฐ๊ณผ๋ฅผ ๋ค๊ฐํ ๋ฐฉ์ฌํ ์ฐจํธ๋ก ํํํ๋, ๊ฐ ํญ๋ชฉ๋ณ ๊ธฐ์ค์ ๋ ํจ๊ป ๋ณด์ฌ์ฃผ์ธ์.โ
โ์ง๋จ ์์ญ๋ณ๋ก ๋ฐฑ๋ถ์ ๋ถํฌ๋ฅผ ๋ณด์ฌ์ฃผ๋ฉด์, ํ์ฌ ๋ด๋ด์์ ์์น๋ ํ์ํด ์ฃผ์ธ์.โ
์ด๋ฌํ ์๊ตฌ์ฌํญ๋ค์ ๋จ์ํ ๋์์ธ ์ ํธ๋๊ฐ ์๋, ์ ๋ฌธ์ ์ธ ์ง๋จ๊ณผ ํด์์ ์ํด ๊ผญ ํ์ํ ๊ฒ๋ค์ด์์ต๋๋ค. ์ฒ์์๋ ๊ธฐ์กด ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ฝ๋๋ฅผ ์ง์ ์์ ํ๊ณ , ์ค๋ฒ๋ผ์ด๋ํ๋ ๋ฐฉ์์ ์๋ํ์ต๋๋ค. ๋ฐค์ ์์๊ฐ๋ฉฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ด๋ถ ๊ตฌ์กฐ๋ฅผ ๋ถ์ํ๊ณ , ํ์ํ ๊ธฐ๋ฅ์ ๊ฐ์ ๋ก ์ฃผ์ ํ์ฃ . ํ์ง๋ง ์ด๋ ์์๋ฐฉํธ์ ๋ถ๊ณผํ์ต๋๋ค.
์ค์๊ฐ ๋ชจ๋ํฐ๋ง ๋์๋ณด๋๋ ๊ธ์ต ์ฐจํธ์ฒ๋ผ, ๋ฐ์ดํฐ ์๊ฐํ๋ ๋๋ก๋ โํต์ฌ ๊ธฐ๋ฅโ ๊ทธ ์์ฒด์ ๋๋ค. ์ฌ๋ฆฌ ์ง๋จ๊ฒ์ฌ ๊ฒฐ๊ณผ๋ฅผ ์๊ฐํํ ๋๋ ๋ง์ฐฌ๊ฐ์ง์์ฃ . ์ ๋ฌธ๊ฐ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ง๊ด์ ์ผ๋ก ์ดํดํ๊ณ ์ ํํ ์ง๋จ์ ๋ด๋ฆฌ๊ธฐ ์ํด์๋, ์ฐจํธ๊ฐ ๋จ์ํ ์์ ๊ทธ๋ํ๊ฐ ์๋ ์ ํํ ์ง๋จ ๋๊ตฌ๋ก ๊ธฐ๋ฅํด์ผ ํ์ต๋๋ค.
์ด๋ค ๊ฐ๋ฐ์๋ค์ โ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ง์ํ์ง ์์ผ๋, ๊ธฐ๋ฅ์ ์กฐ์ ํ์โ๋ผ๊ณ ํํํ๊ธฐ๋ ํฉ๋๋ค. ํ์ง๋ง ๊ณผ์ฐ ๊ทธ๋๋ ๋ ๊น์? ์๋ฃ ํ์ฅ์ ๋ชจ๋ํฐ๋ง ์ฐจํธ๋ ์ฌ๋ฆฌ ๊ฒ์ฌ ๊ฒฐ๊ณผ์ฒ๋ผ, ์ ๋ฌธ์ ์ธ ์์ฌ๊ฒฐ์ ์ ๋๋ ๋๊ตฌ์์ ๊ฐ๋ฐ์ ํธ์์ฑ์ ์ํด ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ์ํ๋ ๊ฒ ๊ณผ์ฐ ์ฌ๋ฐ๋ฅธ ์ ํ์ผ๊น์? ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ ๋ฐ์ดํธ๋ ๋๋ง๋ค ์์ ํ ์ฝ๋๋ค์ด ๊นจ์ก๊ณ , ์ ์ ๋ ๋ง์ ํซํฝ์ค๊ฐ ํ์ํด์ก์ต๋๋ค. ๊ฒฐ๊ตญ ๊นจ๋ฌ์์ต๋๋ค. ๋ ์ด์ ๊ธฐ์กด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํ๊ณ์ ๋ง์ถฐ ํํํ ์๋ ์๋ค๊ณ ์.
๊ทธ๋์ ๊ฒฐ์ฌํ์ต๋๋ค. ์ฒ์๋ถํฐ ๋ค์ ๋ง๋ค๊ธฐ๋ก์. ์ ๋ฌธ๊ฐ๋ค์ ์๊ตฌ์ฌํญ์ ๋ชจ๋ ์์ฉํ ์ ์๋, ์์ ํ ์๋ก์ด ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง๋ค๊ธฐ๋ก ํ์ต๋๋ค. ์ด๋ค ์ํฉ์์๋ ํ์ํ ๊ธฐ๋ฅ์ ์์ ๋กญ๊ฒ ๊ตฌํํ ์ ์๋ ๋๊ตฌ๋ฅผ ๋ง๋ค๊ณ ์ถ์์ต๋๋ค. ์ด๊ฒ์ด ์ ๊ฐ โheadless-chartโ๋ฅผ ์์ํ๊ฒ ๋ ์ด์ ์ ๋๋ค.

๊ธฐ์กด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํ๊ณ, ๊ทธ๋ฆฌ๊ณ ๋ด๊ฐ ํํ ๊ธธ
์ฌ์ค ๊ธฐ์กด ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ๋์ ๊ฒ์ ์๋๋๋ค. Chart.js, D3.js, Recharts ๋ฑ ์๋ง์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๊ฐ์์ ์ฅ์ ์ ๊ฐ์ง๊ณ ์์ฃ . ํ์ง๋ง ์ค์ ํ๋ก์ ํธ์์ ๋ง์ฃผ์น๋ ๋ํ ์ผํ ์๊ตฌ์ฌํญ๋ค์ ๊ตฌํํ๋ ค ํ ๋๋ฉด, ์ด ํ๋ฅญํ ๋๊ตฌ๋ค๋ ์ข ์ข ํ๊ณ๋ฅผ ๋๋ฌ๋ ๋๋ค.
๊ธฐ์กด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ธ ๊ฐ์ง ๊ทผ๋ณธ์ ์ธ ๋ฌธ์
๊ฐ์ฅ ํฐ ๋ฌธ์ ๋ ๋์์ธ ์์คํ ๊ณผ์ ์ถฉ๋์ด์์ต๋๋ค. ํนํ ๊ธฐ์ ์ฉ ๋์๋ณด๋๋ฅผ ๊ฐ๋ฐํ ๋๋ ํ์ฌ์ ๋ธ๋๋ ์ปฌ๋ฌ์ UX ๊ฐ์ด๋๋ผ์ธ์ ์ ํํ ๋ฐ๋ผ์ผ ํ์ต๋๋ค. โ์ด ์์น๋ ๋ฐ๋์ ๊ฐ๋ก๋ก ์ ๋ ฌ๋์ด์ผ ํด์โ, โ์ด ๋ผ๋ฒจ์ ๊ธธ์ด์ง๋ฉด ์๋์ผ๋ก ์ค ๋ฐ๊ฟ์ด ๋์ด์ผ ํด์โ ๊ฐ์ ๊ตฌ์ฒด์ ์ธ ์๊ตฌ์ฌํญ๋ค์ด ์์์ก์ฃ . ํ์ง๋ง ๋๋ถ๋ถ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ฌ์ธํ ์คํ์ผ๋ง์ ์๋ฒฝํ ์ง์ํ์ง ์์์ต๋๋ค.
๋ ๋ฒ์งธ๋ก๋ ์ ๋ฐ์ดํธ์ ํธํ์ฑ ๋ฌธ์ ์ ๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ง์ํ์ง ์๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด ๋ด๋ถ ์ฝ๋๋ฅผ ์ง์ ์์ ํ๊ฑฐ๋, private ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค ๋ณด๋ ๋ฐ์ํ ๋ฌธ์ ์์ฃ .
// ์ปค์คํ
ํดํ์ ๋ง๋ค๊ธฐ ์ํด ์ฐจํธ์ private API๋ฅผ ๊ฑด๋๋ฆฝ๋๋ค
const originalDraw = Chart.prototype._draw;
Chart.prototype._draw = function () {
originalDraw.call(this);
if (this.tooltip._active) {
// ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ด๋ถ ๊ตฌํ์ ์์กดํ๋ ์ํํ ์ฝ๋
const tooltip = this.tooltip;
const points = tooltip._active;
// ... ์ปค์คํ
๋ก์ง
}
};
์ด๋ฐ ์ฝ๋๋ ์์๋ฐฉํธ์ผ๋ก๋ ๋์ํ ์ ์์ง๋ง, ๊ฒฐ๊ตญ ์ํํญํ๊ณผ ๊ฐ์์ต๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ ๋ฐ์ดํธ๋๋ฉด์ ๋ด๋ถ ๊ตฌํ์ด ๋ฐ๋๋ฉด ์์ ํ๋ ์ฝ๋๋ค์ด ๋ชจ๋ ๊นจ์ ธ๋ฒ๋ ธ๊ณ , ๋งค๋ฒ ์๋ก์ด ๋ฒ์ ์ ๋ง์ถฐ ๋ค์ ๊ณ ์ณ์ผ ํ์ต๋๋ค.
์ธ ๋ฒ์งธ๋ ๋ณต์กํ ์ต์ ๊ตฌ์กฐ๋ก ์ธํ ์ ์ง๋ณด์์ ์ด๋ ค์์ ๋๋ค. ์ฐจํธ์ ๋ชจ๋ ์์๋ฅผ ์ต์ ์ผ๋ก ์ ์ดํ๋ค ๋ณด๋, ์ฝ๋๋ ์ ์ ๋ ๋ณต์กํด์ก์ต๋๋ค.
// ๊ธฐ์กด ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ณต์กํ ์ต์
๊ตฌ์กฐ
const chart = new Chart({
options: {
scales: {
y: {
ticks: {
// ์์น ๊ฐ๋ก ์ ๋ ฌ์ ์ํ ๋ณต์กํ ์ต์
align: "end",
crossAlign: "center",
padding: 8,
},
},
},
plugins: {
legend: {
labels: {
// ๋ผ๋ฒจ ์ค๋ฐ๊ฟ์ ์ํ ํดํน
generateLabels: (chart) => {
// ๋ณต์กํ ์ปค์คํ
๋ก์ง...
},
},
},
},
},
});
Headless UI: ์๋ก์ด ์ ๊ทผ ๋ฐฉ์
์ด๋ฌํ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ๊ธฐ ์ํด ๋์ ํ ๊ฒ์ด โheadless UIโ ํจํด์ ๋๋ค. headless-chart๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ ์๊ฐ์ ํํ์ ์์ ํ ๋ถ๋ฆฌํฉ๋๋ค. ์ค์ ๊ตฌํ์ ์ด๋ ๊ฒ ๋จ์ํด์ง๋๋ค.
const chart = BarChart({
data, // ์์ํ ๋ฐ์ดํฐ
custom: {
// ๊ฐ UI ์์๋ฅผ ๋
๋ฆฝ์ ์ผ๋ก ์ปค์คํฐ๋ง์ด์ง
bar: ({ label }) =>
Container({
width: Infinity,
margin: EdgeInsets.symmetric({ horizontal: 8 }),
decoration: new BoxDecoration({
color: getBackgroundColor(label),
border: getBorder(label),
}),
}),
// ๋ผ๋ฒจ ์คํ์ผ๋ง๋ ์์ ์์ฌ
xAxisLabel: ({ name }) =>
Text(name, {
style: new TextStyle({
fontFamily: "Noto Sans JP",
fontSize: 12,
color: "#666666",
}),
}),
},
});
์ด๋ ๊ฒ ํ๋ฉด ์ฐจํธ์ ๊ฐ ์์๋ฅผ ๋ง์น ๋ ๊ณ ๋ธ๋ก์ฒ๋ผ ์กฐ๋ฆฝํ ์ ์์ต๋๋ค. ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ก์ง์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๋ด๋นํ๋, ์๊ฐ์ ํํ์ ์์ ํ ์์ ๋กญ๊ฒ ์ปค์คํฐ๋ง์ด์งํ ์ ์์ฃ . ๋ธ๋๋ ์ปฌ๋ฌ๋ฅผ ์ ์ฉํ๊ณ ์ถ์ผ์ ๊ฐ์? ๊ฐ๋จํฉ๋๋ค. ํน๋ณํ ๋ ์ด์์์ด ํ์ํ์ ๊ฐ์? ๋ฌธ์ ์์ต๋๋ค. ๋ชจ๋ ์๊ฐ์ ์์๋ฅผ ์ํ๋ ๋๋ก ๊ตฌํํ ์ ์์ผ๋๊น์.
์ด๊ฒ์ด ๋ฐ๋ก ์ ๊ฐ โheadless-chartโ๋ฅผ ์์ํ๊ฒ ๋ ๊ฒฐ์ ์ ์ธ ๊ณ๊ธฐ์์ต๋๋ค.
headless-chart
์ flitter
์ ํ์
์ฐจํธ์ ๋ณธ์ง๋ก ๋์๊ฐ๊ธฐ
์ฐจํธ์ ๋ณธ์ง์ ์ธ ์ญํ ์ ๋ค์ ์๊ฐํด ๋ดค์ต๋๋ค. ์ฐจํธ๋ ๋จ์ํ ๋ฐ์ดํฐ๋ฅผ ์๊ฐํํ๋ ๊ฒ์ ๋์ด, ์ฌ์ฉ์์ ์ํธ์์ฉ์ ํ๋ฉฐ ๋์ ์ผ๋ก ๋ฐ์ํด์ผ ํฉ๋๋ค. ๊ธฐ์กด ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ์ด๋ฐ ๋์ ์ธ ์ํธ์์ฉ์ ๊ตฌํํ๊ธฐ ์ํด ๋ณต์กํ ์ค์ ๊ณผ ์ฝ๋ฐฑ ํจ์๋ค์ ์๊ตฌํ์ฃ .
// ๊ธฐ์กด ๋ฐฉ์์ ๋ณต์กํ ์ด๋ฒคํธ ํธ๋ค๋ง
chart.on("mousemove", (evt) => {
const points = chart.getElementsAtEventForMode(evt, "nearest");
if (points.length) {
const firstPoint = points[0];
// ๋ณต์กํ ์ํ ๊ด๋ฆฌ ์ฝ๋...
}
});
Flitter: ๋ธ๋ผ์ฐ์ ์์ ์๊ฐ์ ์ป๋ค
์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ง๋ ๊ฒ์ด โflitterโ์ ๋๋ค. ํฅ๋ฏธ๋กญ๊ฒ๋, ํด๋ต์ ์ฐ๋ฆฌ๊ฐ ๋งค์ผ ์ฌ์ฉํ๋ ๋ธ๋ผ์ฐ์ ์ ๋์ ๋ฐฉ์์ ์์์ต๋๋ค. ๋ธ๋ผ์ฐ์ ๋ ์ด๋ป๊ฒ html์ ๊ฐ์ง๊ณ ๋ณต์กํ ์น ํ์ด์ง๋ฅผ ํจ์จ์ ์ผ๋ก ๋ ๋๋งํ ๊น์?
- ๋ ์ด์์ ๊ณ์ฐ (Layout): ๊ฐ ์์์ ํฌ๊ธฐ์ ์์น๋ฅผ ๊ณ์ฐ
- ํ์ธํ (Painting): ์ค์ ํฝ์ ์ ํ๋ฉด์ ๊ทธ๋ฆฌ๊ธฐ
- ์ปดํฌ์งํ (Compositing): ๋ ์ด์ด๋ฅผ ์กฐํฉํ์ฌ ์ต์ข ํ๋ฉด ๊ตฌ์ฑ
์ด๋ฌํ ๋ธ๋ผ์ฐ์ ์ ๋ ๋๋ง ํ๋ก์ธ์ค์์ ์๊ฐ์ ๋ฐ์, ๊ทธ๋ฆฌ๊ณ Flutter์ ์์ ฏ ์์คํ ์ ์ฐธ๊ณ ํ์ฌ flitter๋ฅผ ์ค๊ณํ์ต๋๋ค. Flutter๊ฐ ๋ชจ๋ฐ์ผ ์ฑ ๊ฐ๋ฐ์์ ์ ์ธ์ UI์ ํจ์จ์ ์ธ ๋ ๋๋ง์ ์ ๊ณตํ๋ฏ, flitter๋ ์น์์ ๊ฐ์ ๊ฒฝํ์ ์ ๊ณตํ๊ณ ์ ํ์ฃ .
// ์ ์ธ์ ์ธ ๋ ์ด์์ ์ ์
const layout = Container({
padding: EdgeInsets.only({ left: 30, bottom: 70 }),
child: Stack({
children: [
// ์๋์ผ๋ก ์ต์ ํ๋ ๋ ์ด์์ ๊ณ์ฐ
Positioned({
top: 0,
right: 0,
child: Text("Value: ${value}", {
style: new TextStyle({
fontFamily: "Noto Sans JP",
fontSize: 12,
color: "#666666",
}),
}),
}),
],
}),
});
// ์ธํฐ๋์
์ ์
const interactiveChart = GestureDetector({
onClick: (event) => {
// ํฐ์น/ํด๋ฆญ ์ด๋ฒคํธ ์ฒ๋ฆฌ
},
onDrag: (event) => {
// ๋๋๊ทธ ์ด๋ฒคํธ ์ฒ๋ฆฌ
},
child: chart,
});
์ ์ธ์ ์ ์ด์ ์ง์ ํ ํ
flitter์ ๊ฐ์ฅ ํฐ ์ฅ์ ์ ๋ชจ๋ ๊ฒ์ ์ ์ธ์ ์ผ๋ก ์ ์ดํ ์ ์๋ค๋ ์ ์ ๋๋ค.
const animatedBar = AnimatedContainer({
duration: 300,
height: value, // ์๋์ผ๋ก ๋ถ๋๋ฌ์ด ์ ํ
decoration: new BoxDecoration({
color: isHovered ? "blue" : "gray",
}),
});
class ChartWidget extends StatefulWidget {
constructor() {
super();
this.state = {
selectedBar: null,
hoveredIndex: -1,
};
}
build() {
return BarChart({
data,
custom: {
bar: ({ index }) =>
Container({
color: this.state.hoveredIndex === index ? "highlight" : "normal",
child: this.buildBarContent(index),
}),
},
});
}
}
const interactiveBar = GestureDetector({
onClick: (event) => {
// ํด๋ฆญ ์ด๋ฒคํธ ์ฒ๋ฆฌ
this.setState({ selectedBar: event.index });
},
onDrag: (event) => {
// ๋๋๊ทธ ์ด๋ฒคํธ ์ฒ๋ฆฌ
this.handleChartDrag(event);
},
child: customBar,
});
๋ ๋๋ฌ ํธํ์ฑ๊ณผ ์ฑ๋ฅ ์ต์ ํ
flitter๋ SVG์ Canvas ๋ชจ๋๋ฅผ ์ง์ํฉ๋๋ค. ๊ฐ๋ฐ์๋ ๋์ผํ ์ฝ๋๋ก ๋ ๋ ๋๋ฌ๋ฅผ ๋ชจ๋ ํ์ฉํ ์ ์์ฃ .
// SVG ๋ ๋๋ฌ ์ฌ์ฉ
<Widget widget={chart} renderer="svg" />
// Canvas ๋ ๋๋ฌ ์ฌ์ฉ
<Widget widget={chart} renderer="canvas" />

ํนํ ์ฑ๋ฅ ์ต์ ํ์ ๋ง์ ๊ณต์ ๋ค์์ต๋๋ค.
- Virtual DOM๊ณผ ์ ์ฌํ ๋ ๋ ํธ๋ฆฌ: ๋ณ๊ฒฝ๋ ๋ถ๋ถ๋ง ํจ์จ์ ์ผ๋ก ์ ๋ฐ์ดํธ
- ์๋ ๋ ์ด์์ ์ต์ ํ: ๋ถํ์ํ ์ฌ๊ณ์ฐ ๋ฐฉ์ง
- ๋ ์ด์ด ๊ด๋ฆฌ: ์ ๋๋ฉ์ด์ ์ด ํ์ํ ์์๋ง ๋ณ๋ ๋ ์ด์ด๋ก ๋ถ๋ฆฌ
์ด ๋ชจ๋ ๋ด์ฉ์ flitter.dev ๋ฌธ์์์ ์์ธํ ํ์ธํ ์ ์์ต๋๋ค. flitter๋ ๋จ์ํ ์บ๋ฒ์ค ์กฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋, ๋ธ๋ผ์ฐ์ ์ ๋ ๋๋ง ํ์ดํ๋ผ์ธ์ ์ฌํด์ํ ๊ฒฐ๊ณผ๋ฌผ์ ๋๋ค. ๋ฆฌ์กํธ์ ์ ์ธ์ UI ๊ด๋ฆฌ์ ๋ธ๋ผ์ฐ์ ์ ํจ์จ์ ์ธ ๋ ๋๋ง ํ๋ก์ธ์ค, ์ด ๋ ๊ฐ์ง ์ฅ์ ์ ๋ชจ๋ ์ทจํ ๊ฒ์ด์ฃ . ๊ฒฐ๊ณผ์ ์ผ๋ก headless-chart๋ ์ด๋ฐ flitter์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ๋ฐํ์ผ๋ก, ์ฐจํธ ๊ฐ๋ฐ์ ์์ด ์์ ํ ์๋ก์ด ํจ๋ฌ๋ค์์ ์ ์ํ ์ ์๊ฒ ๋์์ต๋๋ค.
๋๋๋ง ํ๋ ์์ํฌ๋ฅผ ๋ง๋ค๋ฉด์ ๋ฐฐ์ด ์
flitter๋ฅผ ๋ง๋ค๊ธฐ ์ , Flutter์ ์์ค ์ฝ๋๋ฅผ ๊น์ด ์๊ฒ ๋ถ์ํ์ต๋๋ค. ํนํ ๋ ์ด์์ ์์คํ ์ ์ค๊ณ์์ ํฐ ํต์ฐฐ์ ์ป์์ต๋๋ค.
๋ ์ด์์์ ์ผ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ
Flutter์ ๊ฐ์ฅ ์ธ์์ ์ธ ๋ถ๋ถ์ ๋ณต์กํ ๋ ์ด์์ ๊ณ์ฐ์ ๋จ์ํ ์ผ๋ฐฉํฅ ํ๋ฆ์ผ๋ก ํด๊ฒฐํ ๋ฐฉ์์ด์์ต๋๋ค.
// Flutter ์ฝ๋ ์์
class RenderObject {
layout(Constraints constraints) {
// 1. ๋ถ๋ชจ๋ก๋ถํฐ ์ ์ฝ์กฐ๊ฑด์ด ์๋๋ก ์ ๋ฌ๋จ
this.constraints = constraints;
// 2. ์์๋ค์ ๋ ์ด์์์ ๊ณ์ฐ
this.children.forEach(child => {
child.layout(this.getChildConstraints());
});
// 3. ๊ณ์ฐ๋ ์์๋ค์ ํฌ๊ธฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์ ์ ํฌ๊ธฐ๋ฅผ ๊ฒฐ์ ํ๊ณ ์๋ก ์ ๋ฌ
const size = this.calculateSize();
return size;
}
}
์ด ๋ฐฉ์์ ์๋ฆ๋ค์์ ๋ณต์กํ ๋ถ๋ชจ-์์ ๊ด๊ณ์ ๋ ์ด์์ ๊ณ์ฐ์ ๋จ์ํ ํ ๋ฐฉํฅ ํ๋ฆ์ผ๋ก ์ ๋ฆฌํ๋ค๋ ์ ์ ๋๋ค. ์ ์ฝ์กฐ๊ฑด(Constraints)์ ์์์ ์๋๋ก, ํฌ๊ธฐ(Size)๋ ์๋์์ ์๋ก ์ ๋ฌ๋๋ ๋จ์ํ ๊ท์น์ผ๋ก ๋ชจ๋ ๋ ์ด์์ ๊ณ์ฐ์ด ์ด๋ฃจ์ด์ง๋๋ค.

์์ ๋์ ์กฐํฉ์ ์ ํํ ๋์์ธ ํจํด๋ค
Flutter ์ฝ๋๋ฅผ ๋ถ์ํ๋ฉด์ ๊ฐ์ฅ ํฐ ๊นจ๋ฌ์์ ์ค ๊ฒ์ โ์์์ ์ต์ํํ๋ ๋ค์ํ ํจํด๋คโ์ด์์ต๋๋ค.
๋ฐ์ฝ๋ ์ดํฐ ํจํด: ์์ ฏ ํ์ฅ์ ์ฐ์ํ ๋ฐฉ๋ฒ
// ์์ ๋์ ๋ํ์ผ๋ก ๊ธฐ๋ฅ ํ์ฅ
const paddedChart = Padding({
padding: EdgeInsets.all(16),
child: BorderContainer({
border: Border.all({ color: 'blue' }),
child: Chart(...)
})
});
์๋ก์ด ๊ธฐ๋ฅ์ด ํ์ํ ๋๋ง๋ค ์์์ผ๋ก ์ ํด๋์ค๋ฅผ ๋ง๋๋ ๋์ , ๊ธฐ์กด ์์ ฏ์ ๋ค๋ฅธ ์์ ฏ์ผ๋ก ๊ฐ์ธ๋ ๋ฐฉ์์ ์ฌ์ฉํฉ๋๋ค. ์ด๋ ์ฝ๋์ ์ ์ฐ์ฑ๊ณผ ์ฌ์ฌ์ฉ์ฑ์ ํฌ๊ฒ ๋์ฌ์ค๋๋ค.
์ ๋ต ํจํด: ๊ทธ๋ฆฌ๊ธฐ ๋ก์ง์ ๋ถ๋ฆฌ
ํนํ Border๋ฅผ ๊ทธ๋ฆฌ๋ ๋ถ๋ถ์์ ์ ๋ต ํจํด์ ์ค์ ์ ์ธ ํ์ฉ์ ๋ฐฐ์ ์ต๋๋ค.
// Flutter์ Border ํด๋์ค์์ ์๊ฐ์ ๋ฐ์ ๊ตฌํ
class Border {
paint(canvas, rect) {
// ์ค์ ๊ทธ๋ฆฌ๊ธฐ ์ ๋ต์ ์คํ
this.side.paint(canvas, rect);
}
}
// ์์ ฏ์ ๋จ์ํ ์ ๋ฌ๋ฐ์ Border ๊ฐ์ฒด์ paint๋ฅผ ํธ์ถ
class BorderContainer extends Widget {
performDraw(canvas) {
// border ์์ฑ์ ๊ทธ๋ฆฌ๊ธฐ ์ ๋ต์ ์คํ
this.border.paint(canvas, this.rect);
// ์์ ์์ ฏ ๊ทธ๋ฆฌ๊ธฐ
this.child?.draw(canvas);
}
}
์ด๋ ๊ฒ ํ๋ฉด ๊ทธ๋ฆฌ๊ธฐ ๋ก์ง์ ์์ ํ ๋ถ๋ฆฌํ ์ ์๊ณ , ๋ค๋ฅธ ์์ ฏ์์๋ ๊ฐ์ Border ๋ก์ง์ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค. ์์ ์์ด๋ ์ฝ๋๋ฅผ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ด ๊ฑฐ์ฃ .
์ฑ๋ฅ ์ต์ ํ์ ์ฐ์ํ ํด๊ฒฐ์ฑ
Flutter์ ๋ ๋๋ง ์ต์ ํ ๊ธฐ๋ฒ๋ ๋ง์ ์๊ฐ์ ์ฃผ์์ต๋๋ค. ํนํ ์ ๋๋ฉ์ด์ ํ๋ ์ ๊ด๋ฆฌ ๋ฐฉ์์ด ์ธ์์ ์ด์์ฃ .
class RenderManager {
markNeedsDraw(widget) {
if (!this.scheduledDraw) {
this.scheduledDraw = true;
requestAnimationFrame(() => {
// ํ ํ๋ ์ ๋์ ์์ธ ๋ชจ๋ ๋ณ๊ฒฝ์ฌํญ์ ํ ๋ฒ์ ์ฒ๋ฆฌ
this.drawDirtyWidgets();
this.scheduledDraw = false;
});
}
}
}
์ฝ๋ ๋ถ์ ๋ฅ๋ ฅ์ ์ฑ์ฅ
์ด๋ฌํ ํ์ต ๊ณผ์ ์ ํตํด ์ป์ ๊ฐ์ฅ ํฐ ์ํ์ ์ฝ๋๋ฅผ ์ฝ๋ ๋์ด ์๊ฒผ๋ค๋ ๊ฒ์ ๋๋ค. ์ฒ์์๋ GOF์ ๋์์ธ ํจํด์ ์ฑ ์ผ๋ก ๋ฐฐ์ ์ ๋๋ ์๋ฟ์ง ์์๋ ๊ฐ๋ ๋ค์ด, ๊ตฌ๊ธ ์์ง๋์ด๋ค์ ์ค์ ์ฝ๋๋ฅผ ๋ณด๋ฉด์ ์์ํ๊ฒ ์ดํด๋๊ธฐ ์์ํ์ต๋๋ค. ์ด์ ๋ ์ด๋ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ฝ๋๋ฅผ ๋ณด๋๋ผ๋ โ์ ์ด๋ฐ ๊ตฌ์กฐ๋ฅผ ์ ํํ์๊น?โ, โ์ด ํจํด์ ์ฌ์ฉํจ์ผ๋ก์จ ์ด๋ค ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์๊น?โ๋ฅผ ์์ฐ์ค๋ฝ๊ฒ ๊ณ ๋ฏผํ๊ฒ ๋์๊ณ , ๊ทธ ๊ณผ์ ์์ ์ฝ๋๋ฅผ ์ฝ๊ณ ์ดํดํ๋ ๋ฅ๋ ฅ์ด ํฌ๊ฒ ํฅ์๋์์ต๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก ์ด ๊ฒฝํ๋ค์ ์ ๊ฐ ๋ค๋ฅธ ์คํ์์ค ํ๋ก์ ํธ๋ค์ ๋ถ์ํ๊ณ ๊ธฐ์ฌํ๋ ๋ฐ ํฐ ๋์์ด ๋์์ต๋๋ค. ๋จ์ํ API ๋ฌธ์๋ง ๋ณด๊ณ ์ฌ์ฉํ๋ ๊ฒ์ด ์๋๋ผ, ํ์ํ ๋ ์ค์ ์ฝ๋๋ฅผ ํ๊ณ ๋ค์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ ์์ ๊ฐ์ ์ป๊ฒ ๋ ๊ฒ์ด์ฃ .
๊ฒฐ๋ก : ์ง์ ๋ง๋ค์ด๋ณด๋ฉด์ ์ป๋ ์ฑ์ฅ
headless-chart๋ ์ฌ์ ํ ์งํํ๊ณ ์์ต๋๋ค. ์ฒ์ ์์์ โ์ฐจํธ๋ฅผ ์์ ๋กญ๊ฒ ์ปค์คํฐ๋ง์ด์งํ๊ณ ์ถ๋คโ๋ผ๋ ๋จ์ํ ํ์์์ ์ถ๋ฐํ์ง๋ง, ์ด์ ๋ ๋ ํฐ ๊ทธ๋ฆผ์ ๊ทธ๋ฆฌ๊ณ ์์ต๋๋ค. ๋๊ตฌ๋ ์ํ๋ ์ฐจํธ ๋์์ธ์ ๋จ์จ์ ๋ง๋ค ์ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง๋๋ ๊ฒ์ด ์ ๋ชฉํ์ ๋๋ค. ์ด๋ฅผ ์ํด์๋ ๋ ๋ง์ ์ฌ์ฉ์ ํผ๋๋ฐฑ๊ณผ ์ปค๋ฎค๋ํฐ์ ๊ธฐ์ฌ๊ฐ ํ์ํ๊ณ ์. ์ด ํ๋ก์ ํธ๋ฅผ ํตํด ์ ๊ฐ ์ป์ ๊ฒ์ ๋จ์ํ ๊ธฐ์ ์ ์ธ ์ฑ๊ณผ๋ฅผ ๋์ด์ญ๋๋ค.
- ์ฒซ์งธ, ๋ค๋ฅธ ๊ฐ๋ฐ์์ ์ฝ๋๋ฅผ ์ฝ๊ณ ์ดํดํ๋ ๋ฅ๋ ฅ์ด ํฌ๊ฒ ํฅ์๋์์ต๋๋ค. Flutter์ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ๋ถ์ํ๋ฉด์, ๋๊ท๋ชจ ํ๋ก์ ํธ์ ์ค๊ณ ์ฒ ํ๊ณผ ํจํด์ ์ดํดํ๋ ๋์ด ์๊ฒผ์ต๋๋ค.
- ๋์งธ, ์คํ์์ค ์ํ๊ณ์ ๋ํ ์ดํด๊ฐ ๊น์ด์ก์ต๋๋ค. ๋จ์ํ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ์ ๋์ด, ๋ฌธ์ํ, ์ด์ ๊ด๋ฆฌ, PR ๋ฆฌ๋ทฐ ๋ฑ ํ๋ก์ ํธ ์ ๋ฐ์ ์์ฐ๋ฅด๋ ๊ฒฝํ์ ์์ ์ ์์์ฃ .
- ์ ์งธ, ๋ฌธ์ ํด๊ฒฐ ๋ฅ๋ ฅ์ ํฅ์ํ์ต๋๋ค. ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ๋ฒ๊ทธ๋ฅผ ๋ฐ๊ฒฌํ์ ๋๋, ์ด์ ๋ ๋ง์ค์ด์ง ์๊ณ ์ง์ ์ฝ๋๋ฅผ ๋ถ์ํ๊ณ PR์ ์ฌ๋ฆฝ๋๋ค. "์ด๊ฑด ์ด๋ ค์ธ ๊ฑฐ์ผ"๋ผ๋ ์ ์ ๊ฒฌ ๋์ "ํ๋ฒ ํ๊ณ ๋ค์ด ๋ณด์"๋ ํ๋๊ฐ ์์ฐ์ค๋ฌ์์ก์ต๋๋ค.
๋ฌผ๋ก ์ง๊ธ๋ ๋งค์ผ ์๋ก์ด ๋์ ๊ณผ์ ๋ค์ด ์๊น๋๋ค. ๋ ๋์ ์ฑ๋ฅ, ๋ ์ง๊ด์ ์ธ API, ๋ ํ๋ถํ ๊ธฐ๋ฅ๋คโฆ ํ์ง๋ง ์ด์ ๋ ์ด๋ฐ ๋์ ๋ค์ด ๋๋ ต์ง ์์ต๋๋ค. ์คํ๋ ค ์ค๋ ์ผ๋ก ๋ค๊ฐ์ค์ฃ . ํ๋ก์ ํธ๋ฅผ ์์ํ ๋๋ ๋ชฐ๋๋ ์ง์ค์ด ํ๋ ์์ต๋๋ค. โ๋ฌด์ธ๊ฐ๋ฅผ ๋ง๋ ๋คโ๋ ๊ฒ์ ๋จ์ํ ๊ฒฐ๊ณผ๋ฌผ์ ๋ง๋๋ ๊ฒ์ด ์๋๋ผ, ๊ทธ ๊ณผ์ ์์ ์์ ์ ์ฑ์ฅ์ํค๋ ์ฌ์ ์ด๋ผ๋ ๊ฒ์ ๋๋ค.
๋ง์ฝ ์ด ๊ธ์ ํตํด ๊ด์ฌ์ด ์๊ธฐ์
จ๋ค๋ฉด, ๋ถ๋ด ์์ด headless-chart
๊นํ๋ธ (meursyphus/headless-chart
)์์ ํผ๋๋ฐฑ๊ณผ ์์ด๋์ด๋ฅผ ๋จ๊ฒจ์ฃผ์
๋ ์ข์ต๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง๋ค๋ฉด์ ๋ฐฐ์ด ๊ฐ์ฅ ํฐ ๊ตํ์ ๋ฐ๋ก โ๋ด๊ฐ ๋ง๋ ๊ฒ์ด ๋๊ตฐ๊ฐ์๊ฒ ์ ์ฉํด์ง๋ค๋ฉด, ๊ฑฐ๊ธฐ์๋ถํฐ ๋ชจ๋ ์ฑ์ฅ์ด ์์๋๋ค.โ๋ผ๋ ์ฌ์ค์ด๊ธฐ ๋๋ฌธ์
๋๋ค.
ยฉ๏ธ์์ฆIT์ ๋ชจ๋ ์ฝํ ์ธ ๋ ์ ์๊ถ๋ฒ์ ๋ณดํธ๋ฅผ ๋ฐ๋ ๋ฐ, ๋ฌด๋จ ์ ์ฌ์ ๋ณต์ฌ, ๋ฐฐํฌ ๋ฑ์ ๊ธํฉ๋๋ค.