진행률 스파크라인

진행률 스파크라인 데모는 개요 워크시트와 여러 비즈니스 스타일 워크시트 레이아웃에서 PROGRESSSPARKLINE으로 진행 상황을 간결한 셀에 요약하는 방법을 보여 줍니다.

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; }