amCharts: 무료 차트 라이브러리

* 기존에 개발된 사내 애플리케이션에서 X-Bar ChartR Chart를 사용하는데 상용 Chart FX 7 Windows Forms 컴포넌트를 구입해서 사용하고 있다. 문제는 매번 설치시마다 라이센스가 꼬여서 이메일과 전화로 일주일이 걸려야 해결이 된다. 화딱지가 나서 JavaScript 상에서 무료 차트 라이브러리를 사용하여 구현하는 방법을 고민해봤다.


* X Bar Chart와 R Chart는 기본적으로 Single Line Chart이다. 여기에 공식1)에 의해 계산된 UCL, CL, LCL 값이 Guide로 출력되는 형태이다.


* 조사 결과 개발사의 링크가 차트에 출력되는 조건만 지키면 무료로 사용가능한 amCharts가 이러한 Guide 출력 기능을 가지고 있다. X-Bar Chart를 출력하는 코드를 아래와 같이 급조해서 구현해봤다.

<!DOCTYPE html>

<html>

    <body>

        <div id="xBarChart" style="width: 950px; height: 190px;"></div>

        <div id="rChart" style="width: 950px; height: 190px;"></div>

        <script src="js/sugar-1.3.9.min.js"></script>

        <script src="js/underscore-min.js"></script>

        <script src="js/amcharts/amcharts.js"></script>

        <script>

            // 먼저 관리도의 소스가 되는 군(Subgroup)의 집합을 임의로 생성한다.

            var subgroupList = [];

            var subgroup = {

                DATA1: 53,

                DATA2: 46,

                DATA3: 53,

                DATA4: 58,

                DATA5: 47,

                CPK: 0.646,

                SAMPLECOUNT: 5

            };

            subgroupList.push(subgroup);

            subgroup = {

                DATA1: 46,

                DATA2: 47,

                DATA3: 46,

                DATA4: 48,

                DATA5: 52,

                CPK: 1.762,

                SAMPLECOUNT: 5

            };

            subgroupList.push(subgroup);

            subgroup = {

                DATA1: 39,

                DATA2: 41,

                DATA3: 35,

                DATA4: 31,

                DATA5: 33,

                CPK: 2.022,

                SAMPLECOUNT: 5

            };

            subgroupList.push(subgroup);

            subgroup = {

                DATA1: 47,

                DATA2: 36,

                DATA3: 35,

                DATA4: 42,

                DATA5: 45,

                CPK: 1.246,

                SAMPLECOUNT: 5

            };

            subgroupList.push(subgroup);

            subgroup = {

                DATA1: 47,

                DATA2: 37,

                DATA3: 34,

                DATA4: 34,

                DATA5: 34,

                CPK: 1.407,

                SAMPLECOUNT: 5

            };

            subgroupList.push(subgroup);

            subgroup = {

                DATA1: 37,

                DATA2: 49,

                DATA3: 45,

                DATA4: 43,

                DATA5: 38,

                CPK: 1.242,

                SAMPLECOUNT: 5

            };

            subgroupList.push(subgroup);

            subgroup = {

                DATA1: 44,

                DATA2: 41,

                DATA3: 40,

                DATA4: 38,

                DATA5: 42,

                CPK: 2.975,

                SAMPLECOUNT: 5

            };

            subgroupList.push(subgroup);

            subgroup = {

                DATA1: 37,

                DATA2: 42,

                DATA3: 40,

                DATA4: 41,

                DATA5: 46,

                CPK: 2.014,

                SAMPLECOUNT: 5

            };

            subgroupList.push(subgroup);

            subgroup = {

                DATA1: 39,

                DATA2: 38,

                DATA3: 38,

                DATA4: 34,

                DATA5: 30,

                CPK: 2.226,

                SAMPLECOUNT: 5

            };

            subgroupList.push(subgroup);

            subgroup = {

                DATA1: 36,

                DATA2: 42,

                DATA3: 37,

                DATA4: 25,

                DATA5: 26,

                CPK: 1.251,

                SAMPLECOUNT: 5

            };

            subgroupList.push(subgroup);


            // Control Chart를 출력하는 오브젝트를 작성한다.

            var ControlChart = {

                getFactorsByN: function(n) {

                    var table = [{

                        n: 2,

                        a2: 1.88,

                        d3: 0,

                        d4: 3.267

                    }, {

                        n: 3,

                        a2: 1.023,

                        d3: 0,

                        d4: 2.282

                    }, {

                        n: 4,

                        a2: 0.729,

                        d3: 0,

                        d4: 2.282

                    }, {

                        n: 5,

                        a2: 0.577,

                        d3: 0,

                        d4: 2.115

                    }, {

                        n: 6,

                        a2: 0.419,

                        d3: 0.076,

                        d4: 1.924

                    }, {

                        n: 7,

                        a2: 0.419,

                        d3: 0.076,

                        d4: 1.924

                    }, {

                        n: 8,

                        a2: 0.373,

                        d3: 0.136,

                        d4: 1.864

                    }, {

                        n: 9,

                        a2: 0.337,

                        d3: 0.184,

                        d4: 1.816

                    }, {

                        n: 10,

                        a2: 0.308,

                        d3: 0.223,

                        d4: 1.777

                    }];

                    var factors = _.find(table, function(factors) {

                        return factors.n === n;

                    });

                    return factors;

                },

                getDataSourceBySubgroups: function(subgroups) {

                    if (subgroups.length === 0) {

                        return [];

                    }

                    var n = subgroups[0].SAMPLECOUNT;

                    var factors = this.getFactorsByN(n);

                    var dataSource = {

                        xBar: {

                            xBarList: [],

                            centerline: 0,

                            upperControlLimit: 0,

                            lowControlLimit: 0

                        },

                        r: {

                            rList: [],

                            centerline: 0,

                            upperControlLimit: 0,

                            lowControlLimit: 0

                        }

                    };

                    var prevCpk = subgroups[0].CPK;

                    _.each(subgroups, function(subgroup) {

                        var sumOfSample = 0;

                        if (!_.isUndefined(subgroup.DATA1)) {

                            sumOfSample += subgroup.DATA1;

                        }

                        if (!_.isUndefined(subgroup.DATA2)) {

                            sumOfSample += subgroup.DATA2;

                        }

                        if (!_.isUndefined(subgroup.DATA3)) {

                            sumOfSample += subgroup.DATA3;

                        }

                        if (!_.isUndefined(subgroup.DATA4)) {

                            sumOfSample += subgroup.DATA4;

                        }

                        if (!_.isUndefined(subgroup.DATA5)) {

                            sumOfSample += subgroup.DATA5;

                        }

                        if (!_.isUndefined(subgroup.DATA6)) {

                            sumOfSample += subgroup.DATA6;

                        }

                        if (!_.isUndefined(subgroup.DATA7)) {

                            sumOfSample += subgroup.DATA7;

                        }

                        if (!_.isUndefined(subgroup.DATA8)) {

                            sumOfSample += subgroup.DATA8;

                        }

                        if (!_.isUndefined(subgroup.DATA9)) {

                            sumOfSample += subgroup.DATA9;

                        }

                        if (!_.isUndefined(subgroup.DATA10)) {

                            sumOfSample += subgroup.DATA10;

                        }

                        var xBar = {};

                        xBar.cpk = subgroup.CPK;

                        xBar.xBar = sumOfSample / n;

                        // xBar.cpk < 1.67일 경우 LineColor를 red 색상으로 출력하기 위해 조건을 구분지어 dataSource에 저장한다.

                        if (xBar.cpk < 1.67) {

                            xBar.xBarUnder = xBar.xBar;

                            xBar.xBarBalloonUnder = xBar.xBar;

                            if (prevCpk >= 1.67) {

                                xBar.xBarAbove = xBar.xBar;

                            }

                        } else if (xBar.cpk >= 1.67) {

                            xBar.xBarAbove = xBar.xBar;

                            xBar.xBarBalloonAbove = xBar.xBar;

                            if (prevCpk < 1.67) {

                                xBar.xBarUnder = xBar.xBar;

                            }

                        }

                        prevCpk = xBar.cpk;

                        dataSource.xBar.xBarList.push(xBar);


                        var samples = [subgroup.DATA0, subgroup.DATA1, subgroup.DATA2, subgroup.DATA3, subgroup.DATA4, subgroup.DATA5, subgroup.DATA6, subgroup.DATA7, subgroup.DATA8, subgroup.DATA9, subgroup.DATA10];

                        var r = _.max(samples) - _.min(samples);

                        dataSource.r.rList.push({

                            r: r,

                            cpk: subgroup.CPK

                        });

                    });


                    // xBar의 Centerline을 획득한다. Sugar.js의 Array.average() 메써드를 사용했다.

                    dataSource.xBar.centerline = dataSource.xBar.xBarList.average(function(xBar) {

                        return xBar.xBar;

                    });


                    // xBar의 Upper Control Limit과 Low Control Limit을 계산하기 위한 rBar를 획득한다.

                    // Sugar.js의 Array.average() 메써드를 사용했다.

                    dataSource.r.centerline = dataSource.r.rList.average(function(r) {

                        return r.r;

                    });


                    // xBar의 Upper Control Limit과 Low Control Limit을 획득한다.

                    dataSource.xBar.upperControlLimit = dataSource.xBar.centerline + factors.a2 * dataSource.r.centerline;

                    dataSource.xBar.lowControlLimit = dataSource.xBar.centerline - factors.a2 * dataSource.r.centerline;


                    // r의 Upper Control Limit과 Low Control Limit을 획득한다.

                    dataSource.r.upperControlLimit = factors.d4 * dataSource.r.centerline;

                    dataSource.r.lowControlLimit = factors.d3 * dataSource.r.centerline;


                    return dataSource;

                },

                drawXBarChart: function(dataSource) {

                    if (dataSource.length === 0) {

                        return;

                    }

                    var chart;

                    AmCharts.ready(function() {

                        chart = new AmCharts.AmSerialChart();

                        chart.addTitle('X-Bar Chart', 12, 'black', 0.5, 1);

                        chart.marginTop = 0;

                        chart.marginRight = 10;

                        chart.dataProvider = dataSource.xBar.xBarList;

                        chart.categoryField = "cpk";


                        var categoryAxis = chart.categoryAxis;

                        categoryAxis.dashLength = 0.5;

                        categoryAxis.gridAlpha = 0.1;

                        categoryAxis.axisColor = "#0D8ECF";


                        var valueAxis = new AmCharts.ValueAxis();

                        valueAxis.axisColor = "#0D8ECF";

                        valueAxis.dashLength = 3;

                        chart.addValueAxis(valueAxis);


                        var guide = new AmCharts.Guide();

                        // Upper Control Limit를 소수점 3자리에서 반올림한다. Sugar.js의 Number.round() 메써드를 사용했다.

                        guide.value = (dataSource.xBar.upperControlLimit).round(3);

                        guide.lineColor = 'red';

                        guide.dashLength = 2;

                        guide.label = 'UCL: ' + guide.value;

                        guide.inside = true;

                        guide.lineAlpha = 1;

                        valueAxis.addGuide(guide);


                        guide = new AmCharts.Guide();

                        guide.value = (dataSource.xBar.centerline).round(3);

                        guide.lineColor = 'purple';

                        guide.dashLength = 2;

                        guide.label = 'CL: ' + guide.value;

                        guide.inside = true;

                        guide.lineAlpha = 1;

                        valueAxis.addGuide(guide);


                        guide = new AmCharts.Guide();

                        guide.value = (dataSource.xBar.lowControlLimit).round(3);

                        guide.lineColor = 'blue';

                        guide.dashLength = 2;

                        guide.label = 'LCL: ' + guide.value;

                        guide.inside = true;

                        guide.lineAlpha = 1;

                        valueAxis.addGuide(guide);


                        var graph = new AmCharts.AmGraph();

                        graph.title = 'X-Bar';

                        graph.type = 'line';

                        graph.valueField = 'xBarUnder';

                        graph.balloonText = '[[xBarBalloonUnder]]';

                        graph.lineColor = "#D1655D";

                        graph.lineThickness = 2;

                        graph.bullet = "round";

                        graph.bulletBorderThickness = 4;

                        graph.bulletSize = 8;

                        graph.connect = false;

                        chart.addGraph(graph);


                        graph = new AmCharts.AmGraph();

                        graph.title = "X-Bar";

                        graph.type = "line";

                        graph.valueField = "xBarAbove";

                        graph.balloonText = '[[xBarBalloonAbove]]';

                        graph.lineColor = "#637BB6";

                        graph.lineThickness = 2;

                        graph.bullet = "round";

                        graph.bulletBorderThickness = 4;

                        graph.bulletSize = 8;

                        graph.connect = false;

                        chart.addGraph(graph);


                        var chartCursor = new AmCharts.ChartCursor();

                        chartCursor.zoomable = false;

                        chartCursor.cursorPosition = "mouse";

                        chart.addChartCursor(chartCursor);

                        chart.write("xBarChart");

                    });

                },

                drawRChart: function(dataSource) {

                    if (dataSource.length === 0) {

                        return;

                    }

                    var chart;

                    AmCharts.ready(function() {

                        chart = new AmCharts.AmSerialChart();

                        chart.addTitle('R Chart', 12, 'black', 0.5, 1);

                        chart.marginTop = 0;

                        chart.marginRight = 10;

                        chart.dataProvider = dataSource.r.rList;

                        chart.categoryField = "cpk";


                        var categoryAxis = chart.categoryAxis;

                        categoryAxis.dashLength = 0.5;

                        categoryAxis.gridAlpha = 0.1;

                        categoryAxis.axisColor = "#FF9E01";


                        var valueAxis = new AmCharts.ValueAxis();

                        valueAxis.axisColor = "#FF9E01";

                        valueAxis.dashLength = 3;

                        chart.addValueAxis(valueAxis);


                        var guide = new AmCharts.Guide();

                        // Upper Control Limit를 소수점 3자리에서 반올림한다. Sugar.js의 Number.round() 메써드를 사용했다.

                        guide.value = (dataSource.r.upperControlLimit).round(3);

                        guide.lineColor = 'red';

                        guide.dashLength = 2;

                        guide.label = 'UCL: ' + guide.value;

                        guide.inside = true;

                        guide.lineAlpha = 1;

                        valueAxis.addGuide(guide);


                        guide = new AmCharts.Guide();

                        guide.value = (dataSource.r.centerline).round(3);

                        guide.lineColor = 'purple';

                        guide.dashLength = 2;

                        guide.label = 'CL: ' + guide.value;

                        guide.inside = true;

                        guide.lineAlpha = 1;

                        valueAxis.addGuide(guide);


                        guide = new AmCharts.Guide();

                        guide.value = (dataSource.r.lowControlLimit).round(3);

                        guide.lineColor = 'blue';

                        guide.dashLength = 4;

                        guide.label = 'LCL: ' + guide.value;

                        guide.inside = true;

                        guide.lineAlpha = 2;

                        valueAxis.addGuide(guide);


                        graph = new AmCharts.AmGraph();

                        graph.title = "R";

                        graph.type = "line";

                        graph.valueField = "r";

                        graph.lineColor = "#D1655D";

                        graph.lineThickness = 2;

                        graph.bullet = "round";

                        graph.bulletBorderThickness = 4;

                        graph.bulletSize = 8;

                        graph.connect = false;

                        chart.addGraph(graph);


                        var chartCursor = new AmCharts.ChartCursor();

                        chartCursor.zoomable = false;

                        chartCursor.cursorPosition = "mouse";

                        chart.addChartCursor(chartCursor);

                        chart.write("rChart");

                    });

                }

            };


            // 군의 집합을 파라메터로 전달하여 X-Bar Chart에 출력할 데이터 소스를 획득한다.

            var dataSource = ControlChart.getDataSourceBySubgroups(subgroupList);


            // 획득한 데이터 소스를 파라메터로 전달하여 X-Bar Chart를 출력한다.

            ControlChart.drawXBarChart(dataSource);


            // 획득한 데이터 소스를 파라메터로 전달하여 R Chart를 출력한다.

            ControlChart.drawRChart(dataSource);

        </script>

    </body>

</html>


아래와 같이 출력 결과를 확인할 수 있다.





1) X Bar Chart와 R Chart의 UCL, CL, LCL 계산은 아래 사이트를 참고한다.

http://faculty.northgeorgia.edu/kmelton/xbarrex.htm


저작자 표시 비영리 동일 조건 변경 허락
신고