티스토리 뷰
* 기존에 개발된 사내 애플리케이션에서 X-Bar Chart와 R 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
- Total
- Today
- Yesterday
- JHipster
- CentOS
- maven
- 구동계
- 태그를 입력해 주세요.
- JavaScript
- Spring MVC 3
- Kendo UI
- graylog
- jsp
- DynamoDB
- 알뜰폰
- MySQL
- 로드 바이크
- Eclipse
- 자전거
- Tomcat
- 로드바이크
- jstl
- chrome
- Docker
- Spring Boot
- 평속
- kotlin
- bootstrap
- Kendo UI Web Grid
- java
- jpa
- node.js
- spring
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |