반응형

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
};
반응형
반응형

Grafana에서 Business Charts를 사용할 때 Tooltip에 표시되는 내용을 정렬하는 방법을 설명드리겠습니다.

 

• 적용 전 / 후의 모습입니다.

 

 

• tooltip 옵션의 코드를 아래와 같이 수정합니다.

tooltip: {
  trigger: "axis",
  formatter: function (params) {
    // 수치를 천 단위로 변환하는 함수
    const formatNumber = num => {
      if (num == null || isNaN(num)) return '-';
      return num.toLocaleString();  // 또는: String(num).replace(/\B(?=(\d{3})+(?!\d))/g, ",")
    };

    const sortedParams = params.slice().sort((a, b) => {
      const aVal = a.data != null ? a.data : -Infinity;
      const bVal = b.data != null ? b.data : -Infinity;
      return bVal - aVal;
    });

    let text = `${params[0].axisValue}<br/>`;
    sortedParams.forEach(param => {
      text += `
        <span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:${param.color};"></span>
        ${param.seriesName}: ${formatNumber(param.data)}<br/>
      `;
    });
    return text;
  }
}

 

반응형
반응형

Grafana에서 서드파티 차트중 Business Charts를 사용하는 방법을 알려드리겠습니다.

 

 

0. legend / xvalue / yvalue 컬럼으로 이루어진 데이터를 준비합니다.
1. Business Charts 를 선택 > Editor Mode를 Visual로 선택합니다.

 

2. 아래 JavaScript 코드를 Visual Editor 탭의 Function에 붙여넣어 줍니다.

    코드에 대한 설명은 주석을 참고하시거나 ChatGPT에 코드를 붙여넣은 후 프로프트로 코드 수정을 질문하시면 꽤 정확한 답변을 얻으실 수 있습니다.

const dataMap = {}; 					  // 전체 데이터
const xLabelsSet = new Set(); 	// X축 라벨값 집합

// 데이터 집합을 생성합니다.
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;
  const yValues = yValueField.values;

  for (let i = 0; i < legends.length; i++) {
    const legend = legends.get(i);
    const xValue = xValues.get(i);
    const yValue = yValues.get(i);
    xLabelsSet.add(xValue);
    if (!dataMap[legend]) {
      dataMap[legend] = {};
    }
    dataMap[legend][xValue] = yValue;
  }
});

// X축에 표시할 Unique 라벨 배열
const xLabels = Array.from(xLabelsSet).sort((a, b) => a - b);
// 시리즈 생성
const series = Object.keys(dataMap).map(legend => {
  const xValueMap = dataMap[legend];
  const yValueMap = xLabels.map(xValue => xValueMap[xValue] ?? null);
  return {
    name: legend,     // 범례에 속한 항목 이름
    type: "line",     // 차트 타입(bar, line)
    stack: "",
    data: yValueMap   // Y Value 데이터 배열
  };
});

return {
  grid: {
    top: "30%",
    bottom: "5%",  // 그래프 하단의 여백을 설정하는 효과가 있습니다.
    containLabel: true,
    left: "3%",
    right: "4%"
  },
  tooltip: {
    trigger: "axis"
  },
  legend: { // 범례 설정
    type: 'plain', // plain, scroll
    name: '모델',
    data: Object.keys(dataMap),
    orient: 'horizontal',      // 수평 정렬
    top: 'top',
    left: 'center',
    itemGap: 10,               // 범례 항목 간 간격
    padding: [10, 20],         // 범례 영역 안쪽 여백
    width: '80%',              // 너비 제한을 주면 줄바꿈 발생 가능
    textStyle: {
      fontSize: 14
    }
  },
  xAxis: {  // x축 설정
    name: '',           // 축 이름 텍스트.
    nameLocation: "middle", // "start", "middle", "end" 가능. 이름의 위치 지정.
    nameGap: 30,            // 축 이름과 축 간의 거리(px)
    nameTextStyle: {        // 폰트 크기, 두께 등 스타일 설정
      fontSize: 20,
      fontWeight: 'bold'
    },
    boundaryGap: true,
    type: "category",
    data: xLabels,
    axisLabel: {
      fontSize: 14,
      rotate: 60,  // 라벨을 45도 기울임
      interval: 0 // 모든 항목 출력 (스크롤 지원에 필수)
    }
  },
  yAxis: {  // y축 설정
    name: '판매량',
    nameTextStyle: {
      fontSize: 14,
      fontWeight: 'bold'
    },
    type: "value",
    interval: 100000  // 10 단위 간격 설정
  },
  series,
  dataZoom: [
    {
      type: 'slider',
      show: true,
      xAxisIndex: 0,
      start: 0,
      end: xLabels.length > 20 ? 50 : 100
    }
  ],
  graphic: [
    {
      type: 'text',
      left: 'left',
      bottom: '20',   // 'bottom'은 grid 바깥 기준이므로, 그래프 아래 적당한 픽셀 위치로 설정
      left: '70',   // 좌측 여백을 설정합니다.
    }
  ]
};

 

3. 그래프의 출력 결과는 아래와 같습니다.

반응형

+ Recent posts