SpreadJS는 셀 안에 진행 상황을 직접 표시하는 수식 기반 스파크라인인 PROGRESSSPARKLINE을 지원합니다. 전체 차트 개체를 삽입하지 않고 워크시트, 대시보드 또는 KPI 테이블에 간결한 상태 표시기를 배치할 때 유용합니다.
이 데모에서는 동일한 함수로 여러 워크시트에 선형, 분할, 호 및 방사형 진행률 스타일을 렌더링하여 개요 샘플과 업무 맥락이 포함된 워크시트 레이아웃을 비교할 수 있습니다.
수식 구문:
value: 스파크라인에 표시할 진행률 값입니다. 이 데모에서는 여러 모드를 일관되게 비교할 수 있도록 샘플 값을 고정합니다.
mode: 시각적 스타일을 선택합니다. 이 샘플은 막대, 분할, 점, 호 및 방사형 변형을 포함한 0..14를 다룹니다.
barColor: 완료된 진행 영역의 선택적 전경색입니다. 기본값은 Accent 1입니다. Accent 1 같은 테마 색 토큰과 #4F81BD 같은 CSS 색 값을 모두 지원합니다.
bandColor: 진행 영역 뒤의 선택적 트랙 색입니다. 기본값은 Accent 1 80입니다. Accent 1 80 같은 테마 색 토큰과 #D9E2F3 같은 CSS 색 값을 모두 지원합니다.
showValue: 값 텍스트 표시 여부를 제어하는 선택적 부울 값입니다. 기본값은 TRUE입니다.
일반적인 사용 방법:
mode를 변경하여 선형, 분할, 호 및 방사형 스타일을 전환합니다.
barColor와 bandColor를 워크시트 테마에 맞추거나 사용자 지정 CSS 색으로 상태 수준을 강조합니다.
셀 안의 텍스트 없이 간결하게 표시하려면 showValue를 FALSE로 설정합니다.
예제 코드:
이 데모의 개요 워크시트에는 지원되는 모든 모드와 해당 수식 및 미리 보기가 표시됩니다. 추가 워크시트에서는 진행률 스파크라인을 간결한 비즈니스 스타일 레이아웃에 사용하는 방법을 보여 줍니다.
var spreadNS = window.GC && GC.Spread && GC.Spread.Sheets;
var HA = spreadNS.HorizontalAlign;
var VA = spreadNS.VerticalAlign;
var CF = spreadNS.ConditionalFormatting;
window.onload = function () {
initializeDemo();
};
function initializeDemo() {
var spread = new spreadNS.Workbook(document.getElementById("ss"), { sheetCount: 4 });
spread.suspendPaint();
configureOverviewSheet(spread.getSheet(0));
configureReleaseReadinessSheet(spread.getSheet(1));
configureServiceHealthSheet(spread.getSheet(2));
configureLearningProgressSheet(spread.getSheet(3));
spread.setActiveSheetIndex(0);
spread.resumePaint();
}
function configureSheetBase(sheet, name, rowCount, columnCount) {
sheet.name(name);
sheet.setRowCount(rowCount);
sheet.setColumnCount(columnCount);
}
function styleHeaderRow(sheet, row, columnCount, backColor, foreColor) {
sheet.getRange(row, 0, 1, columnCount)
.font("bold 13px Segoe UI")
.backColor(backColor || "#f3f6fb")
.foreColor(foreColor || "#314155")
.hAlign(HA.center)
.vAlign(VA.center);
}
function applyRangeStyle(range, options) {
if (options.font) {
range.font(options.font);
}
if (options.backColor !== undefined) {
range.backColor(options.backColor);
}
if (options.foreColor !== undefined) {
range.foreColor(options.foreColor);
}
if (options.hAlign !== undefined) {
range.hAlign(options.hAlign);
}
if (options.vAlign !== undefined) {
range.vAlign(options.vAlign);
}
if (options.wordWrap !== undefined) {
range.wordWrap(options.wordWrap);
}
if (options.formatter !== undefined) {
range.formatter(options.formatter);
}
}
function setHeaderValues(sheet, row, values) {
sheet.setArray(row, 0, [values]);
}
function setColumnWidths(sheet, widths) {
for (var col = 0; col < widths.length; col++) {
sheet.setColumnWidth(col, widths[col]);
}
}
function setUniformColumnWidths(sheet, startCol, colCount, width) {
for (var col = startCol; col < startCol + colCount; col++) {
sheet.setColumnWidth(col, width);
}
}
function setRowHeights(sheet, heights) {
for (var row in heights) {
if (Object.prototype.hasOwnProperty.call(heights, row)) {
sheet.setRowHeight(Number(row), heights[row]);
}
}
}
function registerCustomNames(sheet, names) {
for (var name in names) {
if (Object.prototype.hasOwnProperty.call(names, name) && !sheet.getCustomName(name)) {
sheet.addCustomName(name, names[name]);
}
}
}
function createForeColorStyle(color) {
var style = new spreadNS.Style();
style.foreColor = color;
return style;
}
function applySpecificTextColorRules(sheet, ranges, ruleColors) {
for (var text in ruleColors) {
if (Object.prototype.hasOwnProperty.call(ruleColors, text)) {
sheet.conditionalFormats.addSpecificTextRule(
CF.TextComparisonOperators.contains,
text,
createForeColorStyle(ruleColors[text]),
ranges
);
}
}
}
function toColumnMatrix(items, valueSelector) {
return items.map(function (item) {
return [valueSelector(item)];
});
}
function buildServiceHealthFormula(currentRef, targetRef, warningThreshold) {
return '=IF(' + currentRef + '>=' + targetRef + ',"Healthy",IF(' + currentRef + '>=' + warningThreshold + ',"Warning","Critical"))';
}
function registerServiceHealthNames(sheet) {
registerCustomNames(sheet, {
HealthyColor: '="#16a34a"',
WarningColor: '="#d97706"',
CriticalColor: '="#dc2626"',
HealthyBandColor: '="#dcfce7"',
WarningBandColor: '="#fef3c7"',
CriticalBandColor: '="#fee2e2"'
});
}
function buildServiceHealthSparklineFormula(currentRef, targetRef, warningThreshold) {
return '=PROGRESSSPARKLINE(' + currentRef + ',9,IF(' + currentRef + '>=' + targetRef + ',HealthyColor,IF(' + currentRef + '>=' + warningThreshold + ',WarningColor,CriticalColor)),IF(' + currentRef + '>=' + targetRef + ',HealthyBandColor,IF(' + currentRef + '>=' + warningThreshold + ',WarningBandColor,CriticalBandColor)),TRUE)';
}
function buildLiteralFormula(value, mode, barColor, bandColor, showValue) {
var formula = "=PROGRESSSPARKLINE(" + value + "," + mode;
if (barColor !== undefined || bandColor !== undefined || showValue !== undefined) {
if (barColor !== undefined && barColor !== null) {
formula += ',"' + escapeFormulaText(barColor) + '"';
} else {
formula += ",";
}
}
if (bandColor !== undefined || showValue !== undefined) {
if (bandColor !== undefined && bandColor !== null) {
formula += ',"' + escapeFormulaText(bandColor) + '"';
} else {
formula += ",";
}
}
if (showValue !== undefined) {
formula += "," + (showValue ? "TRUE" : "FALSE");
}
formula += ")";
return formula;
}
function buildCellFormula(valueColumnIndex, row, mode, barColor, bandColor, showValue) {
var valueRef = columnName(valueColumnIndex) + (row + 1);
return buildLiteralFormula(valueRef, mode, barColor, bandColor, showValue);
}
function escapeFormulaText(value) {
return String(value).replace(/"/g, '""');
}
function columnName(index) {
return String.fromCharCode("A".charCodeAt(0) + index);
}
function createBorder(color) {
return new spreadNS.LineBorder(color, spreadNS.LineStyle.thin);
}
function createReleaseReadinessSource() {
return RELEASE_READINESS_ROWS.map(function (item) {
return {
workstream: item.workstream,
owner: item.owner,
milestone: item.milestone,
targetDate: item.targetDate
};
});
}
function registerReleaseReadinessNames(sheet) {
registerCustomNames(sheet, {
AlertColor: '="#dc2626"',
AtRiskColor: '="#d97706"',
OnTrackColor: '="#4E7BEF"',
TrackColor: '="#DCE7FB"'
});
}
function buildReleaseReadinessRiskFormula(value) {
return '=LET(progress,' + value + ',IF(progress<0.3,"Alert",IF(progress<0.5,"At Risk","On Track")))';
}
function buildReleaseReadinessSparklineFormula(value) {
return '=LET(progress,' + value + ',PROGRESSSPARKLINE(progress,0,IF(progress<0.3,AlertColor,IF(progress<0.5,AtRiskColor,OnTrackColor)),TrackColor,TRUE))';
}
var OVERVIEW_MODE_DEFINITIONS = [
{ mode: 0, label: "막대", value: 0.28, rowHeight: 50, barColor: "#e74c3c", bandColor: "#fce4e4" },
{ mode: 1, label: "줄무늬", value: 0.46, rowHeight: 50, barColor: "#27ae60", bandColor: null },
{ mode: 2, label: "분할", value: 0.71, rowHeight: 50, barColor: "Accent 4", bandColor: "Accent 4 80" },
{ mode: 3, label: "방사형", value: 0.89, rowHeight: 60, barColor: "#2980b9", bandColor: "#d4e6f1" },
{ mode: 4, label: "호", value: 0.55, rowHeight: 60, barColor: "#8e44ad", bandColor: "#e8d5f5" },
{ mode: 5, label: "해칭", value: 0.50, rowHeight: 50, barColor: "#c0392b", bandColor: "#e6b0aa" },
{ mode: 6, label: "버블", value: 0.40, rowHeight: 50, barColor: "#16a085", bandColor: null },
{ mode: 7, label: "점선", value: 0.33, rowHeight: 50, barColor: "#d35400", bandColor: "#edbb99" },
{ mode: 8, label: "확인란", value: 0.70, rowHeight: 50, barColor: "#1abc9c", bandColor: "#d5dbdb" },
{ mode: 9, label: "방사형-간격", value: 0.37, rowHeight: 60, barColor: "#c0392b", bandColor: "#bdc3c7" },
{ mode: 10, label: "방사형-굵게", value: 0.37, rowHeight: 60, barColor: "#e67e22", bandColor: "#d5d5d5" },
{ mode: 11, label: "방사형-눈금", value: 0.37, rowHeight: 60, barColor: "#27ae60", bandColor: "#d5d5d5" },
{ mode: 12, label: "방사형-대시", value: 0.37, rowHeight: 60, barColor: "#2c3e9b", bandColor: "#d5d5d5" },
{ mode: 13, label: "방사형-점", value: 0.37, rowHeight: 60, barColor: "#8e44ad", bandColor: "#d5d5d5" },
{ mode: 14, label: "방사형-노브", value: 0.37, rowHeight: 60, barColor: "#e74c3c", bandColor: "#d5d5d5" }
];
function configureOverviewSheet(sheet) {
var rows = { header: 0, dataStart: 1 };
var cols = { mode: 0, value: 1, sparkline: 2, formula: 3 };
var rowCount = OVERVIEW_MODE_DEFINITIONS.length;
var totalRows = rows.dataStart + rowCount;
var border = createBorder("#d7dee7");
sheet.suspendPaint();
try {
configureSheetBase(sheet, "모든 진행률 모드", totalRows, 4);
setColumnWidths(sheet, [140, 84, 300, 440]);
styleHeaderRow(sheet, rows.header, 4, "#f3f6fb", "#314155");
setHeaderValues(sheet, rows.header, ["모드", "값", "스파크라인", "수식"]);
sheet.setRowHeight(rows.header, 30);
applyRangeStyle(sheet.getRange(rows.dataStart, cols.mode, rowCount, 1), {
font: "13px Segoe UI",
foreColor: "#1f2937",
hAlign: HA.center,
vAlign: VA.center
});
applyRangeStyle(sheet.getRange(rows.dataStart, cols.value, rowCount, 1), {
font: "13px Segoe UI",
foreColor: "#1f2937",
hAlign: HA.center,
vAlign: VA.center,
formatter: "0%"
});
applyRangeStyle(sheet.getRange(rows.dataStart, cols.sparkline, rowCount, 1), {
backColor: "#ffffff",
hAlign: HA.center,
vAlign: VA.center
});
applyRangeStyle(sheet.getRange(rows.dataStart, cols.formula, rowCount, 1), {
font: "13px Consolas",
foreColor: "#44556b",
backColor: "#fbfcfd",
wordWrap: true,
hAlign: HA.left,
vAlign: VA.center
});
OVERVIEW_MODE_DEFINITIONS.forEach(function (item, index) {
var row = rows.dataStart + index;
var formula = buildCellFormula(cols.value, row, item.mode, item.barColor, item.bandColor, true);
sheet.setRowHeight(row, item.rowHeight + 8);
sheet.setValue(row, cols.mode, item.mode + " " + item.label);
sheet.setValue(row, cols.value, item.value);
sheet.setFormula(row, cols.sparkline, formula);
sheet.setValue(row, cols.formula, formula);
});
sheet.getRange(rows.header, 0, OVERVIEW_MODE_DEFINITIONS.length + 1, 4).setBorder(border, { all: true });
sheet.getRange(0, 0, totalRows, 4).vAlign(VA.center);
} finally {
sheet.resumePaint();
}
}
var RELEASE_READINESS_ROWS = [
{ workstream: "결제", owner: "Iris", milestone: "세금 및 합계 동등성", targetDate: "Apr 08", value: 0.82 },
{ workstream: "ID 관리", owner: "Nolan", milestone: "SSO 콜백 강화", targetDate: "Apr 11", value: 0.64 },
{ workstream: "마이그레이션", owner: "Mia", milestone: "레거시 통합 문서 가져오기", targetDate: "Apr 14", value: 0.58 },
{ workstream: "품질 보증", owner: "Owen", milestone: "회귀 테스트 승인", targetDate: "Apr 15", value: 0.75 },
{ workstream: "알림", owner: "Ethan", milestone: "템플릿 승인", targetDate: "Apr 18", value: 0.43 },
{ workstream: "청구", owner: "Chloe", milestone: "게이트웨이 인증", targetDate: "Apr 19", value: 0.27 },
{ workstream: "감사", owner: "Liam", milestone: "보존 정책 검토", targetDate: "Apr 22", value: 0.56 },
{ workstream: "접근성", owner: "Sofia", milestone: "VPAT 증빙 패키지", targetDate: "Apr 24", value: 0.91 }
];
function configureReleaseReadinessSheet(sheet) {
var headerRow = 0;
var dataStartRow = 1;
var riskColumn = 4;
var readinessColumn = 5;
var rowCount = RELEASE_READINESS_ROWS.length;
var totalRows = dataStartRow + rowCount;
var border = createBorder("#d7dee7");
var source = { rows: createReleaseReadinessSource() };
sheet.suspendPaint();
try {
configureSheetBase(sheet, "릴리스 준비 상태", totalRows, 6);
registerReleaseReadinessNames(sheet);
setColumnWidths(sheet, [240, 124, 250, 116, 108, 290]);
var table = sheet.tables.add("releaseReadinessTable", headerRow, 0, totalRows, 6, spreadNS.Tables.TableThemes.light2);
var tableColumns = [
new spreadNS.Tables.TableColumn(1, "workstream", "작업 영역"),
new spreadNS.Tables.TableColumn(2, "owner", "담당자"),
new spreadNS.Tables.TableColumn(3, "milestone", "마일스톤"),
new spreadNS.Tables.TableColumn(4, "targetDate", "목표 날짜"),
new spreadNS.Tables.TableColumn(5, null, "위험"),
new spreadNS.Tables.TableColumn(6, null, "준비 상태")
];
table.autoGenerateColumns(false);
table.bind(tableColumns, "rows", source);
table.style(spreadNS.Tables.TableThemes["none"]);
table.highlightFirstColumn(false);
table.highlightLastColumn(false);
styleHeaderRow(sheet, headerRow, 6, "#f3f6fb", "#314155");
sheet.setRowHeight(headerRow, 30);
applyRangeStyle(sheet.getRange(dataStartRow, 0, rowCount, 1), {
font: "bold 13px Segoe UI",
foreColor: "#0f172a",
hAlign: HA.left,
vAlign: VA.center
});
applyRangeStyle(sheet.getRange(dataStartRow, 1, rowCount, 2), {
font: "13px Segoe UI",
foreColor: "#334155",
hAlign: HA.left,
vAlign: VA.center
});
applyRangeStyle(sheet.getRange(dataStartRow, 3, rowCount, 1), {
font: "13px Segoe UI",
foreColor: "#334155",
hAlign: HA.center,
vAlign: VA.center
});
applyRangeStyle(sheet.getRange(dataStartRow, riskColumn, rowCount, 1), {
font: "bold 13px Segoe UI",
backColor: "#ffffff",
hAlign: HA.center,
vAlign: VA.center
});
applyRangeStyle(sheet.getRange(dataStartRow, readinessColumn, rowCount, 1), {
backColor: "#ffffff",
hAlign: HA.center,
vAlign: VA.center
});
var riskRange = [new spreadNS.Range(dataStartRow, riskColumn, rowCount, 1)];
applySpecificTextColorRules(sheet, riskRange, {
"On Track": "#475569",
"At Risk": "#d97706",
Alert: "#dc2626"
});
RELEASE_READINESS_ROWS.forEach(function (item, index) {
var row = dataStartRow + index;
var riskFormula = buildReleaseReadinessRiskFormula(item.value);
var readinessFormula = buildReleaseReadinessSparklineFormula(item.value);
sheet.setRowHeight(row, 44);
sheet.setFormula(row, riskColumn, riskFormula);
sheet.setFormula(row, readinessColumn, readinessFormula);
});
sheet.getRange(headerRow, 0, RELEASE_READINESS_ROWS.length + 1, 6).setBorder(border, { all: true });
} finally {
sheet.resumePaint();
}
}
var SERVICE_HEALTH_CARDS = [
{ title: "API 가용성", subtitle: "7일 가동 시간", value: 0.99, target: 0.99, warningThreshold: 0.97, note: "자동 복구된 인시던트 2건" },
{ title: "인시던트 SLA", subtitle: "P1 응답 시간", value: 0.86, target: 0.95, warningThreshold: 0.8, note: "22개 알림 중 3개가 기준 초과" },
{ title: "오류 예산", subtitle: "월간 잔여량", value: 0.64, target: 0.75, warningThreshold: 0.55, note: "릴리스 중단 불필요" },
{ title: "CPU 여유 용량", subtitle: "최대 클러스터 예비 용량", value: 0.72, target: 0.7, warningThreshold: 0.6, note: "스케일 아웃 가능 시간 유지" },
{ title: "패치 준수율", subtitle: "패치 적용 서버", value: 0.91, target: 0.9, warningThreshold: 0.8, note: "에지 노드 2대 재부팅 대기" },
{ title: "큐 처리", subtitle: "백로그 처리율", value: 0.58, target: 0.7, warningThreshold: 0.5, note: "청구 큐 적체 지속" },
{ title: "백업 완료율", subtitle: "야간 작업", value: 0.97, target: 0.95, warningThreshold: 0.85, note: "아카이브 재시도 1건 대기" },
{ title: "인증서 갱신", subtitle: "30일 이내 만료", value: 0.42, target: 0.8, warningThreshold: 0.6, note: "게이트웨이 3대 미갱신" }
];
function configureServiceHealthSheet(sheet) {
var headerRow = 0;
var metricRows = {
subtitle: 1,
current: 2,
target: 3,
health: 4,
note: 5,
progress: 6
};
var serviceStartColumn = 1;
var cardCount = SERVICE_HEALTH_CARDS.length;
var totalRows = 7;
var totalColumns = serviceStartColumn + cardCount;
var border = createBorder("#d8e0ea");
sheet.suspendPaint();
try {
configureSheetBase(sheet, "서비스 상태", totalRows, totalColumns);
registerServiceHealthNames(sheet);
sheet.options.gridline.showVerticalGridline = true;
sheet.options.gridline.showHorizontalGridline = true;
sheet.setColumnWidth(0, 165);
setUniformColumnWidths(sheet, serviceStartColumn, cardCount, 210);
styleHeaderRow(sheet, headerRow, totalColumns, "#f3f6fb", "#314155");
sheet.getRange(headerRow, 0, 1, totalColumns).font("bold 14px Segoe UI");
sheet.addSpan(headerRow, 0, 2, 1);
sheet.setValue(headerRow, 0, "지표");
setRowHeights(sheet, {
0: 38,
1: 30,
2: 38,
3: 34,
4: 34,
5: 54,
6: 116
});
sheet.getCell(headerRow, 0)
.hAlign(HA.center)
.vAlign(VA.center);
applyRangeStyle(sheet.getRange(metricRows.subtitle, serviceStartColumn, 1, cardCount), {
backColor: "#f8fafc",
foreColor: "#64748b",
font: "12px Segoe UI",
wordWrap: true,
hAlign: HA.center,
vAlign: VA.center
});
sheet.setArray(metricRows.current, 0, [["현재"], ["목표"], ["상태"], ["참고"], ["진행률"]]);
[metricRows.current, metricRows.target, metricRows.health, metricRows.note, metricRows.progress].forEach(function (row) {
sheet.getCell(row, 0)
.font("bold 14px Segoe UI")
.foreColor("#475569")
.backColor("#f8fafc")
.hAlign(HA.center)
.vAlign(VA.center);
});
applyRangeStyle(sheet.getRange(metricRows.current, serviceStartColumn, 1, cardCount), {
font: "bold 13px Segoe UI",
foreColor: "#0f172a",
hAlign: HA.center,
vAlign: VA.center,
formatter: "0%"
});
applyRangeStyle(sheet.getRange(metricRows.target, serviceStartColumn, 1, cardCount), {
font: "13px Segoe UI",
foreColor: "#475569",
hAlign: HA.center,
vAlign: VA.center,
formatter: "0%"
});
applyRangeStyle(sheet.getRange(metricRows.health, serviceStartColumn, 1, cardCount), {
font: "bold 13px Segoe UI",
backColor: "#ffffff",
hAlign: HA.center,
vAlign: VA.center
});
applyRangeStyle(sheet.getRange(metricRows.note, serviceStartColumn, 1, cardCount), {
font: "13px Segoe UI",
foreColor: "#64748b",
backColor: "#ffffff",
wordWrap: true,
hAlign: HA.left,
vAlign: VA.center
});
applyRangeStyle(sheet.getRange(metricRows.progress, serviceStartColumn, 1, cardCount), {
backColor: "#ffffff",
hAlign: HA.center,
vAlign: VA.center
});
var healthRange = [new spreadNS.Range(metricRows.health, serviceStartColumn, 1, cardCount)];
applySpecificTextColorRules(sheet, healthRange, {
Healthy: "#15803d",
Warning: "#d97706",
Critical: "#dc2626"
});
sheet.setArray(headerRow, serviceStartColumn, [SERVICE_HEALTH_CARDS.map(function (card) { return card.title; })]);
sheet.setArray(metricRows.subtitle, serviceStartColumn, [SERVICE_HEALTH_CARDS.map(function (card) { return card.subtitle; })]);
sheet.setArray(metricRows.current, serviceStartColumn, [SERVICE_HEALTH_CARDS.map(function (card) { return card.value; })]);
sheet.setArray(metricRows.target, serviceStartColumn, [SERVICE_HEALTH_CARDS.map(function (card) { return card.target; })]);
sheet.setArray(metricRows.note, serviceStartColumn, [SERVICE_HEALTH_CARDS.map(function (card) { return card.note; })]);
for (var index = 0; index < SERVICE_HEALTH_CARDS.length; index++) {
var card = SERVICE_HEALTH_CARDS[index];
var col = serviceStartColumn + index;
var currentValueRef = columnName(col) + (metricRows.current + 1);
var targetValueRef = columnName(col) + (metricRows.target + 1);
var healthFormula = buildServiceHealthFormula(currentValueRef, targetValueRef, card.warningThreshold);
var sparklineFormula = buildServiceHealthSparklineFormula(currentValueRef, targetValueRef, card.warningThreshold);
sheet.setFormula(metricRows.health, col, healthFormula);
sheet.setFormula(metricRows.progress, col, sparklineFormula);
}
sheet.getRange(headerRow, 0, metricRows.progress - headerRow + 1, totalColumns).setBorder(border, { all: true });
} finally {
sheet.resumePaint();
}
}
var TRAINING_ROWS = [
{ learner: "Emma Johnson", program: "관리자 인증", modulesDone: 11, totalModules: 12, nextStep: "최종 시험 예약" },
{ learner: "Liam Smith", program: "API 통합 과정", modulesDone: 8, totalModules: 12, nextStep: "캡스톤 실습 완료" },
{ learner: "Olivia Davis", program: "템플릿 디자인 부트캠프", modulesDone: 6, totalModules: 10, nextStep: "7번 모듈 복습" },
{ learner: "Noah Wilson", program: "마이그레이션 전문가", modulesDone: 4, totalModules: 9, nextStep: "코칭 세션 참석" },
{ learner: "Ava Martinez", program: "대시보드 작성", modulesDone: 12, totalModules: 12, nextStep: "인증 배지 제출" },
{ learner: "Ethan Brown", program: "Designer 고급 사용자", modulesDone: 7, totalModules: 8, nextStep: "실무 검토 일정 예약" },
{ learner: "Sophia Lee", program: "통합 문서 자동화", modulesDone: 5, totalModules: 11, nextStep: "실습 통합 문서 완료" },
{ learner: "Mason Taylor", program: "보고서 작성", modulesDone: 9, totalModules: 14, nextStep: "동료 평가 준비" },
{ learner: "Isabella Wang", program: "거버넌스 및 감사", modulesDone: 10, totalModules: 10, nextStep: "완료 기록 보관" },
{ learner: "Lucas Chen", program: "계산 엔진", modulesDone: 3, totalModules: 8, nextStep: "핵심 수식 보충 학습" }
];
var LEARNING_PROGRESS_FORMATTER = '=PROGRESSSPARKLINE(@,2,"#6B7C93","#E7EDF5",TRUE)';
function configureLearningProgressSheet(sheet) {
var headerRow = 0;
var dataStartRow = 1;
var rowCount = TRAINING_ROWS.length;
var totalRows = dataStartRow + rowCount;
var border = createBorder("#d7dee7");
sheet.suspendPaint();
try {
configureSheetBase(sheet, "학습 진행률", totalRows, 6);
setColumnWidths(sheet, [170, 210, 110, 110, 320, 210]);
styleHeaderRow(sheet, headerRow, 6, "#f3f6fb", "#314155");
setHeaderValues(sheet, headerRow, ["학습자", "과정", "완료 모듈", "전체 모듈", "완료율", "다음 단계"]);
sheet.setRowHeight(headerRow, 30);
applyRangeStyle(sheet.getRange(dataStartRow, 0, rowCount, 1), {
font: "bold 13px Segoe UI",
foreColor: "#0f172a",
hAlign: HA.left,
vAlign: VA.center
});
applyRangeStyle(sheet.getRange(dataStartRow, 1, rowCount, 1), {
font: "12px Segoe UI",
foreColor: "#475569",
hAlign: HA.left,
vAlign: VA.center
});
applyRangeStyle(sheet.getRange(dataStartRow, 2, rowCount, 2), {
font: "12px Consolas",
foreColor: "#334155",
hAlign: HA.right,
vAlign: VA.center
});
applyRangeStyle(sheet.getRange(dataStartRow, 4, rowCount, 1), {
backColor: "#ffffff",
hAlign: HA.center,
vAlign: VA.center,
formatter: LEARNING_PROGRESS_FORMATTER
});
applyRangeStyle(sheet.getRange(dataStartRow, 5, rowCount, 1), {
font: "12px Segoe UI",
foreColor: "#475569",
backColor: "#ffffff",
hAlign: HA.left,
vAlign: VA.center
});
sheet.setArray(dataStartRow, 0, toColumnMatrix(TRAINING_ROWS, function (item) { return item.learner; }));
sheet.setArray(dataStartRow, 1, toColumnMatrix(TRAINING_ROWS, function (item) { return item.program; }));
sheet.setArray(dataStartRow, 2, TRAINING_ROWS.map(function (item) { return [item.modulesDone, item.totalModules]; }));
sheet.setArray(dataStartRow, 5, toColumnMatrix(TRAINING_ROWS, function (item) { return item.nextStep; }));
TRAINING_ROWS.forEach(function (item, index) {
var row = dataStartRow + index;
var modulesDoneRef = columnName(2) + (row + 1);
var totalModulesRef = columnName(3) + (row + 1);
var formula = '=IF(' + totalModulesRef + '=0,0,' + modulesDoneRef + '/' + totalModulesRef + ')';
sheet.setRowHeight(row, 44);
sheet.setFormula(row, 4, formula);
});
sheet.getRange(headerRow, 0, TRAINING_ROWS.length + 1, 6).setBorder(border, { all: true });
} finally {
sheet.resumePaint();
}
}
<!doctype html>
<html style="height: 100%; font-size: 14px;">
<head>
<meta name="spreadjs culture" content="ko-kr"/>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="$DEMOROOT$/ko/purejs/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css">
<script src="$DEMOROOT$/ko/purejs/node_modules/@mescius/spread-sheets/dist/gc.spread.sheets.all.min.js" type="text/javascript"></script>
<script src="$DEMOROOT$/ko/purejs/node_modules/@mescius/spread-sheets-resources-ko/dist/gc.spread.sheets.resources.ko.min.js" type="text/javascript"></script>
<script src="$DEMOROOT$/spread/source/js/license.js" type="text/javascript"></script>
<script src="app.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<div id="ss" class="sample-spreadsheets" aria-live="polite"></div>
<noscript>
<div class="load-error">JavaScript is required to load this demo.</div>
</noscript>
</body>
</html>
html,
body {
height: 100%;
margin: 0;
}
body {
position: absolute;
inset: 0;
background: #ffffff;
}
.sample-spreadsheets {
width: 100%;
height: 100%;
overflow: hidden;
background: #ffffff;
}