반응형
Grafana에서 Business Chart를 사용해서 누적형 그래프를 그리는 방법을 설명하겠습니다.
• 데이터 구조
legend | xvalue | yvalue |
A | 2074-01 | 138,938 |
B | 2074-01 | 31,065 |
C | 2074-01 | 9,509 |
D | 2074-01 | 22,073 |
A | 2074-02 | 121,677 |
B | 2074-02 | 31,899 |
C | 2074-02 | 12,903 |
D | 2074-02 | 21,503 |
• 그래프
• Script 코드
const dataMap = {}; // 시즌별 데이터 저장용
const xLabelsSet = new Set(); // x축 레벨 값들
let minYValue = Infinity; // y축 최소값 계산용
context.panel.data.series.forEach((series) => {
const legendField = series.fields.find(f => f.name.toLowerCase().includes("legend")); // 범례
const xvalueField = series.fields.find(f => f.name.toLowerCase().includes("xvalue")); // x축에 표시할 값
const yvalueField = series.fields.find(f => f.name.toLowerCase().includes("yvalue")); // y축에 표시할 값
if (!legendField || !xvalueField || !yvalueField) return;
const legends = legendField.values; // 범례 배열
const xvalues = xvalueField.values; // x축 값 배열
const values = yvalueField.values; // y축 값 배열
for (let i = 0; i < legends.length; i++) {
const legend = legends.get(i);
const xvalue = xvalues.get(i);
const yvalue = values.get(i);
xLabelsSet.add(xvalue);
if (!dataMap[legend]) {
dataMap[legend] = {};
}
dataMap[legend][xvalue] = yvalue;
// 최소값 갱신
if (typeof yvalue === 'number' && yvalue < minYValue) {
minYValue = yvalue;
}
}
});
// x축 정렬된 레벨 값들 (문자열 기준 정렬)
const xLabels = Array.from(xLabelsSet).sort((a, b) => a - b);
// 시리즈 생성
const series = Object.keys(dataMap).map(legend => {
const xValueMap = dataMap[legend];
const displayData = xLabels.map(xvalue => xValueMap[xvalue] ?? null);
// 최고값 찾기
let maxIndex = -1;
let maxValue = -Infinity;
displayData.forEach((v, i) => {
if (typeof v === 'number' && v > maxValue) {
maxValue = v;
maxIndex = i;
}
});
return {
name: legend,
type: "line",
stack: "total",
data: displayData,
symbolSize: 4, // 점의 크기
areaStyle: {
opacity: 0.3 // 투명도 조절 (0 ~ 1 사이 값)
},
};
});
return {
grid: {
bottom: "4.5%",
containLabel: true,
left: "3%",
right: "4%"
},
tooltip: {
trigger: "item",
axisPointer: {
type: 'cross',
},
formatter: function (params) {
// 숫자 포맷: 천단위 구분
const value = typeof params.data === 'number'
? params.data.toLocaleString('ko-KR')
: params.data;
return `
${params.marker}
<strong style="color:${params.color}">${params.seriesName}</strong>: ${value}
`;
}
},
legend: {
name: '시즌',
data: Object.keys(dataMap)
},
xAxis: { // x축 설정
name: '', // 축 이름 텍스트.
nameLocation: "middle", // "start", "middle", "end" 가능. 이름의 위치 지정.
nameGap: 25, // 축 이름과 축 간의 거리(px)
nameTextStyle: { // 폰트 크기, 두께 등 스타일 설정
fontSize: 14,
fontWeight: 'bold'
},
boundaryGap: false,
type: "category",
data: xLabels,
axisLabel: {
margin: 25, // 레이블과 축 간 거리
fontSize: 13 // ← 글씨 크기 설정 (예: 12px)
}
},
yAxis: { // y축 설정
name: '',
nameTextStyle: {
fontSize: 14,
fontWeight: 'bold'
},
type: "value",
min: (minYValue * 0.85).toFixed(0)
},
series
};
반응형