Git Code 버전 : 0.0.6-SNAPSHOT
주식데이터를 켄들차트로 그려보겠다. Apache ECharts에서는 많은 형식의 그래프를 지원하며 예제도 잘되어있다.
https://echarts.apache.org/en/index.html
Apache ECharts
ECharts: A Declarative Framework for Rapid Construction of Web-based Visualization You are welcomed to cite the following paper whenever you use ECharts in your R&D projects, products, research papers, technical reports, news reports, books, presentations,
echarts.apache.org
라이브러리 포함하는 법은 간단하다. 아래와 같이 echart CDN을 HTML에 포함해 주면 된다.
<!-- ECharts -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
코드는 아래와 같이 작성한다.
- stock.html
더보기
<!-- 상단 입력 메뉴 코드-->
<div class="row">
<div class="mt-5">
<div class="md-12"></div>
<div id="myChart" style="height: 600px"></div>
</div>
</div>
<!-- 하단 DataTable 표시 코드-->
{ 중 략 }
<script type="text/javascript">
// { 중 략 }
function getStockPriceInfo(table) {
if ($("#itmsNm").val() === "") {
alert("종목명을 입력해주세요!");
return;
}
let jsonData = {
itmsNm: $("#itmsNm").val(),
numOfRows: $("#numOfRows").val(),
pageNo: 1,
};
$.ajax({
url: "/stock/getStockPriceInfo",
type: "GET",
data: jsonData,
success: function (items) {
table.clear().draw();
// items 배열을 순회하며 HTML 문자열 생성
items.forEach(function (item) {
table.row
.add([
item.basDt,
item.itmsNm,
item.clpr,
item.vs,
item.mkp,
item.hipr,
item.lopr,
item.mrktTotAmt,
])
.draw();
});
// 주식정보 가져온 것을 차트 그리는 함수를 호출한다.
drawCandleChart(items); // items를 사용하여 차트를 다시 그립니다.
$("#itemTable").DataTable();
},
error: function (xhr, status, error) {
alert("데이터를 가져오는 데 실패했습니다.");
},
});
}
// { 중 략 }
// 하단 코드를 추가한다.
function drawCandleChart(stockData) {
const upColor = "#FF0000";
const downColor = "#0000FF";
// 10일 주식 데이터 샘플 (날짜, 시가, 종가, 저가, 고가, 거래량)
let chartDom = $("#myChart")[0];
let myChart = echarts.init(chartDom, null, {
renderer: "canvas",
useDirtyRecy: false,
});
let data = splitData(stockData);
let getDataLen = data.categoryData.length;
myChart.setOption(
(option = {
animation: false,
legend: {
bottom: 10,
left: "center",
data: [$("#itmsNm").val(), "MA5", "MA10", "MA20", "MA30"],
},
tooltip: {
trigger: "axis",
axisPointer: { type: "cross" },
borderWidth: 1,
borderColor: "#ccc",
padding: 10,
textStyle: { color: "#000" },
position: function (pos, params, el, elRect, size) {
const obj = { top: 10 };
if (pos[0] < size.viewSize[0] / 2) {
obj["left"] = 30;
} else {
obj["right"] = 30;
}
return obj;
},
},
axisPointer: {
link: [{ xAxisIndex: "all" }],
label: { backgroundColor: "#777" },
},
toolbox: {
feature: {
dataZoom: { yAxisIndex: false },
brush: { type: ["lineX", "clear"] },
},
},
brush: {
xAxisIndex: "all",
brushLink: "all",
outOfBrush: { colorAlpha: 0.1 },
},
visualMap: {
show: false,
seriesIndex: 5,
dimension: 2,
pieces: [
{
value: 1,
color: downColor,
},
{
value: -1,
color: upColor,
},
],
},
grid: [
{
left: "10%",
right: "8%",
height: "50%",
},
{
left: "10%",
right: "8%",
top: "63%",
height: "16%",
},
],
xAxis: [
{
type: "category",
data: data.categoryData,
boundaryGap: false,
axisLine: { onZero: false },
splitLine: { show: false },
min: "dataMin",
max: "dataMax",
axisPointer: { z: 100 },
},
{
type: "category",
gridIndex: 1,
data: data.categoryData,
boundaryGap: false,
axisLine: { onZero: false },
axisTick: { show: false },
splitLine: { show: false },
axisLabel: { show: false },
min: "dataMin",
max: "dataMax",
},
],
yAxis: [
{
scale: true,
splitArea: { show: true },
},
{
scale: true,
gridIndex: 1,
splitNumber: 2,
axisLabel: { show: false },
axisLine: { show: false },
axisTick: { show: false },
splitLine: { show: false },
},
],
dataZoom: [
{
type: "inside",
xAxisIndex: [0, 1],
start: 50,
end: 100,
},
{
show: true,
xAxisIndex: [0, 1],
type: "slider",
top: "85%",
start: 50,
end: 100,
},
],
series: [
{
name: $("#itmsNm").val(),
type: "candlestick",
data: data.values,
itemStyle: {
color: upColor,
color0: downColor,
borderColor: undefined,
borderColor0: undefined,
},
},
{
name: "MA5",
type: "line",
data: calculateMA(5, data),
smooth: true,
lineStyle: {
opacity: 0.5,
},
},
{
name: "MA10",
type: "line",
data: calculateMA(10, data),
smooth: true,
lineStyle: {
opacity: 0.5,
},
},
{
name: "MA20",
type: "line",
data: calculateMA(20, data),
smooth: true,
lineStyle: {
opacity: 0.5,
},
},
{
name: "MA30",
type: "line",
data: calculateMA(30, data),
smooth: true,
lineStyle: {
opacity: 0.5,
},
},
{
name: "Volume",
type: "bar",
xAxisIndex: 1,
yAxisIndex: 1,
data: data.volumes,
},
],
}),
true
);
/*
myChart.dispatchAction({
type: "brush",
areas: [
{
brushType: "lineX",
coordRange: [data.categoryData[getDataLen - 2], data.categoryData[getDataLen - 1]],
xAxisIndex: 0,
},
],
});*/
}
// 캔들 데이터 추출 함수
function splitData(items) {
let categoryData = [];
let values = [];
let volumes = [];
items.sort(arrOrder("basDt"));
for (let i = 0; i < items.length; i++) {
categoryData.push(items[i].basDt);
values.push([
Number(items[i].mkp),
Number(items[i].clpr),
Number(items[i].lopr),
Number(items[i].hipr),
Number(items[i].mrktTotAmt),
]);
volumes.push([i, items[i].mrktTotAmt, Number(items[i].mkp) > Number(items[i].clpr) ? 1 : -1]); // 거래량 데이터 추가
}
return {
categoryData: categoryData,
values: values,
volumes: volumes,
};
}
function arrOrder(key) {
return (a, b) => a[key] - b[key];
}
// 이동 평균선 계산 함수
function calculateMA(dayCount, data) {
let result = new Array(data.values.length).fill("-");
let sum = 0;
// 첫 번째 `dayCount` 값의 초기 합계를 계산합니다.
for (let i = 0; i < dayCount; i++) {
sum += data.values[i][1]; // 고가 데이터 사용
}
// 첫 번째 MA 값을 저장합니다.
result[dayCount - 1] = (sum / dayCount).toFixed(1);
// 슬라이딩 윈도우를 사용하여 나머지 MA 값을 계산합니다.
for (let i = dayCount; i < data.values.length; i++) {
sum += data.values[i][1]; // 새로운 값을 더합니다.
sum -= data.values[i - dayCount][1]; // 슬라이드 아웃되는 값을 뺍니다.
result[i] = (sum / dayCount).toFixed(1);
}
return result;
}
</script>
켄들차트 이외 이동 평균선 함수까지 같이 포함된 코드이다. 수행해 보면 아래와 같이 표시된다.