"Fields":[{"Name":"PLANT_NAME","Caption":"공정이름","Width":150,"Height":120,"Sortable":true,"Filterable":false,"ShowSubTotalRow":false},{"Name":"LINE_NAME","Caption":"라인이름","Width":120,"Height":120, ... },{"Name":"DATE_FROM_TEXT", ... },// 데이터 필드도 전부 등록해야 DataRow에서 접근 가능
...
]
모든 DataSource 컬럼을 Fields에 등록해야cell.DataRow.GetValue("XXX")로 접근할 수 있습니다.
Width는 RowHeader 영역에서 해당 필드의 가로 너비. Height는 컬럼 헤더 세로 높이.
Sortable: 헤더 클릭 정렬 여부. Filterable: 필터 아이콘 표시 여부.
2.2 OptionData — 필드 매핑과 시간축 설정
"OptionData":{"HeaderFieldNames":"PLANT_NAME,LINE_NAME",// RowHeader로 표시할 필드 (좌측 고정 영역)"FromFieldName":"DATE_FROM_TEXT",// 셀 시작일 필드"ToFieldName":"DATE_TO_TEXT",// 셀 종료일 필드"ColorFieldName":"COLOR",// 셀 배경색 필드 (색상명 또는 #hex)"LabelFieldName":"DURATION",// 셀에 표시할 텍스트"ToolTipFieldName":"TOOLTIP",// 마우스 호버 툴팁"DateTimeFormat":"yyyy-MM-dd HH:mm:ss",// 데이터의 날짜 포맷 (파싱용)"MoveAbleKeyFieldName":"PLANT_NAME",// 이동 가능 범위 제한 키(동일 값끼리만 이동)"GroupFieldName":"GROUP_NAME",// 셀 선택 시 같이 선택될 필드(그룹 선택)"DisplayFromDate":"20170101000000",// 표시 기간 시작 (yyyyMMddHHmmss)"DisplayToDate":"20170530000000","AvaliablePeriodFromDate":"20170103000000",// 이동 가능 기간(밖으로 이동 불가)"AvaliablePeriodToDate":"20170531000000","MinorUnit":{"Unit":2,"Value":1,"Format":"{0:yyyy-MM-dd}"},// 최소 눈금(Hour=2)"MajorUnit":[// 복수 멀티 헤더{"Width":7,"Unit":6,"Value":1,"Format":"{0:QQ}","Label":["1Q","2Q","3Q","4Q"]},{"Width":7,"Unit":4,"Value":1,"Format":"{0:MMM}","Label":["Jan","Feb",...]},{"Width":7,"Unit":3,"Value":1,"Format":"{0:dd}","Label":[]},{"Width":7,"Unit":7,"Value":1,"Format":"ww","Label":[]}],"LineSchedule":{"LineStopList":[{"Interval":9,"Year":"2023","Month":"11","Day":"01"},// SpecialDay{"Interval":2,"DayOfWeek":0}// Weekly(일요일 정지)]},"GrandTotalLabel":["총계"],"UseSpecialDayMove":false,"RoundType":2// 0:버림 1:반올림 2:올림}
"OptionView":{"ColumnHeaderHeight":23,// 시간축 헤더 1행 높이"RowHeight":30,// 데이터 행 높이"CellWidth":5,// Minor Unit 1칸의 너비(픽셀)"LabelMargin":5,"BoxMargin":2,// 셀 상하좌우 여백"BoxLabelMargin":10,"LineHeight":5// 멀티라인 시 행간}
2.4 OptionStyle — BoxStyle 키 + 색상
"OptionStyle":{"ColumnHeaderBoxStyleKey":"BX8FFB5C2489BC4D0AAB7E393442ABB119","RowHeaderBoxStyleKey":"BXDD80F1DA2A524BDABB6E663CB6DD95D6","GuideAreaBoxStyleKey":"BXA56CA4F06973411B8F71600E18D5617C",// 드래그 중 가이드 영역"SubTotalHeaderBoxStyleKey":"BX1C85207526164A5F89B469BB2B68484D","SubTotalDataBoxStyleKey":"BXDE549298837943D79F81D950BF074E02","GrandTotalHeaderBoxStyleKeys":["BX5E6066E92DC4468C96C33F3695775AD7"],"GrandTotalDataBoxStyleKeys":[],"LineBrush":{"Color":{"R":170,"G":170,"B":172,"A":1}},"SelectedCellBrush":{"Color":{"R":102,"G":102,"B":102,"A":0.1}},"SaturdayFontColor":"#50d1ff","SundayFontColor":"#ff0000","SpecialDayBackColor":"#ebebf0","ShowGuideArea":true,"ShowDetailLine":false}
BoxStyle 키가 서버에 없으면 기본 색상이 사용됩니다 (최근 패치로 null 체크됨).
반드시 getScriptClass("BIMatrix.ScheduleGrid.ScheduleGrid")로 꺼내 사용합니다. AddIn 객체를 직접 사용하면 ScheduleGrid API에 접근할 수 없습니다.
3.2 데이터 바인딩 — 3 steps 패턴
const inquiry = function() {
Matrix.Execute("Data1", function(p) { // 또는 RunScriptif (p.Success == false) { Matrix.Alert(p.Message); return; }
let ds = p.DataSet;
grid.SetDataSet(ds); // 1) 데이터 주입// 조회 때마다 기간을 갱신해야 하면 여기서 설정// grid.Model.OptionData.SetDisplayPeriod("20170101000000","20170531000000");// grid.Model.OptionData.SetAvaliablePeriod(...);
grid.Calculate(false); // 2) 내부 레이아웃 재계산 (false: offset 유지)
grid.Update(); // 3) 화면 갱신
});
};
핵심: 데이터/옵션을 바꾼 뒤에는 반드시 Calculate(false) → Update() 순서로 호출합니다. Update()만 호출하면 시간축/셀 위치가 재계산되지 않습니다.
4. 행렬 방향 (TransposeType)
// 스크립트에서 변경
grid.Model.OptionData.TransposeType = 1; // 0:가로축(Right) 1:세로축(Bottom)// 또는 토글 버튼
grid.Transpose(); // Right ↔ Bottom 토글 후 재계산 필요
grid.Calculate(false);
grid.Update();
TransposeType
설명
예시 용도
0 (Right, 기본)
시간축이 가로, 행이 세로로 쌓임
장기간 프로젝트 일정 (분기별/월별)
1 (Bottom)
시간축이 세로, 열이 가로로 쌓임
일일 설비 가동 스케줄 (시간별)
5. ContextMenu 커스터마이징
grid.View.OnContextMenuOpenning에 함수를 할당하면 우클릭 시 호출됩니다. grid.ContextMenu.Clear()로 초기화 후 AddMenu("라벨", 콜백)으로 항목을 추가합니다.
grid.View.OnContextMenuOpenning = function() {
grid.ContextMenu.Clear();
// 선택된 셀이 있을 때만 메뉴 표시if (grid.Model.Selection.Cells.length > 0) {
grid.ContextMenu.AddMenu("Summary", function() {
if (grid.View.MenuCell) { // 우클릭한 셀let dataRow = grid.View.MenuCell.DataRow;
Matrix.getObject("lblPlanCode").Text = dataRow.GetValue("PLAN_CODE");
formPopup = Matrix.ShowWindow("Form2", 0, 0, 300, 400, true, true, "Summary", true, null, null, null);
}
});
// Lock 처리 — DataRow에 값을 쓰고 Update
grid.ContextMenu.AddMenu("Lock Selected Cells", function() {
for (let i = 0; i < grid.Model.Selection.Cells.length; i++) {
let cell = grid.Model.Selection.Cells[i];
cell.DataRow.SetValue("LOCK_YN", "Y");
cell.DataRow.SetValue("COLOR", "gray"); // 색상 즉시 반영
}
grid.Update();
});
grid.ContextMenu.AddMenu("Unlock Selected Cells", function() {
for (let i = 0; i < grid.Model.Selection.Cells.length; i++) {
let cell = grid.Model.Selection.Cells[i];
cell.DataRow.SetValue("LOCK_YN", "N");
cell.DataRow.SetValue("COLOR", cell.DataRow.GetValue("ORIGIN_COLOR")); // 원본 색 복원
}
grid.Update();
});
}
// 2개 이상 선택 시에만 표시if (grid.Model.Selection.Cells.length > 1) {
grid.ContextMenu.AddMenu("Merge", function() { ... });
}
};
grid.View.MenuCell: 우클릭 대상 단일 셀
grid.Model.Selection.Cells: 현재 선택된 셀 배열 (Shift/Ctrl 다중 선택)
메뉴를 숨기려면 AddMenu를 호출하지 않으면 됩니다 (조건부 분기).
6. 셀 조작 API
6.1 셀 분할 (SplitCell) — 수량 기반 분할 예시
// "btnPopupSplit": 총 수량 20개짜리 셀을 8개(앞) + 12개(뒤)로 나눔let total = grid.View.MenuCell.DataRow.GetValue("DURATION"); // 20let firstCellDuration = Number(Matrix.getObject("txtSplitCount").Value);
let secondCellDuration = total - firstCellDuration;
// 비율에 맞춘 너비 계산 (전체 셀 너비 × 앞 비율)let firstCellWidth = Math.round(
(grid.View.MenuCell.Width / cellWidth.Text) * (firstCellDuration / total)
);
let cells = grid.Model.SplitCell(grid.View.MenuCell, firstCellWidth); // Array<ScheduleCell>if (cells.length > 0) {
cells[0].DataRow.SetValue("DURATION", firstCellDuration);
cells[1].DataRow.SetValue("DURATION", secondCellDuration);
}
grid.Update();
constOnGanttChartCellDoubleClick = function(sender, args) {
if (args.Id == "GanttChart") {
let planCode = args.Cell.DataRow.GetValue("PLAN_CODE");
// DataGrid에서 해당 행 찾아 스크롤 + 선택let cnt = mainGrid.GetRowCount();
for (let i = 0; i < cnt; i++) {
let row = mainGrid.GetRow(i);
if (planCode == row.GetValue("PLAN_CODE")) {
mainGrid.ScrollTop = row.Bound.Top - mainGrid.ColumnHeaderHeight;
mainGrid.SelectRow(i);
break;
}
}
}
};
8. DataGrid 양방향 동기화 패턴
ScheduleGrid는 단독 사용뿐 아니라 DataGrid와 동일한 DataSet을 공유하는 구성이 흔합니다.
// 조회 시 — 한 DataSet을 양쪽에 바인딩let ds = p.DataSet;
grid.SetDataSet(ds, true); // 두 번째 인자 true: updateLayout (컬럼 헤더 유지)
mainGrid.SetDataSet(ds);
grid.Model.OptionData.SetDisplayPeriod(displayFromTo.Value, displayFromTo.Value2);
grid.Model.OptionData.SetAvaliablePeriod(movablePeriod.Value, movablePeriod.Value2);
grid.Calculate(false);
grid.Update();
// DataGrid에서 값 수정 → ScheduleGrid 반영 (OnValidate)constOnValidate = function(sender, args) {
if (args.Id == "MainGrid") {
grid.SetDataSet(mainGrid.DataSet);
grid.Calculate(false);
grid.Update();
}
};
// DataGrid 더블클릭 → 간트 차트의 해당 셀로 이동constOnCellDoubleClick = function(sender, args) {
if (args.Id == "MainGrid") {
let planCode = args.Cell.Row.GetValue("PLAN_CODE");
grid.View.FocusCell("PLAN_CODE", planCode);
}
};
// 간트 차트에서 셀 조작 → DataGrid에 반영
grid.Model.SplitCell(grid.View.MenuCell, firstCellWidth);
grid.Update();
mainGrid.SetDataSet(grid.Model.DataSet); // 결과 동기화
9. 기간/라인 스케줄 스크립트 API
9.1 기간 설정 (조회 버튼에서 동적 변경)
// "yyyyMMddHHmmss" 형식 문자열
grid.Model.OptionData.SetDisplayPeriod("20170101000000", "20170531000000");
grid.Model.OptionData.SetAvaliablePeriod("20170103000000", "20170531000000");
grid.Calculate(false);
grid.Update();
9.2 라인 정지 일정 — 휴무일/주말 블록
let ls = grid.Model.OptionData.LineSchedule;
ls.StopTimeClear();
ls.AddStopSpecialDay(2023, 11, 1); // 특정일
ls.AddStopTimeWeekly(0); // 매주 일요일
ls.AddStopTimeDaily(0, 8); // 매일 0~8시
ls.AddStopTimeMonthly(15); // 매월 15일
ls.AddStopTimeYearly(1, 1); // 매년 1/1
ls.AddStopTimeWeeklyBetween(0, 10, 18); // 일요일 10~18시
이후 grid.Calculate(false); grid.Update()를 호출해야 반영됩니다.
10. CRUD 저장
elseif (args.Id == "btnSave") {
if (grid.IsModified()) { // 수정 여부 체크Matrix.RunScript("MainGrid", "SaveGantt", function(p) {
if (p.Success == false) { Matrix.Alert(p.Message); return; }
Matrix.iMessage("저장되었습니다.");
inquiry(); // 저장 후 재조회
});
} else {
Matrix.Information("수정된 데이타가 없습니다.");
}
}
grid.IsModified(): DataSet의 RowState가 N/U/D 인 행이 하나라도 있으면 true
서버 스크립트 첫 인자(gridNames)에 DataGrid 이름을 넘기면 해당 그리드의 변경 데이터(req.getDataSet)가 함께 전달됩니다.