From 2738b4758d1c542e262706b6b2fec7d57bc29cfc Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 13:09:32 +0900 Subject: [PATCH 01/14] =?UTF-8?q?=E2=9C=A8=20feat:=20=EA=B1=B0=EB=9E=98?= =?UTF-8?q?=ED=98=84=ED=99=A9=20SSE=20=EC=97=B0=EB=8F=99=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20#61?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/StocksDetail/PriceSection.tsx | 82 +++++++++---------- .../StocksDetail/PriceSectionSseHook.ts | 2 +- 2 files changed, 38 insertions(+), 46 deletions(-) diff --git a/FE/src/components/StocksDetail/PriceSection.tsx b/FE/src/components/StocksDetail/PriceSection.tsx index 8266d8b5..85d74c57 100644 --- a/FE/src/components/StocksDetail/PriceSection.tsx +++ b/FE/src/components/StocksDetail/PriceSection.tsx @@ -3,51 +3,49 @@ import PriceTableColumn from './PriceTableColumn.tsx'; import PriceTableLiveCard from './PriceTableLiveCard.tsx'; import PriceTableDayCard from './PriceTableDayCard.tsx'; import { useParams } from 'react-router-dom'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; import { DailyPriceDataType, PriceDataType } from './PriceDataType.ts'; import { getTradeHistory } from 'service/getTradeHistory.ts'; -// import { createSSEConnection } from './PriceSectionSseHook.ts'; +import { createSSEConnection } from './PriceSectionSseHook.ts'; export default function PriceSection() { + const { id } = useParams(); const [buttonFlag, setButtonFlag] = useState(true); const indicatorRef = useRef(null); const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]); - const { id } = useParams(); - // const [tradeData, setTradeData] = useState([]); - // const queryClient = useQueryClient(); - const { data: initialData = [], isLoading } = useQuery({ + const queryClient = useQueryClient(); + + const { data: tradeData = [], isLoading } = useQuery({ queryKey: ['detail', id, buttonFlag], queryFn: () => getTradeHistory(id as string, buttonFlag), - // refetchInterval: 1000, cacheTime: 30000, staleTime: 1000, }); - // const addData = (newData: PriceDataType) => { - // setTradeData((prev) => [...prev, newData]); - // - // queryClient.setQueryData( - // ['detail', id, buttonFlag], - // (old: PriceDataType[] = []) => { - // return [...old, newData]; - // }, - // ); - // }; + const addData = (newData: PriceDataType) => { + queryClient.setQueryData( + ['detail', id, buttonFlag], + (old: PriceDataType[] = []) => { + return [newData, ...old].slice(0, 30); + }, + ); + }; - // useEffect(() => { - // const targetWord = buttonFlag ? 'today' : 'daily'; - // - // const eventSource = createSSEConnection( - // `http://223.130.151.42:3000/api/stocks/trade-history/${id}/${targetWord}-sse`, - // addData, - // ); - // - // return () => { - // if (eventSource) { - // eventSource.close(); - // } - // }; - // }, [buttonFlag, id]); + useEffect(() => { + const targetWord = buttonFlag ? 'today' : 'daily'; + + const eventSource = createSSEConnection( + `http://223.130.151.42:3000/api/stocks/trade-history/${id}/${targetWord}-sse`, + addData, + ); + + return () => { + if (eventSource) { + console.log('SSE connection close'); + eventSource.close(); + } + }; + }, [buttonFlag, id]); useEffect(() => { const tmpIndex = buttonFlag ? 0 : 1; @@ -60,10 +58,6 @@ export default function PriceSection() { } }, [buttonFlag]); - // useEffect(() => { - // setTradeData(initialData); - // }, [initialData]); - return (
Loading... - ) : !initialData ? ( + ) : !tradeData ? ( No data available ) : buttonFlag ? ( - initialData.map((eachData: PriceDataType, index: number) => ( + tradeData.map((eachData: PriceDataType, index: number) => ( )) ) : ( - initialData.map( - (eachData: DailyPriceDataType, index: number) => ( - - ), - ) + tradeData.map((eachData: DailyPriceDataType, index: number) => ( + + )) )} diff --git a/FE/src/components/StocksDetail/PriceSectionSseHook.ts b/FE/src/components/StocksDetail/PriceSectionSseHook.ts index e384b06c..237657e2 100644 --- a/FE/src/components/StocksDetail/PriceSectionSseHook.ts +++ b/FE/src/components/StocksDetail/PriceSectionSseHook.ts @@ -9,7 +9,7 @@ export const createSSEConnection = ( eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data); - onMessage(data); + onMessage(data.tradeData); } catch (error) { console.error('Failed to parse SSE message:', error); } From ca8820349edea28f391f1a1c76ac188256c88184 Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 13:10:37 +0900 Subject: [PATCH 02/14] =?UTF-8?q?=F0=9F=92=84design:=20=EC=B0=A8=ED=8A=B8?= =?UTF-8?q?=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 26984c75..44571401 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -185,7 +185,7 @@ export default function Chart({ code }: StocksDeatailChartProps) {
{/* X축 영역 */} -
+
From 5d6dc7810b2fcced8cc45ec4f847f36ec45ab0b5 Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 13:19:46 +0900 Subject: [PATCH 03/14] =?UTF-8?q?=F0=9F=94=A7=20fix:=20SSE=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20#61=20SSE?= =?UTF-8?q?=EC=9D=98=20=EA=B2=BD=EC=9A=B0=20=EB=8B=B9=EC=9D=BC=20=EC=8B=A4?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A7=8C=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/PriceSection.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FE/src/components/StocksDetail/PriceSection.tsx b/FE/src/components/StocksDetail/PriceSection.tsx index 85d74c57..0030c070 100644 --- a/FE/src/components/StocksDetail/PriceSection.tsx +++ b/FE/src/components/StocksDetail/PriceSection.tsx @@ -32,10 +32,9 @@ export default function PriceSection() { }; useEffect(() => { - const targetWord = buttonFlag ? 'today' : 'daily'; - + if (!buttonFlag) return; const eventSource = createSSEConnection( - `http://223.130.151.42:3000/api/stocks/trade-history/${id}/${targetWord}-sse`, + `http://223.130.151.42:3000/api/stocks/trade-history/${id}/today-sse`, addData, ); From cb76a016f7a257480af6033f5ecc9b5b8e7eeff5 Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 14:03:19 +0900 Subject: [PATCH 04/14] =?UTF-8?q?=E2=9C=A8=20feat:=20Y=EA=B2=A9=EC=9E=90?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20#123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/utils/chart.ts | 52 ++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/FE/src/utils/chart.ts b/FE/src/utils/chart.ts index 2085c759..3cc5addf 100644 --- a/FE/src/utils/chart.ts +++ b/FE/src/utils/chart.ts @@ -94,12 +94,25 @@ export function drawCandleChart( const n = data.length; - const arr = data.map((d) => - Math.max(+d.stck_clpr, +d.stck_oprc, +d.stck_hgpr, +d.stck_lwpr), - ); + const values = data + .map((d) => [+d.stck_hgpr, +d.stck_lwpr, +d.stck_clpr, +d.stck_oprc]) + .flat(); + const yMax = Math.round(Math.max(...values) * (1 + weight)); + const yMin = Math.round(Math.min(...values) * (1 - weight)); - const yMax = Math.round(Math.max(...arr) * (1 + weight)); - const yMin = Math.round(Math.min(...arr) * (1 - weight)); + const labels = makeYLabels(yMax, yMin); + + ctx.beginPath(); + labels.forEach((label) => { + const yPos = + padding.top + height - ((label - yMin) / (yMax - yMin)) * height; + + ctx.moveTo(0, yPos); + ctx.lineTo(width + padding.left + padding.right, yPos); + }); + ctx.strokeStyle = '#D2DAE0'; + ctx.lineWidth = 2; + ctx.stroke(); data.forEach((e, i) => { ctx.beginPath(); @@ -236,7 +249,6 @@ export const drawUpperYLabel = ( const values = data .map((d) => [+d.stck_hgpr, +d.stck_lwpr, +d.stck_clpr, +d.stck_oprc]) .flat(); - const yMax = Math.round(Math.max(...values) * (1 + weight)); const yMin = Math.round(Math.min(...values) * (1 - weight)); @@ -247,30 +259,24 @@ export const drawUpperYLabel = ( height + padding.top + padding.bottom, ); - // 라벨 갯수 - const step = Math.ceil((yMax - yMin) / 3); - const labels = []; - for (let value = yMin; value <= yMax; value += step) { - labels.push(Math.round(value)); - } - if (!labels.includes(yMax)) { - labels.push(yMax); - } + const labels = makeYLabels(yMax, yMin); ctx.font = '24px sans-serif'; ctx.fillStyle = '#000'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; + ctx.beginPath(); labels.forEach((label) => { const yPos = padding.top + height - ((label - yMin) / (yMax - yMin)) * height; const formattedValue = label.toLocaleString(); + ctx.moveTo(0, yPos); + ctx.lineTo(padding.left, yPos); ctx.fillText(formattedValue, width / 2, yPos); }); - ctx.beginPath(); ctx.moveTo(padding.left, 0); ctx.lineTo(padding.left, height + padding.top + padding.bottom); ctx.strokeStyle = '#D2DAE0'; @@ -305,3 +311,17 @@ export const drawLowerYLabel = ( ctx.lineTo(padding.left, height + padding.top + 4); ctx.stroke(); }; + +const makeYLabels = (yMax: number, yMin: number) => { + const labels = []; + const rawTickInterval = Math.ceil((yMax - yMin) / 3); + const magnitude = 10 ** (String(rawTickInterval).length - 1); + const tickInterval = Math.floor(rawTickInterval / magnitude) * magnitude; + const startValue = Math.ceil(yMin / tickInterval) * tickInterval; + + for (let value = startValue; value <= yMax; value += tickInterval) { + labels.push(Math.round(value)); + } + + return labels; +}; From 6ff894a12ee3be6b69bc22c621dfe5d85ed1fc13 Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 15:32:44 +0900 Subject: [PATCH 05/14] =?UTF-8?q?=E2=9C=A8=20feat:=20=EA=B1=B0=EB=9E=98?= =?UTF-8?q?=EB=9F=89=20=EC=B0=A8=ED=8A=B8=20Y=EC=B6=95=20=EB=9D=BC?= =?UTF-8?q?=EB=B2=A8=20&=20=EA=B2=A9=EC=9E=90=EC=84=A0=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20#123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 27 +++-- FE/src/utils/chart.ts | 119 +++++++++++++++++------ 2 files changed, 104 insertions(+), 42 deletions(-) diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 44571401..63f772a9 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -58,9 +58,9 @@ export default function Chart({ code }: StocksDeatailChartProps) { const displayHeight = parent.clientHeight; const upperHeight = displayHeight * 0.5; - const lowerHeight = displayHeight * 0.3; - const chartWidth = displayWidth * 0.9; - const yAxisWidth = displayWidth * 0.1; + const lowerHeight = displayHeight * 0.25; + const chartWidth = displayWidth * 0.92; + const yAxisWidth = displayWidth * 0.08; // 차트 영역 설정 upperChartCanvas.width = chartWidth * 2; @@ -133,10 +133,8 @@ export default function Chart({ code }: StocksDeatailChartProps) { drawBarChart( LowerChartCtx, data, - 0, - 0, lowerChartWidth, - lowerChartHeight - padding.bottom, + lowerChartHeight - padding.top - padding.bottom, padding, ); @@ -151,6 +149,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { drawLowerYLabel( LowerYCtx, + data, lowerChartYCanvas.width - padding.left - padding.right, lowerChartYCanvas.height - padding.top - padding.bottom, padding, @@ -158,8 +157,8 @@ export default function Chart({ code }: StocksDeatailChartProps) { }, [timeCategory, data, isLoading]); return ( -
-
+
+

차트

-
+
{/* Upper 차트 영역 */}
From 37b7e514c3a1a878d57f0c52dce6c02b6f62e989 Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 21:25:21 +0900 Subject: [PATCH 13/14] =?UTF-8?q?=E2=9C=A8=20feat:=20resize=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=83=9D=EC=84=B1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/StocksDetail/Chart.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index b9cf63eb..83e3cb3d 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -177,7 +177,7 @@ export default function Chart({ code }: StocksDeatailChartProps) { }, [timeCategory, data, isLoading]); return ( -
+

차트

+
+
+
{/* Lower 차트 영역 */}
From 14acbace23890cc259697825c5f04050ec6c1e08 Mon Sep 17 00:00:00 2001 From: Seo San Date: Tue, 19 Nov 2024 22:08:19 +0900 Subject: [PATCH 14/14] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20&=20z-index=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20#123?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FE/src/components/Search/index.tsx | 6 ++---- FE/src/components/StocksDetail/Chart.tsx | 14 +++++++------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/FE/src/components/Search/index.tsx b/FE/src/components/Search/index.tsx index 0173c5b1..0fc866c2 100644 --- a/FE/src/components/Search/index.tsx +++ b/FE/src/components/Search/index.tsx @@ -45,12 +45,10 @@ export default function SearchModal() { <> toggleSearchModal()} />
diff --git a/FE/src/components/StocksDetail/Chart.tsx b/FE/src/components/StocksDetail/Chart.tsx index 83e3cb3d..e5a8d690 100644 --- a/FE/src/components/StocksDetail/Chart.tsx +++ b/FE/src/components/StocksDetail/Chart.tsx @@ -194,21 +194,21 @@ export default function Chart({ code }: StocksDeatailChartProps) {
{/* Upper 차트 영역 */} -
- - +
+ +
-
+
{/* Lower 차트 영역 */}
- - + +
{/* X축 영역 */}
- +