본문 바로가기
지금, 개발하기/개발꿀팁

Echarts] Echats로 피라미드 차트 만들기

by Seaco :) 2023. 7. 25.

1. ECharts 라이브러리

 

📌 Echarts란?

:Echarts는 데이터 시각화를 지원하는 강력한 오픈 소스 자바스크립트 라이브러리입니다😉
저는 많은 자료들을 시각화해서 유저들에게 보여줄 때 Echarts라이브러리를 요긴하게 사용하였는데요. 이 라이브러리는 선 그래프, 막대 그래프, 원 그래프, 산점도, 히스토그램 등 다양한 차트를 지원해줄 뿐만아니라 사용자 커스텀으로도 차트를 변경할 수도 있어서 원하는 차트는 무엇이든 만들 수 있습니다. 

Echarts는 기본적으로 JSON 형식의 데이터를 입력으로 받아 시각화를 수행하기때문에 사용하기도 쉽고, API문서도 잘되어있어서 원하는 스타일들을 쉽게 찾을 수도 있고, 웹상에서 바로바로 테스트를 해볼수도 있습니다. 

 

📌 Echarts 예시

https://echarts.apache.org/en/index.html

 

2. 피라미드 차트 구현하기

Echarts에는 정말 다양한 차트가 있어서 웬만한 차트는 라이브러리의 예시 안에서 만들 수 있습니다.
그런데 아주 가~끔은 원하는 그래프가 없을 때도 있습니다 ㅎㅎ 제가 구현하고자하는 피라미드그래프가 그랬는데요. 


🚩목표 : 유입·유출량을 그래프

저는 아래와 같은 모양의 유입·유출량을 그래프를 만들고 싶었습니다. 아래 그래프는 y축에서 0점을 기준으로 위로 올라가거나 아래로 내려가면 값이 커지는 그래프입니다.  y축이 위아래 모두 양수로 커지는 그래프를 만들어서 초록색의 유입량과 보라색의 유출량을 비교하는 그래프를 구현하는게 목표였습니다.

 

😱 한계점 : nagitive value 그래프

그런데 불행히도 제가 원하는 식의 그래프는 Echarts에 없었습니다. 그나마 제가 원하는 것과 가장 비슷했던 Echarts의 그래프는 nagative value 그래프 였는데요. 그렇지만 nagative 그래프는 말그대로 음수값을 지원하는 그래프라서요. 원하는 축의 한쪽이 양수(+)값이면 반대쪽은 음수(-)값을 가지는 그래프라 제가 사용하기가 적절치 않았습니다.

 

💡 해결방법

: 처음에는 네가티브를 그래프를 어떻게 바꿔서 구현을 해볼까생각했는데요. 그렇게 되면 연결되어있는 모든 값들도 다 바꿔주어야하는 상황이되고, 한 개가 꼬이면 나머지 데이터도 꼬일 것같았습니다.
그래서 저는 그래프를 두개 만들어서 붙이기로 했습니다🧐 하나의 그래프는 일반 barChart 형태로 만들고, 다른 하나는 뒤집어서 붙어주기로 하였습니다. 그리고나서 두 그래프가 legend와 tooltip을 공유할 수 있도록 연결해보기로 하였습니다.

 

1) 두 개의 그래프 연결하기

① grid
: Echarts에서 두 개 이상의 그래프를 연결하여 그릴 때는 grid를 사용합니다.  grid는 차트 영역의 크기와 위치를 지정하는 데 사용되며, 각각의 그래프가 배치될 위치를 조정할 수 있습니다.

grid : [
        {
            left: 60,
            right: 50,
            height: "35%"
        },
        {
            left: 60,
            right: 50,
            top: "50%",
            height: "35%"
        }
    ],

 

② xAxis
: gridIndex를 사용하여 여러 개의 grid를 생성하고 각각의 grid에 해당하는 xAxis를 설정할 수 있습니다. 이렇게 함으로써 각 그래프가 서로 다른 xAxis 설정을 가지게 됩니다. 저는 첫번째 그래프에 대한 설정은 xAxis의 첫번째 요소에 쓰고, 두번째 그래프에 대한 설정은 xAxis의 두번재 요소에 넣어주었는데요. 단, 두번째 그래프라는 걸 알려주기 위해서 gridIndex를 1로 설정해주었습니다.
참고로, barChart를 만들때는 bounday Gap을 true로 해줘야 막대가 그래프 밖으로 삐져나오지 않습니다. 

  xAxis: [
        {
            type: "category",
            boundaryGap: true,
            axisLine: {
                onZero: false,
                show: false
            },
            data: ["00","01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23"]
        },
        {
            gridIndex: 1,
            type": "category,
            boundaryGap: true,
            axisLine: {
                onZero: true,
                lineStyle: {
                    color: "#8b959a50"
                }
            },

            data: ["00","01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23"]
            ],
            position: "bottom"
        }
    ],

 

③ yAxis
: xAxis와 마찬가지로 gridIndex를 사용하여 여러 개의 grid를 생성하고 각각의 grid에 해당하는 yAxis를 설정할 수 있습니다. 두번째 그래프는 뒤집어야하기때문에 inverse를 true로 설정하였습니다. 

yAxis: [
        {
            type: "value",
            max: 500,
        },
        {
            gridIndex: 1,
            type: "value",
            inverse: true,
            max: 500,
        }
    ],

 

③ Series
: Series에 데이터를 넣을 때는 xAxisIndex, yAxisIndex를 지정해서 넣어주시면 됩니다.

 series: [
        {
            name: "유입량",
            type: "bar",
            symbolSize: 8,
            data: [300, 24, 20, 28, 20, 25, 30, 26, 28, 31, 16, 26, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        },
        {
            name: "유출량",
            type: "bar",
            xAxisIndex: 1,
            yAxisIndex: 1,
            symbolSize: 8,
            data: [ 25, 214, 70, 128, 120, 25, 130, 26, 28, 31,16, 126,23, 188,0, 200,100, 0,30,0,240, 180,70,330
            ]
        }
    ],

 

④ axisPointer
: axisPointer는 axisPointer의 link 속성을 사용하면 두 개 이상의 그래프의 tooltip을 연결할 수 있습니다. tooltip은 차트 위의 요소에 마우를 가리키면 해당 요소와 관련된 정보를 표시해주는 도구 인데요. 아래 코드처럼 link 배열 xAxisIndex: "all"을 설정하면, 제가 그린  모든그래프의 xAxis를 서로 연결하고 동기화해줍니다. 따라서 마우스가 한 그래프의 xAxis 위에서 움직이면 다른 모든 그래프들의 xAxis에 대해서도 해당 위치의 정보를 보여주는 도구팁이 동시에 활성화됩니다.

axisPointer: {
        link: [
            {
                xAxisIndex: "all"
            }
        ]
    }

 

2. 최종 코드 
짜잔!
아래는 제가 만든 피라미드 차트의 모든 옵션입니다. 저는 스타일링때문에 myExcelDown, 스타일 옵션, 등이 들어가서 코드가 조금 긴데요. 위에서 말씀드린 핵심포인트 위주로 봐주시길 바랍니다. 😉

{
    backgroundColor: "#191a19",
    title: {
        text: "유입·유출량",
        x: "55%",
        y: "2%",
        textAlign: "center",
        textStyle: {
            color: "#f8f9fa",
            fontSize: "22"
        },
        subtextStyle": {
            color: "#white",
            fontSize: "16"
        }
    },
    tooltip: {
        trigger: "axis",
        axisPointer: {
            animation: false,
            lineStyle: {
                color: "#8b959a50",
                type: "solid"
            }
        }
    },
    legend: {
		data": [
            유입량,
            유출량
        ],
        textStyle: {
            color: "#f0ebd8"
        },
        bottom: 5
    },
    toolbox: {
        feature: {
            myExcelDown: {
                show: true,
                title: "Excel Export",
                icon: "sample.png",
                iconStyle: {
                    borderColor: "#36ac68"
                }
            },
            restore: {
                show: false,
                title: "ReStore",
                iconStyle: {
                    borderColor: "#36ac68"
                }
            },
            saveAsImage: {
                show: true,
                title: "SAVE",
                iconStyle: {
                    borderColor: "#36ac68"
                }
            }
        },
        iconStyle: {
            emphasis: {
                borderColor: "#36ac68",
                textStyle: {
                    color: "#36ac68"
                }
            }
        }
    },
	grid: [
        {
            left: 60,
            right: 50,
            height: "35%"
        },
        {
            left: 60,
            right: 50,
            top: "50%",
            height: "35%"
        }
    ],
    xAxis: [
        {
            type: "category",
            boundaryGap: true,
            axisLine: {
                onZero: false,
                show: false
            },
            axisLabel: {
                color: "#f0ebd8",
                formatter: "{value}시",
                textStyle: {
                    color: "#f0ebd8"
                },
                show: false
            },
            axisTick: {
                show: false
            },
            splitLine: {
                show: false
            },
            splitArea: {
                show: false
            },
            data: ["00","01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23"]
        },
        {
            gridIndex: 1,
            type": "category,
            boundaryGap: true,
            axisLine: {
                onZero: true,
                lineStyle: {
                    color: "#8b959a50"
                }
            },
            axisLabel: {
                color: "#f0ebd8",
                formatter: "{value}시",
                textStyle: {
                    "color: "#f0ebd8"
                }
            },
            axisTick: {
                show: false
            },
            splitLine: {
                show: false
            },
            splitArea: {
                show: false
            },
            data: ["00","01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23"]
            ],
            position: "bottom"
        }
    ],
    yAxis: [
        {
            type: "value",
            max: 500,
            axisLine: {
                lineStyle: {
                    color: "#8b959a50"
                }
            },
            axisLabel: {
                color: "#f0ebd8"
            },
            splitLine: {
                show: true,
                lineStyle: {
                    color: "#8b959a50"
                }
            },
            splitArea: {
                show: false
            },
            nameTextStyle: {
                color: "rgb(248, 249, 250, 0.5)"
            },
            nameGap: 25
        },
        {
            gridIndex: 1,
            type: "value",
            inverse: true,
            max: 500,
            axisLine: {
                lineStyle: {
                    color: "#8b959a50"
                }
            },
            axisLabel: {
                color: "#f0ebd8"
            },
            splitLine: {
                show: true,
                lineStyle: {
                    color: "#8b959a50"
                }
            },
            splitArea: {
                show: false
            },
            nameTextStyle: {
                color: "rgb(248, 249, 250, 0.5)"
            },
            nameGap: 25
        }
    ],
    dataset: {
        source: []
    },
    dataZoom: [
        {
            type: "inside",
            minSpan: 30
        }
    ],
    series: [
        {
            name: "유입량",
            type: "bar",
            symbolSize: 8,
            data: [300, 24, 20, 28, 20, 25, 30, 26, 28, 31, 16, 26, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        },
        {
            name: "유출량",
            type: "bar",
            xAxisIndex: 1,
            yAxisIndex: 1,
            symbolSize: 8,
            data: [ 25, 214, 70, 128, 120, 25, 130, 26, 28, 31,16, 126,23, 188,0, 200,100, 0,30,0,240, 180,70,330
            ]
        }
    ],
    color: [
        "#36ac68"
    ],
    axisPointer: {
        link: [
            {
                xAxisIndex: "all"
            }
        ]
    }
}