diff --git a/FE/src/components/Login/index.tsx b/FE/src/components/Login/index.tsx index c72f4cc5..a4570b08 100644 --- a/FE/src/components/Login/index.tsx +++ b/FE/src/components/Login/index.tsx @@ -46,8 +46,8 @@ export default function Login() { }[errorCode] }

-
-
+ +
- diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index be34074b..095ed44e 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -1,3 +1,193 @@ +import { useEffect, useRef } from 'react'; +import { dummy, DummyStock } from './dummy'; + +type Padding = { + top: number; + left: number; + right: number; + bottom: number; +}; + export default function Chart() { - return
차트
; + const containerRef = useRef(null); + const canvasRef = useRef(null); + + useEffect(() => { + const parent = containerRef.current; + const canvas = canvasRef.current; + + if (!canvas || !parent) return; + + const displayWidth = parent.clientWidth; + const displayHeight = parent.clientHeight; + + // 해상도 높이기 + canvas.width = displayWidth * 4; + canvas.height = displayHeight * 4; + + canvas.style.width = `${displayWidth}px`; + canvas.style.height = `${displayHeight}px`; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + ctx.fillStyle = 'white'; + + ctx.fillRect(0, 0, canvas.width, canvas.height); + + const padding = { + top: 10, + right: 10, + bottom: 10, + left: 10, + }; + + const chartWidth = canvas.width - padding.left - padding.right; + const chartHeight = canvas.height - padding.top - padding.bottom; + const volumeBoundary = chartHeight * 0.2; // chartHeight의 20% + const mainHeight = chartHeight - volumeBoundary; + + drawLineChart(ctx, dummy, chartWidth, mainHeight, padding); + + drawBarChart( + ctx, + dummy, + chartWidth, + chartHeight, + padding, + 0, + chartHeight * 0.8, + ); + + drawCandleChart(ctx, dummy, chartWidth, mainHeight, padding, 0, 0); + }, []); + + return ( +
+ +
+ ); +} + +function drawLineChart( + ctx: CanvasRenderingContext2D, + data: DummyStock[], + width: number, + height: number, + padding: Padding, + x: number = 0, + y: number = 0, +) { + ctx.beginPath(); + + const n = data.length; + + const yMax = Math.round(Math.max(...data.map((d) => d.low)) * 1.006 * 100); + const yMin = Math.round(Math.min(...data.map((d) => d.low)) * 0.994 * 100); + + data.forEach((v, i) => { + const value = Math.round(v.low * 100); + const cx = x + padding.left + (width * i) / (n - 1); + const cy = + y + padding.top + height - (height * (value - yMin)) / (yMax - yMin); + + if (i === 0) { + ctx.moveTo(cx, cy); + } else { + ctx.lineTo(cx, cy); + } + }); + + ctx.lineWidth = 1; + ctx.stroke(); +} + +function drawBarChart( + ctx: CanvasRenderingContext2D, + data: DummyStock[], + width: number, + height: number, + padding: Padding, + x: number, + y: number, +) { + ctx.beginPath(); + + const yMax = Math.round(Math.max(...data.map((d) => d.volume)) * 1.006 * 100); + const yMin = Math.round(Math.min(...data.map((d) => d.volume)) * 0.994 * 100); + + const gap = Math.floor((width / dummy.length) * 0.8); + + data.forEach((e, i) => { + const value = Math.round(e.volume * 100); + const cx = x + padding.left + (width * i) / (dummy.length - 1); + const cy = padding.top + ((height - y) * (value - yMin)) / (yMax - yMin); + + ctx.fillStyle = e.open < e.close ? 'red' : 'blue'; + ctx.fillRect(cx, height, gap, -cy); + }); + + ctx.lineWidth = 2; + ctx.stroke(); +} + +function drawCandleChart( + ctx: CanvasRenderingContext2D, + data: DummyStock[], + width: number, + height: number, + padding: Padding, + x: number, + y: number, +) { + ctx.beginPath(); + + const yMax = Math.round( + Math.max(...data.map((d) => Math.max(d.close, d.open, d.high, d.low))) * + 1.006 * + 100, + ); + const yMin = Math.round( + Math.min(...data.map((d) => Math.max(d.close, d.open, d.high, d.low))) * + 0.994 * + 100, + ); + + data.forEach((e, i) => { + ctx.beginPath(); + + const { open, close, high, low } = e; + const gap = Math.floor((width / dummy.length) * 0.8); + const cx = x + padding.left + (width * i) / (dummy.length - 1); + + const openValue = Math.round(open * 100); + const closeValue = Math.round(close * 100); + const highValue = Math.round(high * 100); + const lowValue = Math.round(low * 100); + + const openY = + y + padding.top + height - (height * (openValue - yMin)) / (yMax - yMin); + const closeY = + y + padding.top + height - (height * (closeValue - yMin)) / (yMax - yMin); + const highY = + y + padding.top + height - (height * (highValue - yMin)) / (yMax - yMin); + const lowY = + y + padding.top + height - (height * (lowValue - yMin)) / (yMax - yMin); + + if (open > close) { + ctx.fillStyle = 'blue'; + ctx.strokeStyle = 'blue'; + ctx.fillRect(cx, closeY, gap, openY - closeY); + } else { + ctx.fillStyle = 'red'; + ctx.strokeStyle = 'red'; + ctx.fillRect(cx, openY, gap, closeY - openY); + } + + const middle = cx + Math.floor(gap / 2); + + ctx.moveTo(middle, highY); + ctx.lineTo(middle, lowY); + ctx.stroke(); + }); } diff --git a/FE/src/components/StocksDetail/Header.tsx b/FE/src/components/StocksDetail/Header.tsx index 91309470..767746c8 100644 --- a/FE/src/components/StocksDetail/Header.tsx +++ b/FE/src/components/StocksDetail/Header.tsx @@ -1,8 +1,39 @@ export default function Header() { return ( -
-
삼성전자
-
당기순이익
+
+
+
+

삼성전자

+

005930

+
+
+

60,900원

+

어제보다

+

+1800원 (3.0%)

+
+
+
+
+

당기순이익

+

9조 8,143억

+
+
+

영업이익

+

10조 4,439억

+
+
+

매출액

+

74조 683억

+
+
+

시총

+

361조 1,718억

+
+
+

PER

+

14.79배

+
+
); } diff --git a/FE/src/components/StocksDetail/dummy.ts b/FE/src/components/StocksDetail/dummy.ts new file mode 100644 index 00000000..ec2288da --- /dev/null +++ b/FE/src/components/StocksDetail/dummy.ts @@ -0,0 +1,611 @@ +export type DummyStock = { + date: string; + open: number; + close: number; + high: number; + low: number; + volume: number; +}; + +export const dummy: DummyStock[] = [ + { + date: '2024-11-07', + open: 58000, + close: 57000, + high: 58300, + low: 57000, + volume: 13451844, + }, + { + date: '2024-11-06', + open: 56900, + close: 57500, + high: 58100, + low: 56800, + volume: 16951844, + }, + { + date: '2024-11-05', + open: 57600, + close: 57300, + high: 58000, + low: 56300, + volume: 21901844, + }, + { + date: '2024-11-04', + open: 57800, + close: 57600, + high: 58100, + low: 57200, + volume: 17291844, + }, + { + date: '2024-11-03', + open: 58600, + close: 58700, + high: 59400, + low: 58400, + volume: 15471844, + }, + { + date: '2024-10-31', + open: 59000, + close: 58300, + high: 59600, + low: 58100, + volume: 18831844, + }, + { + date: '2024-10-30', + open: 58500, + close: 59200, + high: 61200, + low: 58300, + volume: 34901844, + }, + { + date: '2024-10-29', + open: 59100, + close: 59100, + high: 59800, + low: 58600, + volume: 19181844, + }, + { + date: '2024-10-28', + open: 58000, + close: 59600, + high: 59600, + low: 57300, + volume: 27871844, + }, + { + date: '2024-10-27', + open: 55700, + close: 58100, + high: 58500, + low: 55700, + volume: 27571844, + }, + { + date: '2024-10-24', + open: 56000, + close: 55900, + high: 56900, + low: 55800, + volume: 25511844, + }, + { + date: '2024-10-23', + open: 58200, + close: 56600, + high: 58500, + low: 56600, + volume: 30701844, + }, + { + date: '2024-10-22', + open: 57500, + close: 59100, + high: 60000, + low: 57100, + volume: 27111844, + }, + { + date: '2024-10-21', + open: 58800, + close: 57700, + high: 58900, + low: 57700, + volume: 26881844, + }, + { + date: '2024-10-20', + open: 59000, + close: 59000, + high: 59600, + low: 58500, + volume: 18331844, + }, + { + date: '2024-10-17', + open: 59900, + close: 59200, + high: 60100, + low: 59100, + volume: 14171844, + }, + { + date: '2024-10-16', + open: 59400, + close: 59700, + high: 60100, + low: 59100, + volume: 23001844, + }, + { + date: '2024-10-15', + open: 59400, + close: 59500, + high: 60000, + low: 59200, + volume: 23021844, + }, + { + date: '2024-10-14', + open: 61100, + close: 61000, + high: 61400, + low: 60100, + volume: 20651844, + }, + { + date: '2024-10-13', + open: 59500, + close: 60800, + high: 61200, + low: 59400, + volume: 20371844, + }, + { + date: '2024-10-10', + open: 59100, + close: 59300, + high: 60100, + low: 59000, + volume: 28901844, + }, + { + date: '2024-10-09', + open: 60100, + close: 58900, + high: 60200, + low: 58900, + volume: 44601844, + }, + { + date: '2024-10-08', + open: 60000, + close: 60100, + high: 60500, + low: 59800, + volume: 18631844, + }, + { + date: '2024-10-07', + open: 59800, + close: 60000, + high: 60300, + low: 59500, + volume: 17651844, + }, + { + date: '2024-10-06', + open: 59500, + close: 59800, + high: 60000, + low: 59300, + volume: 16671844, + }, + { + date: '2024-10-03', + open: 59000, + close: 59200, + high: 59500, + low: 58800, + volume: 16671844, + }, + { + date: '2024-10-02', + open: 59500, + close: 59000, + high: 59800, + low: 58800, + volume: 17651844, + }, + { + date: '2024-10-01', + open: 60000, + close: 59500, + high: 60200, + low: 59300, + volume: 18631844, + }, + { + date: '2024-09-30', + open: 60500, + close: 60000, + high: 60800, + low: 59800, + volume: 19611844, + }, + { + date: '2024-09-27', + open: 61000, + close: 60500, + high: 61200, + low: 60300, + volume: 20591844, + }, + { + date: '2024-09-26', + open: 61500, + close: 61000, + high: 61800, + low: 60800, + volume: 21571844, + }, + { + date: '2024-09-25', + open: 62000, + close: 61500, + high: 62300, + low: 61300, + volume: 22551844, + }, + { + date: '2024-09-24', + open: 62500, + close: 62000, + high: 62800, + low: 61800, + volume: 23531844, + }, + { + date: '2024-09-23', + open: 63000, + close: 62500, + high: 63300, + low: 62300, + volume: 24511844, + }, + { + date: '2024-09-20', + open: 63500, + close: 63000, + high: 63800, + low: 62800, + volume: 25491844, + }, + { + date: '2024-09-19', + open: 64000, + close: 63500, + high: 64300, + low: 63300, + volume: 26471844, + }, + { + date: '2024-09-18', + open: 64500, + close: 64000, + high: 64800, + low: 63800, + volume: 27451844, + }, + { + date: '2024-09-17', + open: 65000, + close: 64500, + high: 65300, + low: 64300, + volume: 28431844, + }, + { + date: '2024-09-16', + open: 65500, + close: 65000, + high: 65800, + low: 64800, + volume: 29411844, + }, + { + date: '2024-09-13', + open: 66000, + close: 65500, + high: 66300, + low: 65300, + volume: 30391844, + }, + { + date: '2024-09-12', + open: 66500, + close: 66000, + high: 66800, + low: 65800, + volume: 31371844, + }, + { + date: '2024-09-11', + open: 67000, + close: 66500, + high: 67300, + low: 66300, + volume: 32351844, + }, + { + date: '2024-09-10', + open: 67500, + close: 67000, + high: 67800, + low: 66800, + volume: 33331844, + }, + { + date: '2024-09-09', + open: 68000, + close: 67500, + high: 68300, + low: 67300, + volume: 34311844, + }, + { + date: '2024-09-06', + open: 68500, + close: 68000, + high: 68800, + low: 67800, + volume: 35291844, + }, + { + date: '2024-09-05', + open: 69000, + close: 68500, + high: 69300, + low: 68300, + volume: 36271844, + }, + { + date: '2024-09-04', + open: 69500, + close: 69000, + high: 69800, + low: 68800, + volume: 37251844, + }, + { + date: '2024-09-03', + open: 70000, + close: 69500, + high: 70300, + low: 69300, + volume: 38231844, + }, + { + date: '2024-09-02', + open: 70500, + close: 70000, + high: 70800, + low: 69800, + volume: 39211844, + }, + { + date: '2024-08-30', + open: 71000, + close: 70500, + high: 71300, + low: 70300, + volume: 40191844, + }, + { + date: '2024-08-29', + open: 71500, + close: 71000, + high: 71800, + low: 70800, + volume: 40191844, + }, + { + date: '2024-08-28', + open: 72000, + close: 71500, + high: 72300, + low: 71300, + volume: 39171844, + }, + { + date: '2024-08-27', + open: 72500, + close: 72000, + high: 72800, + low: 71800, + volume: 38151844, + }, + { + date: '2024-08-26', + open: 73000, + close: 72500, + high: 73300, + low: 72300, + volume: 37131844, + }, + { + date: '2024-08-23', + open: 73500, + close: 73000, + high: 73800, + low: 72800, + volume: 36111844, + }, + { + date: '2024-08-22', + open: 74000, + close: 73500, + high: 74300, + low: 73300, + volume: 35091844, + }, + { + date: '2024-08-21', + open: 74500, + close: 74000, + high: 74800, + low: 73800, + volume: 34071844, + }, + { + date: '2024-08-20', + open: 75000, + close: 74500, + high: 75300, + low: 74300, + volume: 33051844, + }, + { + date: '2024-08-19', + open: 75500, + close: 75000, + high: 75800, + low: 74800, + volume: 32031844, + }, + { + date: '2024-08-16', + open: 76000, + close: 75500, + high: 76300, + low: 75300, + volume: 31011844, + }, + { + date: '2024-08-15', + open: 76500, + close: 76000, + high: 76800, + low: 75800, + volume: 29991844, + }, + { + date: '2024-08-14', + open: 77000, + close: 76500, + high: 77300, + low: 76300, + volume: 28971844, + }, + { + date: '2024-08-13', + open: 77500, + close: 77000, + high: 77800, + low: 76800, + volume: 27951844, + }, + { + date: '2024-08-12', + open: 78000, + close: 77500, + high: 78300, + low: 77300, + volume: 26931844, + }, + { + date: '2024-08-09', + open: 78500, + close: 78000, + high: 78800, + low: 77800, + volume: 25911844, + }, + { + date: '2024-08-08', + open: 79000, + close: 78500, + high: 79300, + low: 78300, + volume: 24891844, + }, + { + date: '2024-08-07', + open: 79500, + close: 79000, + high: 79800, + low: 78800, + volume: 23871844, + }, + { + date: '2024-08-06', + open: 80000, + close: 79500, + high: 80300, + low: 79300, + volume: 22851844, + }, + { + date: '2024-08-05', + open: 80500, + close: 80000, + high: 80800, + low: 79800, + volume: 21831844, + }, + { + date: '2024-08-02', + open: 81000, + close: 80500, + high: 81300, + low: 80300, + volume: 20811844, + }, + { + date: '2024-08-01', + open: 81500, + close: 81000, + high: 81800, + low: 80800, + volume: 19791844, + }, + { + date: '2024-07-31', + open: 82000, + close: 81500, + high: 82300, + low: 81300, + volume: 18771844, + }, + { + date: '2024-07-30', + open: 82500, + close: 82000, + high: 82800, + low: 81800, + volume: 17751844, + }, + { + date: '2024-07-29', + open: 83000, + close: 82500, + high: 83300, + low: 82300, + volume: 16731844, + }, + { + date: '2024-07-26', + open: 83500, + close: 83000, + high: 83800, + low: 82800, + volume: 15711844, + }, +].reverse(); diff --git a/FE/src/page/StocksDetail.tsx b/FE/src/page/StocksDetail.tsx index 536ef091..2eee8274 100644 --- a/FE/src/page/StocksDetail.tsx +++ b/FE/src/page/StocksDetail.tsx @@ -5,7 +5,7 @@ import TradeSection from 'components/StocksDetail/TradeSection'; export default function StocksDetail() { return ( -
+