스파크라인 규칙

스파크라인 조건부 서식 규칙은 스파크라인 차트를 셀 안에 직접 렌더링합니다. 원형, 꺾은선형, 막대형, 불릿, 가로막대형, 승패, 롤리팝 분산 등 다양한 스파크라인 유형을 지원합니다.

SpreadJS는 조건부 서식의 일부로 SparklineRule을 제공합니다. 이 규칙은 조건부 서식을 기반으로 셀 안에 스파크라인 차트를 렌더링합니다. addSparklineRule API를 사용하여 스파크라인 규칙을 만들 수 있습니다. 이 규칙은 기본 제공 식별자 $CF_RANGE$(규칙의 셀 범위를 참조) 및 @(현재 셀의 인덱스 또는 값을 나타냄)를 지원합니다. 개별 Sparkline 규칙의 구성 가능한 옵션은 GC.Spread.Sheets.Sparklines.ISparklineOptions를 참조하세요. 예: 또한 Sparkline 구성 옵션은 매개 변수 배열을 지원합니다. 매개 변수 순서는 Sparkline 함수 설명에 정의된 매개 변수 순서와 일치해야 합니다.
var spreadNS = GC.Spread.Sheets; window.onload = function () { var spread = new spreadNS.Workbook(document.getElementById('ss'), { sheetCount: 4 }); spread.suspendPaint(); initSpread(spread); initRuleControlPanel(spread); initPanelToggle(spread); spread.resumePaint(); }; function initSpread(spread) { initSheet1_LollipopVariance(spread); initSheet2_Column(spread); initSheet3_Pie(spread); initSheet4_Bullet(spread); spread.setActiveSheetIndex(0); } /* ================================================================ Helpers ================================================================ */ function colToLetter(col) { var s = ''; col++; while (col > 0) { col--; s = String.fromCharCode(65 + (col % 26)) + s; col = Math.floor(col / 26); } return s; } function absRange(row, col, rowCount, colCount) { var r1 = row + 1, r2 = row + rowCount; var c1 = colToLetter(col), c2 = colToLetter(col + colCount - 1); return '$' + c1 + '$' + r1 + ':$' + c2 + '$' + r2; } // Relative-row range: column is absolute ($C), row is relative (4) — shifts per cell function relRowRange(row, col, rowCount, colCount) { var r1 = row + 1, r2 = row + rowCount; var c1 = colToLetter(col), c2 = colToLetter(col + colCount - 1); return '$' + c1 + r1 + ':$' + c2 + r2; } function relRowCell(row, col) { return '$' + colToLetter(col) + (row + 1); } function makeStyle(opts) { var s = new spreadNS.Style(); if (opts.font) s.font = opts.font; if (opts.bg) s.backColor = opts.bg; if (opts.fg) s.foreColor = opts.fg; if (opts.hAlign !== undefined) s.hAlign = opts.hAlign; if (opts.vAlign !== undefined) s.vAlign = opts.vAlign; if (opts.formatter) s.formatter = opts.formatter; if (opts.wordWrap) s.wordWrap = true; return s; } /* ================================================================ Sheet 1 — Lollipop Variance (two sparkline columns: % and absolute) ================================================================ */ function initSheet1_LollipopVariance(spread) { var sheet = spread.getSheet(0); sheet.name('Lollipop Variance'); sheet.options.sheetTabColor = '#6fa300'; sheet.suspendPaint(); sheet.setColumnCount(8); sheet.setRowCount(12); var cfs = sheet.conditionalFormats; var i, rule; var c = 0; var titleStyle = makeStyle({ font: 'bold 14px Calibri', fg: '#1a1a1a', vAlign: 1 }); var headerStyle = makeStyle({ font: 'bold 11px Calibri', bg: '#6fa300', fg: '#FFFFFF', hAlign: 1, vAlign: 1 }); var labelStyle = makeStyle({ bg: '#e6f2cc', font: '11px Calibri', vAlign: 1 }); var dollarStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1, formatter: '$#,##0' }); var colorStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1 }); var thinBorder = new spreadNS.LineBorder('#a3c760', spreadNS.LineStyle.thin); // A:Dept B:Planned C:Actual D:Variance(%) E:Variance(Abs) F:PosColor G:NegColor var headers = ['Department', 'Planned ($K)', 'Actual ($K)', 'Variance (%)', 'Variance (Abs)', 'Positive Color', 'Negative Color']; var colWidths = [140, 100, 100, 180, 180, 110, 110]; // Title var r = 1; sheet.addSpan(r, c, 1, headers.length); sheet.setValue(r, c, 'Budget Variance Analysis'); sheet.setStyle(r, c, titleStyle); sheet.setRowHeight(r, 30); // Headers r = 2; for (i = 0; i < headers.length; i++) { sheet.setValue(r, c + i, headers[i]); sheet.setStyle(r, c + i, headerStyle); sheet.setColumnWidth(c + i, colWidths[i]); } sheet.setRowHeight(r, 30); // Data: [dept, planned, actual, posColor, negColor] var data = [ ['R&D', 850, 760, '#6fa300', '#c8a000'], ['Marketing', 420, 500, '#6fa300', '#c8a000'], ['Sales', 380, 330, '#4a7a00', '#c44d2b'], ['Customer Support', 260, 305, '#4a7a00', '#c44d2b'], ['Infrastructure', 520, 475, '#6fa300', '#c8a000'], ['HR & Training', 180, 210, '#6fa300', '#c8a000'], ['Legal', 150, 128, '#4a7a00', '#c44d2b'], ['General Admin', 200, 245, '#4a7a00', '#c44d2b'] ]; var dataFirstRow = r + 1; for (i = 0; i < data.length; i++) { var row = dataFirstRow + i; sheet.setValue(row, c, data[i][0]); sheet.setStyle(row, c, labelStyle); sheet.setValue(row, c + 1, data[i][1]); sheet.setStyle(row, c + 1, dollarStyle); sheet.setValue(row, c + 2, data[i][2]); sheet.setStyle(row, c + 2, dollarStyle); // c+3, c+4 are sparkline columns (skip) sheet.setValue(row, c + 5, data[i][3]); sheet.setStyle(row, c + 5, colorStyle); sheet.setValue(row, c + 6, data[i][4]); sheet.setStyle(row, c + 6, colorStyle); sheet.setRowHeight(row, 42); } // Borders sheet.getRange(r, c, data.length + 1, headers.length).setBorder(thinBorder, { all: true }); var plannedRef = absRange(dataFirstRow, c + 1, data.length, 1); var actualRef = absRange(dataFirstRow, c + 2, data.length, 1); // Rule 1: Percentage variance → column D (c+3) rule = cfs.addSparklineRule('LOLLIPOPVARISPARKLINE', { plannedValue: plannedRef, actualValue: actualRef, index: '@', reference: 0, mini: -0.15, maxi: 0.25, legend: true, colorPositive: relRowCell(dataFirstRow, c + 5), colorNegative: relRowCell(dataFirstRow, c + 6), lollipopHeaderColor: '#4a7a00' }, [new spreadNS.Range(dataFirstRow, c + 3, data.length, 1)]); if (rule) rule.showSparklineOnly(true); // Rule 2: Absolute variance → column E (c+4) rule = cfs.addSparklineRule('LOLLIPOPVARISPARKLINE', { plannedValue: plannedRef, actualValue: actualRef, index: '@', absolute: true, reference: 0, mini: -100, maxi: 100, legend: true, colorPositive: relRowCell(dataFirstRow, c + 5), colorNegative: relRowCell(dataFirstRow, c + 6), lollipopHeaderColor: '#4a7a00' }, [new spreadNS.Range(dataFirstRow, c + 4, data.length, 1)]); if (rule) rule.showSparklineOnly(true); sheet.resumePaint(); } /* ================================================================ Sheet 2 — Column ================================================================ */ function initSheet2_Column(spread) { var sheet = spread.getSheet(1); sheet.name('Column'); sheet.options.sheetTabColor = '#c8a000'; sheet.suspendPaint(); sheet.setColumnCount(7); sheet.setRowCount(10); var cfs = sheet.conditionalFormats; var i, j, rule; var c = 0; var titleStyle = makeStyle({ font: 'bold 14px Calibri', fg: '#1a1a1a', vAlign: 1 }); var headerStyle = makeStyle({ font: 'bold 11px Calibri', bg: '#c8a000', fg: '#FFFFFF', hAlign: 1, vAlign: 1 }); var labelStyle = makeStyle({ bg: '#f5f0d0', font: '11px Calibri', vAlign: 1 }); var dollarStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1, formatter: '$#,##0' }); var thinBorder = new spreadNS.LineBorder('#d4c46a', spreadNS.LineStyle.thin); // A:Region B-E:Q1-Q4 F:Quarterly(sparkline) var headers = ['Region', 'Q1', 'Q2', 'Q3', 'Q4', 'Quarterly']; var colWidths = [120, 80, 80, 80, 80, 160]; // Title var r = 1; sheet.addSpan(r, c, 1, headers.length); sheet.setValue(r, c, 'Quarterly Revenue by Region'); sheet.setStyle(r, c, titleStyle); sheet.setRowHeight(r, 30); // Headers r = 2; for (i = 0; i < headers.length; i++) { sheet.setValue(r, c + i, headers[i]); sheet.setStyle(r, c + i, headerStyle); sheet.setColumnWidth(c + i, colWidths[i]); } sheet.setRowHeight(r, 30); // Data: [region, Q1, Q2, Q3, Q4] var data = [ ['North America', 1800, 1620, 1750, 1920], ['Europe', 1200, 1380, 1150, 1400], ['Asia Pacific', 1080, 950, 1140, 1020], ['Latin America', 480, 560, 430, 580], ['Middle East', 400, 350, 420, 370], ['Africa', 190, 260, 210, 270] ]; var dataFirstRow = r + 1; for (i = 0; i < data.length; i++) { var row = dataFirstRow + i; sheet.setValue(row, c, data[i][0]); sheet.setStyle(row, c, labelStyle); for (j = 1; j <= 4; j++) { sheet.setValue(row, c + j, data[i][j]); sheet.setStyle(row, c + j, dollarStyle); } sheet.setRowHeight(row, 60); } // Borders sheet.getRange(r, c, data.length + 1, headers.length).setBorder(thinBorder, { all: true }); // Column sparkline — single rule spanning all data rows var setting = new GC.Spread.Sheets.Sparklines.SparklineSetting(); setting.options.seriesColor = '#4a7a00'; setting.options.showHigh = true; setting.options.highMarkerColor = '#8b0000'; setting.options.showLow = true; setting.options.lowMarkerColor = '#d45500'; setting.options.showFirst = true; setting.options.firstMarkerColor = '#3d6b00'; setting.options.showLast = true; setting.options.lastMarkerColor = '#b5cc18'; rule = cfs.addSparklineRule('COLUMNSPARKLINE', { data: relRowRange(dataFirstRow, c + 1, 1, 4), dataOrientation: 1, setting: setting }, [new spreadNS.Range(dataFirstRow, c + 5, data.length, 1)]); if (rule) rule.showSparklineOnly(true); sheet.resumePaint(); } /* ================================================================ Sheet 3 — Pie ================================================================ */ function initSheet3_Pie(spread) { var sheet = spread.getSheet(2); sheet.name('Pie'); sheet.options.sheetTabColor = '#4a7a00'; sheet.suspendPaint(); sheet.setColumnCount(9); sheet.setRowCount(11); var cfs = sheet.conditionalFormats; var i, j, rule; var c = 0; var titleStyle = makeStyle({ font: 'bold 14px Calibri', fg: '#1a1a1a', vAlign: 1 }); var headerStyle = makeStyle({ font: 'bold 11px Calibri', bg: '#4a7a00', fg: '#FFFFFF', hAlign: 1, vAlign: 1 }); var labelStyle = makeStyle({ bg: '#dce8c8', font: '11px Calibri', vAlign: 1 }); var pctStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1, formatter: '0%' }); var colorStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1 }); var thinBorder = new spreadNS.LineBorder('#8fb356', spreadNS.LineStyle.thin); // A:Product B-D:HW%,SW%,Svc% E:Distribution(sparkline) F-H:Color1-3 var headers = ['Product', 'Hardware %', 'Software %', 'Services %', 'Distribution', 'Color 1', 'Color 2', 'Color 3']; var colWidths = [120, 90, 90, 90, 160, 80, 80, 80]; // Title var r = 1; sheet.addSpan(r, c, 1, headers.length); sheet.setValue(r, c, 'Revenue Distribution by Product'); sheet.setStyle(r, c, titleStyle); sheet.setRowHeight(r, 30); // Headers r = 2; for (i = 0; i < headers.length; i++) { sheet.setValue(r, c + i, headers[i]); sheet.setStyle(r, c + i, headerStyle); sheet.setColumnWidth(c + i, colWidths[i]); } sheet.setRowHeight(r, 30); // Data: [product, hw%, sw%, svc%, color1, color2, color3] var data = [ ['Laptops', 0.45, 0.35, 0.20, '#4a7a00', '#c8a000', '#6fa300'], ['Desktops', 0.30, 0.45, 0.25, '#8bc34a', '#b5cc18', '#A5A5A5'], ['Tablets', 0.55, 0.25, 0.20, '#2d5200', '#b8d86e', '#3d6b00'], ['Phones', 0.40, 0.30, 0.30, '#4a7a00', '#c8a000', '#6fa300'], ['Accessories', 0.35, 0.40, 0.25, '#d45500', '#c5e17a', '#9acd32'], ['Servers', 0.50, 0.30, 0.20, '#2d5200', '#7d9a30', '#c5d48a'], ['Networking', 0.25, 0.50, 0.25, '#4a7a00', '#c8a000', '#6fa300'] ]; var dataFirstRow = r + 1; for (i = 0; i < data.length; i++) { var row = dataFirstRow + i; sheet.setValue(row, c, data[i][0]); sheet.setStyle(row, c, labelStyle); for (j = 1; j <= 3; j++) { sheet.setValue(row, c + j, data[i][j]); sheet.setStyle(row, c + j, pctStyle); } // c+4 is sparkline column (skip) // c+5, c+6, c+7 are color columns (plain text) for (j = 0; j < 3; j++) { sheet.setValue(row, c + 5 + j, data[i][4 + j]); sheet.setStyle(row, c + 5 + j, colorStyle); } sheet.setRowHeight(row, 34); } // Borders sheet.getRange(r, c, data.length + 1, headers.length).setBorder(thinBorder, { all: true }); // Pie sparkline — single rule spanning all data rows // colors use relRowCell to reference color columns per row rule = cfs.addSparklineRule('PIESPARKLINE', { value: relRowRange(dataFirstRow, c + 1, 1, 3), colors: [relRowCell(dataFirstRow, c + 5), relRowCell(dataFirstRow, c + 6), relRowCell(dataFirstRow, c + 7)] }, [new spreadNS.Range(dataFirstRow, c + 4, data.length, 1)]); if (rule) rule.showSparklineOnly(true); sheet.resumePaint(); } /* ================================================================ Sheet 4 — Bullet ================================================================ */ function initSheet4_Bullet(spread) { var sheet = spread.getSheet(3); sheet.name('Bullet'); sheet.options.sheetTabColor = '#8bc34a'; sheet.suspendPaint(); sheet.setColumnCount(9); sheet.setRowCount(11); var cfs = sheet.conditionalFormats; var i, j, rule; var c = 0; var titleStyle = makeStyle({ font: 'bold 14px Calibri', fg: '#1a1a1a', vAlign: 1 }); var headerStyle = makeStyle({ font: 'bold 11px Calibri', bg: '#8bc34a', fg: '#FFFFFF', hAlign: 1, vAlign: 1 }); var labelStyle = makeStyle({ bg: '#eaf2d5', font: '11px Calibri', vAlign: 1 }); var dollarStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1, formatter: '$#,##0' }); var colorStyle = makeStyle({ font: '11px Calibri', hAlign: 1, vAlign: 1 }); var thinBorder = new spreadNS.LineBorder('#b8d86e', spreadNS.LineStyle.thin); // A:Rep B-F:Actual,Target,Max,Good,Bad G:Color var headers = ['Sales Rep', 'Actual ($K)', 'Target ($K)', 'Max ($K)', 'Good ($K)', 'Bad ($K)', 'Color']; var colWidths = [120, 160, 90, 80, 80, 80, 80]; // Title var r = 1; sheet.addSpan(r, c, 1, headers.length); sheet.setValue(r, c, 'Sales Team Performance'); sheet.setStyle(r, c, titleStyle); sheet.setRowHeight(r, 30); // Headers r = 2; for (i = 0; i < headers.length; i++) { sheet.setValue(r, c + i, headers[i]); sheet.setStyle(r, c + i, headerStyle); sheet.setColumnWidth(c + i, colWidths[i]); } sheet.setRowHeight(r, 30); // Data: [name, actual, target, max, good, bad, color] var data = [ ['Alice Chen', 82, 90, 120, 90, 60, '#4a7a00'], ['Bob Martinez', 95, 85, 120, 85, 55, '#c8a000'], ['Carol Wang', 68, 80, 120, 80, 50, '#6fa300'], ['David Park', 105, 100, 130, 100, 65, '#b5cc18'], ['Eva Johnson', 73, 75, 110, 75, 45, '#8bc34a'], ['Frank Liu', 88, 95, 125, 95, 60, '#4a7a00'], ['Grace Kim', 110, 105, 135, 105, 70, '#c8a000'] ]; var dataFirstRow = r + 1; for (i = 0; i < data.length; i++) { var row = dataFirstRow + i; sheet.setValue(row, c, data[i][0]); sheet.setStyle(row, c, labelStyle); for (j = 1; j <= 5; j++) { sheet.setValue(row, c + j, data[i][j]); sheet.setStyle(row, c + j, dollarStyle); } // c+6 is color column (plain text) sheet.setValue(row, c + 6, data[i][6]); sheet.setStyle(row, c + 6, colorStyle); sheet.setRowHeight(row, 34); } // Borders sheet.getRange(r, c, data.length + 1, headers.length).setBorder(thinBorder, { all: true }); // Bullet sparkline → Performance column (c+6), colorScheme from Color cell (c+7) rule = cfs.addSparklineRule('BULLETSPARKLINE', { measure: '@', target: relRowCell(dataFirstRow, c + 2), maxi: relRowCell(dataFirstRow, c + 3), good: relRowCell(dataFirstRow, c + 4), bad: relRowCell(dataFirstRow, c + 5), tickUnit: 20, colorScheme: relRowCell(dataFirstRow, c + 6) }, [new spreadNS.Range(dataFirstRow, c + 1, data.length, 1)]); if (rule) rule.showSparklineOnly(true); sheet.resumePaint(); } /* ================================================================ Control Panel — Redesigned ================================================================ */ function initRuleControlPanel(spread) { var ruleList = new RuleList(spread, document.getElementById('listItemContainer')); var rulePanel = new RulePanel(spread); rulePanel.bind('onRuleAdded', function () { ruleList.update(); }); rulePanel.bind('onCancelUpdate', function () { ruleList.clearSelectedState(); }); ruleList.bind('onRuleSelected', function (evt, args) { if (args.rule) { rulePanel.showUpdateMode(args.activeSheet, args.rule); } else { rulePanel.showAddMode(); } }); ruleList.bind('onRuleRemoved', function () { rulePanel.showAddMode(); }); ruleList.bind('onRuleRangeChanged', function (evt, args) { if (args.rule && rulePanel.ruleForUpdate === args.rule) { rulePanel.setRuleRanges(args.ranges); } }); spread.bind(spreadNS.Events.ActiveSheetChanged, function () { ruleList.update(); rulePanel.showAddMode(); }); document.getElementById('format-rule-move-up').addEventListener('click', function () { ruleList.sortFormatRules(true); }); document.getElementById('format-rule-move-down').addEventListener('click', function () { ruleList.sortFormatRules(false); }); } function initPanelToggle(spread) { var panel = document.querySelector('.options-container'); var toggleBtn = document.getElementById('panelToggleBtn'); var collapseBtn = document.getElementById('panelCollapseBtn'); function collapsePanel() { panel.classList.add('collapsed'); toggleBtn.classList.add('visible'); setTimeout(function () { spread.refresh(); }, 320); } function expandPanel() { panel.classList.remove('collapsed'); toggleBtn.classList.remove('visible'); setTimeout(function () { spread.refresh(); }, 320); } collapseBtn.addEventListener('click', collapsePanel); toggleBtn.addEventListener('click', expandPanel); } function cloneRanges(ranges) { var cloned = []; if (!ranges) return cloned; for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; cloned.push(new spreadNS.Range(range.row, range.col, range.rowCount, range.colCount)); } return cloned; } function rangesToFormula(ranges) { var formulaParts = []; for (var i = 0; i < ranges.length; i++) { formulaParts.push( spreadNS.CalcEngine.rangeToFormula( ranges[i], 0, 0, spreadNS.CalcEngine.RangeReferenceRelative.allAbsolute ) ); } return formulaParts.join(','); } function parseRangesFormula(sheet, formula) { if (!formula) return null; try { var groups = spreadNS.CalcEngine.formulaToRanges(sheet, formula, 0, 0); var ranges = []; if (!groups || !groups.length) return null; for (var i = 0; i < groups.length; i++) { var groupRanges = groups[i] && groups[i].ranges; if (!groupRanges || !groupRanges.length) continue; for (var j = 0; j < groupRanges.length; j++) { ranges.push(groupRanges[j]); } } return ranges.length ? cloneRanges(ranges) : null; } catch (error) { return null; } } /* ================================================================ RulePanel — Add / Update sparkline rules ================================================================ */ function RulePanel(spread) { this.spread = spread; this.eventsHandler = {}; this.ruleForUpdate = null; this.sheetForUpdate = null; this.rangesForUpdate = null; this.$modeLabel = document.getElementById('panelModeLabel'); this.$addBtn = document.getElementById('btnAddRule'); this.$updateBtn = document.getElementById('btnUpdateRule'); this.$cancelBtn = document.getElementById('btnCancelUpdate'); this.$sparklineType = document.getElementById('sparklineType'); this.$showOnly = document.getElementById('showSparklineOnly'); this.init(); } RulePanel.prototype.init = function () { var self = this; // Build initial sparkline options UI buildSparklineOptionsUI(self.$sparklineType.value, self.spread); self.$sparklineType.addEventListener('change', function () { buildSparklineOptionsUI(this.value, self.spread); }); self.$addBtn.addEventListener('click', function () { self.addRule(); }); self.$updateBtn.addEventListener('click', function () { self.updateRule(); }); self.$cancelBtn.addEventListener('click', function () { self.showAddMode(); self.trigger('onCancelUpdate'); }); }; RulePanel.prototype.addRule = function (sheet, ranges) { var self = this; var activeSheet = sheet || self.spread.getActiveSheet(); var targetRanges = ranges || activeSheet.getSelections(); var sparklineType = self.$sparklineType.value; var sparklineOpts = collectSparklineOptions(sparklineType); activeSheet.suspendPaint(); var rule = activeSheet.conditionalFormats.addSparklineRule(sparklineType, sparklineOpts, targetRanges); if (rule) { rule.showSparklineOnly(self.$showOnly.checked); } activeSheet.resumePaint(); self.trigger('onRuleAdded'); }; RulePanel.prototype.updateRule = function () { if (!this.ruleForUpdate || !this.sheetForUpdate) return; var ranges = cloneRanges(this.rangesForUpdate || this.ruleForUpdate.ranges()); this.sheetForUpdate.conditionalFormats.removeRule(this.ruleForUpdate); this.addRule(this.sheetForUpdate, ranges); this.showAddMode(); this.trigger('onCancelUpdate'); }; RulePanel.prototype.showAddMode = function () { this.$modeLabel.textContent = 'Add Rule'; this.$addBtn.style.display = ''; this.$updateBtn.style.display = 'none'; this.$cancelBtn.style.display = 'none'; this.ruleForUpdate = null; this.sheetForUpdate = null; this.rangesForUpdate = null; // Reset to defaults this.$sparklineType.value = 'PIESPARKLINE'; buildSparklineOptionsUI('PIESPARKLINE', this.spread); this.$showOnly.checked = false; }; RulePanel.prototype.showUpdateMode = function (sheet, rule) { this.ruleForUpdate = rule; this.sheetForUpdate = sheet; this.rangesForUpdate = cloneRanges(rule.ranges ? rule.ranges() : []); this.$modeLabel.textContent = 'Update Rule'; this.$addBtn.style.display = 'none'; this.$updateBtn.style.display = ''; this.$cancelBtn.style.display = ''; // Sync UI with existing rule var ruleType = rule.ruleType(); if (ruleType === spreadNS.ConditionalFormatting.RuleType.sparklineRule) { var sparklineType = rule.sparklineType ? rule.sparklineType() : ''; var sparklineOptions = rule.sparklineOptions ? rule.sparklineOptions() : {}; this.$sparklineType.value = sparklineType; this.$showOnly.checked = rule.showSparklineOnly ? rule.showSparklineOnly() : false; buildSparklineOptionsUI(sparklineType, this.spread); populateSparklineOptions(sparklineType, sparklineOptions); } }; RulePanel.prototype.setRuleRanges = function (ranges) { this.rangesForUpdate = cloneRanges(ranges); }; RulePanel.prototype.bind = function (event, callback) { if (!this.eventsHandler[event]) this.eventsHandler[event] = []; this.eventsHandler[event].push(callback); }; RulePanel.prototype.trigger = function (event, args) { var handlers = this.eventsHandler[event]; if (!handlers) return; for (var i = 0; i < handlers.length; i++) { handlers[i].call(this, event, args); } }; /* ================================================================ RuleList — Display & manage applied rules ================================================================ */ function RuleList(spread, host) { this.spread = spread; this.host = host; this.conditionalRules = {}; this.eventsHandler = {}; this.update(); } RuleList.Events = { onRuleSelected: 'onRuleSelected', onRuleRemoved: 'onRuleRemoved', onRuleRangeChanged: 'onRuleRangeChanged' }; RuleList.prototype.update = function () { this.clear(); this.buildList(); }; RuleList.prototype.clear = function () { this.host.innerHTML = ''; }; RuleList.prototype.clearSelectedState = function () { var items = this.host.querySelectorAll('.ruleListItem'); for (var i = 0; i < items.length; i++) { items[i].classList.remove('selected'); } }; RuleList.prototype.selectRule = function (item, ruleRef) { this.clearSelectedState(); item.classList.add('selected'); this.trigger('onRuleSelected', { activeSheet: this.spread.getActiveSheet(), rule: ruleRef }); }; RuleList.prototype.startRangeEdit = function (item, ruleRef, rangeHost) { if (rangeHost.classList.contains('editing')) return; var self = this; var sheet = self.spread.getActiveSheet(); var originalFormula = rangesToFormula(ruleRef.ranges()); rangeHost.classList.add('editing'); rangeHost.innerHTML = ''; var input = document.createElement('input'); input.type = 'text'; input.className = 'ruleRangeInput'; input.value = originalFormula; input.title = 'Press Enter to apply'; rangeHost.appendChild(input); var completed = false; function finish(shouldApply) { if (completed) return; completed = true; rangeHost.classList.remove('editing'); var nextFormula = originalFormula; if (shouldApply) { var updatedRanges = parseRangesFormula(sheet, input.value.trim()); if (updatedRanges) { sheet.suspendPaint(); ruleRef.ranges(updatedRanges); sheet.resumePaint(); self.spread.refresh(); nextFormula = rangesToFormula(ruleRef.ranges()); self.trigger('onRuleRangeChanged', { activeSheet: sheet, rule: ruleRef, ranges: cloneRanges(ruleRef.ranges()) }); } else { window.alert('Invalid rule range. Please enter a valid range formula.'); } } rangeHost.textContent = nextFormula; rangeHost.title = nextFormula; } input.addEventListener('click', function (e) { e.stopPropagation(); }); input.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); finish(true); } else if (e.key === 'Escape') { e.preventDefault(); finish(false); } }); input.addEventListener('blur', function () { finish(true); }); setTimeout(function () { input.focus(); input.select(); }, 0); }; RuleList.prototype.buildList = function () { var self = this; var sheet = self.spread.getActiveSheet(); self.conditionalRules = sheet.conditionalFormats; var rules = self.conditionalRules.getRules(); for (var i = rules.length - 1; i >= 0; i--) { var rule = rules[i]; var $item = document.createElement('div'); $item.classList.add('ruleListItem'); // Description with badge var $desc = document.createElement('div'); $desc.classList.add('ruleDescription'); var description = 'Rule'; var badgeClass = 'badge-default'; if (rule.ruleType() === spreadNS.ConditionalFormatting.RuleType.sparklineRule) { var sparklineType = rule.sparklineType ? rule.sparklineType() : ''; var friendly = sparklineType.replace('SPARKLINE', '').replace('LOLLIPOPVARI', 'Lollipop').replace('VARI', 'Variance'); if (friendly && friendly === friendly.toUpperCase()) { friendly = friendly.charAt(0) + friendly.slice(1).toLowerCase(); } description = friendly || 'Sparkline'; var typeKey = friendly.toLowerCase(); if (typeKey === 'bullet') badgeClass = 'badge-bullet'; else if (typeKey === 'pie') badgeClass = 'badge-pie'; else if (typeKey === 'hbar') badgeClass = 'badge-hbar'; else if (typeKey === 'line') badgeClass = 'badge-line'; else if (typeKey === 'column') badgeClass = 'badge-column'; else if (typeKey === 'winloss') badgeClass = 'badge-winloss'; else if (typeKey === 'lollipop') badgeClass = 'badge-lollipop'; } var badge = document.createElement('span'); badge.className = 'sparkline-badge ' + badgeClass; badge.textContent = description; $desc.appendChild(badge); // Range formula var $range = document.createElement('div'); $range.classList.add('ruleFormulas'); var formula = rangesToFormula(rule.ranges()); $range.textContent = formula; $range.title = formula; // Stop If True var $stop = document.createElement('div'); $stop.classList.add('ruleStopIfTrue'); var $cb = document.createElement('input'); $cb.type = 'checkbox'; $cb.checked = rule.stopIfTrue(); if (rule.ruleType() === spreadNS.ConditionalFormatting.RuleType.sparklineRule) { $cb.disabled = true; } $stop.appendChild($cb); // Remove button var $remove = document.createElement('div'); $remove.classList.add('ruleRemove'); var $removeBtn = document.createElement('button'); $removeBtn.className = 'removeRuleBtn'; $removeBtn.textContent = '\u00d7'; $removeBtn.title = 'Remove rule'; $remove.appendChild($removeBtn); $item.appendChild($desc); $item.appendChild($range); $item.appendChild($stop); $item.appendChild($remove); self.host.appendChild($item); // Event: select row (function (item, ruleRef) { item.addEventListener('click', function (e) { if (e.target.classList.contains('removeRuleBtn')) return; self.selectRule(item, ruleRef); }); })($item, rule); (function (item, ruleRef, rangeHost) { rangeHost.addEventListener('click', function (e) { e.stopPropagation(); self.selectRule(item, ruleRef); self.startRangeEdit(item, ruleRef, rangeHost); }); })($item, rule, $range); // Event: remove (function (ruleRef, removeBtn) { removeBtn.addEventListener('click', function () { var sheet = self.spread.getActiveSheet(); sheet.conditionalFormats.removeRule(ruleRef); self.trigger('onRuleRemoved'); self.update(); }); })(rule, $removeBtn); // Event: stop if true (function (ruleRef, checkbox) { checkbox.addEventListener('change', function () { ruleRef.stopIfTrue(checkbox.checked); }); })(rule, $cb); } }; RuleList.prototype.sortFormatRules = function (isUp) { var self = this; var items = self.host.querySelectorAll('.ruleListItem'); var selectedIdx = -1; for (var i = 0; i < items.length; i++) { if (items[i].classList.contains('selected')) { selectedIdx = i; break; } } if (selectedIdx === -1) return; if ((isUp && selectedIdx === 0) || (!isUp && selectedIdx === items.length - 1)) return; var tempFormats = $.extend(true, {}, self.conditionalRules); self.conditionalRules.clearRule(); var total = tempFormats.count(); var indexInFormats = total - 1 - selectedIdx; for (i = 0; i < total; i++) { if (isUp) { if (i === indexInFormats) self.conditionalRules.addRule(tempFormats.getRule(indexInFormats + 1)); else if (i === indexInFormats + 1) self.conditionalRules.addRule(tempFormats.getRule(indexInFormats)); else self.conditionalRules.addRule(tempFormats.getRule(i)); } else { if (i === indexInFormats) self.conditionalRules.addRule(tempFormats.getRule(indexInFormats - 1)); else if (i === indexInFormats - 1) self.conditionalRules.addRule(tempFormats.getRule(indexInFormats)); else self.conditionalRules.addRule(tempFormats.getRule(i)); } } self.update(); var newItems = self.host.querySelectorAll('.ruleListItem'); var newIdx = isUp ? selectedIdx - 1 : selectedIdx + 1; if (newItems[newIdx]) newItems[newIdx].classList.add('selected'); }; RuleList.prototype.bind = function (event, callback) { if (!this.eventsHandler[event]) this.eventsHandler[event] = []; this.eventsHandler[event].push(callback); }; RuleList.prototype.trigger = function (event, args) { var handlers = this.eventsHandler[event]; if (!handlers) return; for (var i = 0; i < handlers.length; i++) { handlers[i].call(this, event, args); } }; /* ================================================================ Sparkline Configuration & UI ================================================================ */ var sparklineConfigs = { 'PIESPARKLINE': [ { name: 'value', label: 'Range/Percentage', type: 'range', defaultValue: '@' }, { name: 'colors', label: 'Colors (comma separated)', type: 'text', defaultValue: '' } ], 'LINESPARKLINE': [ { name: 'data', label: 'Data', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'dataOrientation', label: 'Data Orientation', type: 'select', defaultValue: '0', options: ['0', '1'], optionLabels: ['Vertical', 'Horizontal'] }, { name: 'dateAxisData', label: 'Date Axis Data', type: 'range', defaultValue: '' }, { name: 'dateAxisOrientation', label: 'Date Axis Orientation', type: 'select', defaultValue: '0', options: ['0', '1'], optionLabels: ['Vertical', 'Horizontal'] }, { name: 'setting.showMarkers', label: 'Show Markers', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showFirst', label: 'Show First', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showLast', label: 'Show Last', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showHigh', label: 'Show High', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showLow', label: 'Show Low', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showNegative', label: 'Show Negative', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.displayXAxis', label: 'Display X Axis', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.seriesColor', label: 'Series Color', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.negativeColor', label: 'Negative Color', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.firstMarkerColor', label: 'First Marker', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.lastMarkerColor', label: 'Last Marker', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.highMarkerColor', label: 'High Marker', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.lowMarkerColor', label: 'Low Marker', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.markersColor', label: 'Markers Color', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.axisColor', label: 'Axis Color', type: 'text', defaultValue: '', group: 'setting' } ], 'COLUMNSPARKLINE': [ { name: 'data', label: 'Data', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'dataOrientation', label: 'Data Orientation', type: 'select', defaultValue: '0', options: ['0', '1'], optionLabels: ['Vertical', 'Horizontal'] }, { name: 'dateAxisData', label: 'Date Axis Data', type: 'range', defaultValue: '' }, { name: 'dateAxisOrientation', label: 'Date Axis Orientation', type: 'select', defaultValue: '0', options: ['0', '1'], optionLabels: ['Vertical', 'Horizontal'] }, { name: 'setting.showFirst', label: 'Show First', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showLast', label: 'Show Last', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showHigh', label: 'Show High', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showLow', label: 'Show Low', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.showNegative', label: 'Show Negative', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.displayXAxis', label: 'Display X Axis', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.seriesColor', label: 'Series Color', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.negativeColor', label: 'Negative Color', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.firstMarkerColor', label: 'First Marker', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.lastMarkerColor', label: 'Last Marker', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.highMarkerColor', label: 'High Marker', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.lowMarkerColor', label: 'Low Marker', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.axisColor', label: 'Axis Color', type: 'text', defaultValue: '', group: 'setting' } ], 'WINLOSSSPARKLINE': [ { name: 'data', label: 'Data', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'dataOrientation', label: 'Data Orientation', type: 'select', defaultValue: '0', options: ['0', '1'], optionLabels: ['Vertical', 'Horizontal'] }, { name: 'dateAxisData', label: 'Date Axis Data', type: 'range', defaultValue: '' }, { name: 'dateAxisOrientation', label: 'Date Axis Orientation', type: 'select', defaultValue: '0', options: ['0', '1'], optionLabels: ['Vertical', 'Horizontal'] }, { name: 'setting.showNegative', label: 'Show Negative', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.displayXAxis', label: 'Display X Axis', type: 'checkbox', defaultValue: false, group: 'setting' }, { name: 'setting.seriesColor', label: 'Series Color', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.negativeColor', label: 'Negative Color', type: 'text', defaultValue: '', group: 'setting' }, { name: 'setting.axisColor', label: 'Axis Color', type: 'text', defaultValue: '', group: 'setting' } ], 'AREASPARKLINE': [ { name: 'points', label: 'Points', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'mini', label: 'Mini', type: 'text', defaultValue: '' }, { name: 'maxi', label: 'Maxi', type: 'text', defaultValue: '' }, { name: 'line1', label: 'Line1', type: 'text', defaultValue: '' }, { name: 'line2', label: 'Line2', type: 'text', defaultValue: '' }, { name: 'colorPositive', label: 'Color Positive', type: 'text', defaultValue: '' }, { name: 'colorNegative', label: 'Color Negative', type: 'text', defaultValue: '' } ], 'SCATTERSPARKLINE': [ { name: 'points1', label: 'Points1', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'points2', label: 'Points2', type: 'range', defaultValue: '' }, { name: 'minX', label: 'MinX', type: 'text', defaultValue: '' }, { name: 'maxX', label: 'MaxX', type: 'text', defaultValue: '' }, { name: 'minY', label: 'MinY', type: 'text', defaultValue: '' }, { name: 'maxY', label: 'MaxY', type: 'text', defaultValue: '' }, { name: 'hLine', label: 'HLine', type: 'text', defaultValue: '' }, { name: 'vLine', label: 'VLine', type: 'text', defaultValue: '' }, { name: 'tags', label: 'Tags', type: 'text', defaultValue: '' }, { name: 'drawSymbol', label: 'Draw Symbol', type: 'checkbox', defaultValue: false }, { name: 'drawLines', label: 'Draw Lines', type: 'checkbox', defaultValue: false }, { name: 'color1', label: 'Color1', type: 'text', defaultValue: '' }, { name: 'color2', label: 'Color2', type: 'text', defaultValue: '' }, { name: 'dash', label: 'Dash', type: 'checkbox', defaultValue: false } ], 'BULLETSPARKLINE': [ { name: 'measure', label: 'Measure', type: 'range', defaultValue: '@' }, { name: 'target', label: 'Target', type: 'range', defaultValue: '' }, { name: 'maxi', label: 'Maxi', type: 'range', defaultValue: '' }, { name: 'good', label: 'Good', type: 'range', defaultValue: '' }, { name: 'bad', label: 'Bad', type: 'range', defaultValue: '' }, { name: 'forecast', label: 'Forecast', type: 'range', defaultValue: '' }, { name: 'tickUnit', label: 'Tick Unit', type: 'range', defaultValue: '' }, { name: 'colorScheme', label: 'Color Scheme', type: 'text', defaultValue: '' }, { name: 'vertical', label: 'Vertical', type: 'checkbox', defaultValue: false }, { name: 'measureColor', label: 'Measure Color', type: 'text', defaultValue: '' }, { name: 'targetColor', label: 'Target Color', type: 'text', defaultValue: '' }, { name: 'maxiColor', label: 'Maxi Color', type: 'text', defaultValue: '' }, { name: 'goodColor', label: 'Good Color', type: 'text', defaultValue: '' }, { name: 'badColor', label: 'Bad Color', type: 'text', defaultValue: '' }, { name: 'forecastColor', label: 'Forecast Color', type: 'text', defaultValue: '' }, { name: 'allowMeasureOverMaxi', label: 'Allow Over Maxi', type: 'checkbox', defaultValue: false }, { name: 'barSize', label: 'Bar Size', type: 'text', defaultValue: '' } ], 'SPREADSPARKLINE': [ { name: 'points', label: 'Points', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'showAverage', label: 'Show Average', type: 'checkbox', defaultValue: false }, { name: 'scaleStart', label: 'Scale Start', type: 'text', defaultValue: '' }, { name: 'scaleEnd', label: 'Scale End', type: 'text', defaultValue: '' }, { name: 'style', label: 'Style (1-6)', type: 'text', defaultValue: '1' }, { name: 'colorScheme', label: 'Color Scheme', type: 'text', defaultValue: '' }, { name: 'vertical', label: 'Vertical', type: 'checkbox', defaultValue: false } ], 'STACKEDSPARKLINE': [ { name: 'points', label: 'Points', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'colorRange', label: 'Color Range', type: 'range', defaultValue: '' }, { name: 'labelRange', label: 'Label Range', type: 'range', defaultValue: '' }, { name: 'maximum', label: 'Maximum', type: 'text', defaultValue: '' }, { name: 'targetRed', label: 'Target Red', type: 'text', defaultValue: '' }, { name: 'targetGreen', label: 'Target Green', type: 'text', defaultValue: '' }, { name: 'targetBlue', label: 'Target Blue', type: 'text', defaultValue: '' }, { name: 'targetYellow', label: 'Target Yellow', type: 'text', defaultValue: '' }, { name: 'color', label: 'Color', type: 'text', defaultValue: '' }, { name: 'highlightPosition', label: 'Highlight Position', type: 'text', defaultValue: '' }, { name: 'vertical', label: 'Vertical', type: 'checkbox', defaultValue: false }, { name: 'textOrientation', label: 'Text Orientation', type: 'text', defaultValue: '0' }, { name: 'textSize', label: 'Text Size', type: 'text', defaultValue: '10' } ], 'HBARSPARKLINE': [ { name: 'value', label: 'Value', type: 'range', defaultValue: '@' }, { name: 'colorScheme', label: 'Color Scheme', type: 'text', defaultValue: 'gray' }, { name: 'axisVisible', label: 'Axis Visible', type: 'checkbox', defaultValue: true }, { name: 'barHeight', label: 'Bar Height', type: 'text', defaultValue: '0.8' } ], 'VBARSPARKLINE': [ { name: 'value', label: 'Value', type: 'range', defaultValue: '@' }, { name: 'colorScheme', label: 'Color Scheme', type: 'text', defaultValue: 'gray' }, { name: 'axisVisible', label: 'Axis Visible', type: 'checkbox', defaultValue: true }, { name: 'barWidth', label: 'Bar Width', type: 'text', defaultValue: '0.8' } ], 'VARISPARKLINE': [ { name: 'variance', label: 'Variance', type: 'range', defaultValue: '@' }, { name: 'reference', label: 'Reference', type: 'range', defaultValue: '' }, { name: 'mini', label: 'Mini', type: 'range', defaultValue: '' }, { name: 'maxi', label: 'Maxi', type: 'range', defaultValue: '' }, { name: 'mark', label: 'Mark', type: 'range', defaultValue: '' }, { name: 'tickUnit', label: 'Tick Unit', type: 'range', defaultValue: '' }, { name: 'legend', label: 'Legend', type: 'checkbox', defaultValue: false }, { name: 'colorPositive', label: 'Color Positive', type: 'text', defaultValue: '' }, { name: 'colorNegative', label: 'Color Negative', type: 'text', defaultValue: '' }, { name: 'vertical', label: 'Vertical', type: 'checkbox', defaultValue: false } ], 'LOLLIPOPVARISPARKLINE': [ { name: 'plannedValue', label: 'Planned Value', type: 'range', defaultValue: '' }, { name: 'actualValue', label: 'Actual Value', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'index', label: 'Index', type: 'range', defaultValue: '@' }, { name: 'absolute', label: 'Absolute', type: 'checkbox', defaultValue: false }, { name: 'reference', label: 'Reference', type: 'range', defaultValue: '' }, { name: 'mini', label: 'Mini', type: 'range', defaultValue: '' }, { name: 'maxi', label: 'Maxi', type: 'range', defaultValue: '' }, { name: 'tickUnit', label: 'Tick Unit', type: 'range', defaultValue: '' }, { name: 'legend', label: 'Legend', type: 'checkbox', defaultValue: false }, { name: 'colorPositive', label: 'Color Positive', type: 'text', defaultValue: '' }, { name: 'colorNegative', label: 'Color Negative', type: 'text', defaultValue: '' }, { name: 'lollipopHeaderColor', label: 'Header Color', type: 'text', defaultValue: '' }, { name: 'vertical', label: 'Vertical', type: 'checkbox', defaultValue: false } ], 'BOXPLOTSPARKLINE': [ { name: 'points', label: 'Points', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'boxPlotClass', label: 'BoxPlot Class', type: 'select', defaultValue: '5ns', options: ['5ns', '7ns', 'tukey', 'bowley', 'sigma3'] }, { name: 'showAverage', label: 'Show Average', type: 'checkbox', defaultValue: false }, { name: 'scaleStart', label: 'Scale Start', type: 'text', defaultValue: '' }, { name: 'scaleEnd', label: 'Scale End', type: 'text', defaultValue: '' }, { name: 'acceptableStart', label: 'Acceptable Start', type: 'text', defaultValue: '' }, { name: 'acceptableEnd', label: 'Acceptable End', type: 'text', defaultValue: '' }, { name: 'colorScheme', label: 'Color Scheme', type: 'text', defaultValue: '' }, { name: 'style', label: 'Style (0 or 1)', type: 'text', defaultValue: '0' }, { name: 'vertical', label: 'Vertical', type: 'checkbox', defaultValue: false } ], 'CASCADESPARKLINE': [ { name: 'pointsRange', label: 'Points Range', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'pointIndex', label: 'Point Index', type: 'range', defaultValue: '@' }, { name: 'labelsRange', label: 'Labels Range', type: 'range', defaultValue: '' }, { name: 'minimum', label: 'Minimum', type: 'text', defaultValue: '' }, { name: 'maximum', label: 'Maximum', type: 'text', defaultValue: '' }, { name: 'colorPositive', label: 'Color Positive', type: 'text', defaultValue: '' }, { name: 'colorNegative', label: 'Color Negative', type: 'text', defaultValue: '' }, { name: 'vertical', label: 'Vertical', type: 'checkbox', defaultValue: false }, { name: 'itemTypeRange', label: 'Item Type Range', type: 'range', defaultValue: '' }, { name: 'colorTotal', label: 'Color Total', type: 'text', defaultValue: '' } ], 'PARETOSPARKLINE': [ { name: 'points', label: 'Points', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'pointIndex', label: 'Point Index', type: 'range', defaultValue: '@' }, { name: 'colorRange', label: 'Color Range', type: 'range', defaultValue: '' }, { name: 'target', label: 'Target', type: 'range', defaultValue: '' }, { name: 'target2', label: 'Target2', type: 'range', defaultValue: '' }, { name: 'highlightPosition', label: 'Highlight Position', type: 'range', defaultValue: '' }, { name: 'label', label: 'Label (0:None,1:Number,2:Percent)', type: 'text', defaultValue: '0' }, { name: 'vertical', label: 'Vertical', type: 'checkbox', defaultValue: false }, { name: 'targetColor', label: 'Target Color', type: 'text', defaultValue: '' }, { name: 'target2Color', label: 'Target2 Color', type: 'text', defaultValue: '' }, { name: 'labelColor', label: 'Label Color', type: 'text', defaultValue: '' }, { name: 'barSize', label: 'Bar Size', type: 'text', defaultValue: '' } ], 'MONTHSPARKLINE': [ { name: 'year', label: 'Year', type: 'text', defaultValue: new Date().getFullYear().toString() }, { name: 'month', label: 'Month', type: 'text', defaultValue: '1' }, { name: 'dataRange', label: 'Data Range', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'emptyColor', label: 'Empty Color', type: 'text', defaultValue: '' }, { name: 'startColor', label: 'Start Color', type: 'text', defaultValue: '' }, { name: 'middleColor', label: 'Middle Color', type: 'text', defaultValue: '' }, { name: 'endColor', label: 'End Color', type: 'text', defaultValue: '' }, { name: 'colorRange', label: 'Color Range', type: 'range', defaultValue: '' } ], 'YEARSPARKLINE': [ { name: 'year', label: 'Year', type: 'text', defaultValue: new Date().getFullYear().toString() }, { name: 'dataRange', label: 'Data Range', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'emptyColor', label: 'Empty Color', type: 'text', defaultValue: '' }, { name: 'startColor', label: 'Start Color', type: 'text', defaultValue: '' }, { name: 'middleColor', label: 'Middle Color', type: 'text', defaultValue: '' }, { name: 'endColor', label: 'End Color', type: 'text', defaultValue: '' }, { name: 'colorRange', label: 'Color Range', type: 'range', defaultValue: '' } ], 'HISTOGRAMSPARKLINE': [ { name: 'dataRange', label: 'Data Range', type: 'range', defaultValue: '$CF_RANGE$' }, { name: 'continuous', label: 'Continuous', type: 'checkbox', defaultValue: false }, { name: 'paintLabel', label: 'Paint Label', type: 'checkbox', defaultValue: false }, { name: 'scale', label: 'Scale', type: 'text', defaultValue: '' }, { name: 'barWidth', label: 'Bar Width', type: 'text', defaultValue: '' }, { name: 'barColor', label: 'Bar Color', type: 'text', defaultValue: '' }, { name: 'labelFontStyle', label: 'Label Font Style', type: 'text', defaultValue: '' }, { name: 'labelColor', label: 'Label Color', type: 'text', defaultValue: '' }, { name: 'edgeColor', label: 'Edge Color', type: 'text', defaultValue: '' } ], 'GAUGEKPISPARKLINE': [ { name: 'targetValue', label: 'Target Value', type: 'range', defaultValue: '' }, { name: 'currentValue', label: 'Current Value', type: 'range', defaultValue: '@' }, { name: 'minValue', label: 'Min Value', type: 'range', defaultValue: '0' }, { name: 'maxValue', label: 'Max Value', type: 'range', defaultValue: '100' }, { name: 'showLabel', label: 'Show Label', type: 'checkbox', defaultValue: false }, { name: 'targetValueLabel', label: 'Target Label', type: 'text', defaultValue: '' }, { name: 'currentValueLabel', label: 'Current Label', type: 'text', defaultValue: '' }, { name: 'minValueLabel', label: 'Min Label', type: 'text', defaultValue: '' }, { name: 'maxValueLabel', label: 'Max Label', type: 'text', defaultValue: '' }, { name: 'minAngle', label: 'Min Angle', type: 'text', defaultValue: '-90' }, { name: 'maxAngle', label: 'Max Angle', type: 'text', defaultValue: '90' }, { name: 'radiusRatio', label: 'Radius Ratio', type: 'text', defaultValue: '' }, { name: 'gaugeType', label: 'Gauge Type (0,1,2)', type: 'text', defaultValue: '' } ], 'IMAGE': [ { name: 'source', label: 'Source (URL)', type: 'range', defaultValue: '@' }, { name: 'alt_text', label: 'Alt Text', type: 'text', defaultValue: '' }, { name: 'sizing', label: 'Sizing (0-3)', type: 'text', defaultValue: '0' }, { name: 'height', label: 'Height', type: 'text', defaultValue: '' }, { name: 'width', label: 'Width', type: 'text', defaultValue: '' }, { name: 'clipX', label: 'Clip X', type: 'text', defaultValue: '' }, { name: 'clipY', label: 'Clip Y', type: 'text', defaultValue: '' }, { name: 'clipHeight', label: 'Clip Height', type: 'text', defaultValue: '' }, { name: 'clipWidth', label: 'Clip Width', type: 'text', defaultValue: '' }, { name: 'vAlign', label: 'VAlign (0-2)', type: 'text', defaultValue: '1' }, { name: 'hAlign', label: 'HAlign (0-2)', type: 'text', defaultValue: '1' } ], 'RANGEBLOCKSPARKLINE': [ { name: 'template_range', label: 'Template Range', type: 'range', defaultValue: '' }, { name: 'data_expression', label: 'Data Expression (JSON)', type: 'text', defaultValue: '' } ] }; var sparklineFormulaTextBoxes = {}; function buildSparklineOptionsUI(sparklineType, spread) { var container = document.getElementById('sparklineOptions'); for (var key in sparklineFormulaTextBoxes) { if (sparklineFormulaTextBoxes.hasOwnProperty(key) && sparklineFormulaTextBoxes[key].destroy) { sparklineFormulaTextBoxes[key].destroy(); } } sparklineFormulaTextBoxes = {}; container.innerHTML = ''; var config = sparklineConfigs[sparklineType]; if (!config) return; var table = document.createElement('table'); table.style.width = '100%'; var lastGroup = null; for (var i = 0; i < config.length; i++) { var param = config[i]; if (param.group && param.group !== lastGroup) { var trH = document.createElement('tr'); trH.className = 'group-header'; var tdH = document.createElement('td'); tdH.colSpan = 2; tdH.innerText = 'Sparkline Setting'; trH.appendChild(tdH); table.appendChild(trH); lastGroup = param.group; } var tr = document.createElement('tr'); var tdLabel = document.createElement('td'); tdLabel.className = 'opt-label'; tdLabel.innerText = param.label; var tdInput = document.createElement('td'); if (param.type === 'checkbox') { var cb = document.createElement('input'); cb.type = 'checkbox'; cb.id = 'sp_' + param.name; cb.checked = !!param.defaultValue; tdInput.appendChild(cb); } else if (param.type === 'select') { var sel = document.createElement('select'); sel.id = 'sp_' + param.name; sel.className = 'ruleOptionsInput'; for (var j = 0; j < param.options.length; j++) { var opt = document.createElement('option'); opt.value = param.options[j]; opt.text = param.optionLabels ? param.optionLabels[j] : param.options[j]; if (param.options[j] === param.defaultValue) opt.selected = true; sel.appendChild(opt); } tdInput.appendChild(sel); } else if (param.type === 'range') { var div = document.createElement('div'); div.id = 'sp_' + param.name; div.className = 'ruleOptionsFormulaTextBox'; tdInput.appendChild(div); } else { var input = document.createElement('input'); input.type = 'text'; input.id = 'sp_' + param.name; input.className = 'ruleOptionsInput'; input.value = param.defaultValue || ''; tdInput.appendChild(input); } tr.appendChild(tdLabel); tr.appendChild(tdInput); table.appendChild(tr); } container.appendChild(table); for (var k = 0; k < config.length; k++) { var p = config[k]; if (p.type === 'range') { var el = document.getElementById('sp_' + p.name); if (el && spread) { var ftb = new spreadNS.FormulaTextBox.FormulaTextBox(el, { absoluteReference: true, needSheetName: false, rangeSelectMode: true }); ftb.workbook(spread); if (p.defaultValue) ftb.text(p.defaultValue); sparklineFormulaTextBoxes[p.name] = ftb; } } } } var sparklineStringFields = [ 'data', 'points', 'points1', 'points2', 'pointsRange', 'dataRange', 'plannedValue', 'actualValue', 'colorRange', 'labelRange', 'labelsRange', 'itemTypeRange', 'template_range', 'data_expression', 'tags', 'colors', 'colorScheme', 'boxPlotClass', 'source', 'alt_text', 'labelFontStyle', 'dateAxisData', 'value', 'targetValueLabel', 'currentValueLabel', 'minValueLabel', 'maxValueLabel' ]; function collectSparklineOptions(sparklineType) { var config = sparklineConfigs[sparklineType]; if (!config) return {}; var options = {}; var settingOptions = {}; var hasSetting = false; for (var i = 0; i < config.length; i++) { var param = config[i]; var val; if (param.type === 'checkbox') { var el = document.getElementById('sp_' + param.name); if (!el) continue; val = el.checked; } else if (param.type === 'range') { var ftb = sparklineFormulaTextBoxes[param.name]; if (!ftb) continue; val = ftb.text(); } else { var el2 = document.getElementById('sp_' + param.name); if (!el2) continue; val = el2.value; } if (param.group === 'setting') { var settingKey = param.name.replace('setting.', ''); if (param.type === 'checkbox') { if (val) { settingOptions[settingKey] = val; hasSetting = true; } } else if (val !== '' && val !== null && val !== undefined) { settingOptions[settingKey] = val; hasSetting = true; } continue; } if (param.type !== 'checkbox' && (val === '' || val === null || val === undefined)) continue; if (param.type === 'checkbox' && !val) continue; if (param.name === 'colors' && val) { var colorArr = val.split(',').map(function (c) { return c.trim(); }).filter(function (c) { return c; }); if (colorArr.length > 0) options[param.name] = colorArr; continue; } if (param.type === 'text' && sparklineStringFields.indexOf(param.name) === -1 && val !== '@' && val !== '$CF_RANGE$') { if (typeof val === 'string' && val.charAt(0) !== '#' && !isColorName(val)) { var numVal = parseFloat(val); if (!isNaN(numVal) && isFinite(numVal)) val = numVal; } } if (param.type === 'select' && val !== '') { var selNum = parseFloat(val); if (!isNaN(selNum) && isFinite(selNum)) val = selNum; } options[param.name] = val; } if (hasSetting) { var setting = new GC.Spread.Sheets.Sparklines.SparklineSetting(); for (var key in settingOptions) { if (settingOptions.hasOwnProperty(key)) setting.options[key] = settingOptions[key]; } options['setting'] = setting; } return options; } function isColorName(val) { var colors = ['red', 'green', 'blue', 'yellow', 'orange', 'purple', 'pink', 'black', 'white', 'gray', 'grey', 'cyan', 'magenta', 'brown', 'lime', 'navy', 'teal', 'maroon', 'olive', 'aqua', 'silver', 'fuchsia']; return colors.indexOf(val.toLowerCase()) >= 0; } function populateSparklineOptions(sparklineType, sparklineOptions) { var config = sparklineConfigs[sparklineType]; if (!config || !sparklineOptions) return; for (var i = 0; i < config.length; i++) { var param = config[i]; var val; if (param.group === 'setting') { var settingObj = sparklineOptions['setting']; if (settingObj) { var settingKey = param.name.replace('setting.', ''); var opts = settingObj.options || settingObj; val = opts[settingKey]; } else { val = undefined; } } else if (param.name === 'colors' && Array.isArray(sparklineOptions[param.name])) { val = sparklineOptions[param.name].join(', '); } else { val = sparklineOptions[param.name]; } if (val === undefined || val === null) continue; if (param.type === 'checkbox') { var el = document.getElementById('sp_' + param.name); if (el) el.checked = !!val; } else if (param.type === 'range') { var ftb = sparklineFormulaTextBoxes[param.name]; if (ftb) ftb.text(val + ''); } else { var el2 = document.getElementById('sp_' + param.name); if (el2) el2.value = val; } } }
<!doctype html> <html style="height:100%;font-size:14px;"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="spreadjs culture" content="ko-kr" /> <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="$DEMOROOT$/spread/source/js/jquery-1.8.2.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 class="sample-tutorial"> <div id="ss" class="sample-spreadsheets"></div> <div id="panelToggleBtn" class="panel-toggle-btn" title="Toggle Panel"> <span class="panel-toggle-icon"><span></span></span> </div> <div class="options-container"> <div class="panel-header"> <span class="panel-title">Sparkline Rules</span> <span id="panelCollapseBtn" class="panel-collapse-btn" title="Collapse">&times;</span> </div> <div class="panel-section"> <div class="panel-section-title"> <span>Rule List</span> <div class="rule-order-btns"> <button id="format-rule-move-up" class="btn-icon" title="Move Up"> <span class="icon-up"></span> </button> <button id="format-rule-move-down" class="btn-icon" title="Move Down"> <span class="icon-down"></span> </button> </div> </div> <div id="listContainer" class="rule-list-container"> <div id="listControlBar" class="rule-list-header"> <div class="rl-col rl-col-type">Type</div> <div class="rl-col rl-col-range">Range</div> <div class="rl-col rl-col-stop">Stop If True</div> <div class="rl-col rl-col-action"></div> </div> <div id="listItemContainer" class="rule-list-body"> </div> </div> </div> <div class="panel-section panel-section-editor"> <div class="panel-section-title"> <span>Rule Option Setting</span> <div class="panel-editor-mode"> <span id="panelModeLabel">Add Rule</span> <button id="btnCancelUpdate" class="btn-link" style="display:none;">Cancel</button> </div> </div> <div class="panel-section-hint">Select a rule above to edit its options. Click a rule range to edit it directly.</div> <div class="panel-card"> <div class="form-group form-group-inline"> <label class="form-label">Sparkline Type</label> <select id="sparklineType" class="form-select"> <option value="PIESPARKLINE">Pie</option> <option value="LINESPARKLINE">Line</option> <option value="COLUMNSPARKLINE">Column</option> <option value="WINLOSSSPARKLINE">WinLoss</option> <option value="AREASPARKLINE">Area</option> <option value="SCATTERSPARKLINE">Scatter</option> <option value="BULLETSPARKLINE">Bullet</option> <option value="SPREADSPARKLINE">Spread</option> <option value="STACKEDSPARKLINE">Stacked</option> <option value="HBARSPARKLINE">HBar</option> <option value="VBARSPARKLINE">VBar</option> <option value="VARISPARKLINE">Variance</option> <option value="LOLLIPOPVARISPARKLINE">Lollipop Variance</option> <option value="BOXPLOTSPARKLINE">BoxPlot</option> <option value="CASCADESPARKLINE">Cascade</option> <option value="PARETOSPARKLINE">Pareto</option> <option value="MONTHSPARKLINE">Month</option> <option value="YEARSPARKLINE">Year</option> <option value="HISTOGRAMSPARKLINE">Histogram</option> <option value="GAUGEKPISPARKLINE">GaugeKPI</option> <option value="IMAGE">Image</option> <option value="RANGEBLOCKSPARKLINE">RangeBlock</option> </select> </div> <div id="sparklineOptions" class="sparkline-options-area"></div> <label class="form-checkbox"> <input type="checkbox" id="showSparklineOnly"/> <span>Show Sparkline Only</span> </label> <div class="panel-actions"> <button id="btnAddRule" class="btn btn-primary">Add Rule</button> <button id="btnUpdateRule" class="btn btn-primary" style="display:none;">Update Rule</button> </div> </div> </div> </div> </div> </body> </html>
body { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } .sample-tutorial { position: relative; height: 100%; overflow: hidden; } .sample-spreadsheets { width: 100%; height: 100%; overflow: hidden; } .options-container { position: absolute; top: 0; right: 0; width: 480px; overflow-y: auto; overflow-x: hidden; height: 100%; box-sizing: border-box; background: #fff; z-index: 1000; box-shadow: -2px 0 12px rgba(0,0,0,0.10); transition: transform 0.3s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 13px; color: #333; } .options-container.collapsed { transform: translateX(100%); } .panel-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid #e8e8e8; background: #fff; position: sticky; top: 0; z-index: 2; } .panel-title { font-weight: 600; font-size: 15px; color: #1a1a1a; } .panel-collapse-btn { cursor: pointer; font-size: 20px; color: #999; width: 28px; height: 28px; line-height: 28px; text-align: center; border-radius: 6px; user-select: none; transition: all 0.15s; } .panel-collapse-btn:hover { background: #f0f0f0; color: #333; } .panel-toggle-btn { position: absolute; top: 8px; right: 8px; z-index: 999; width: 36px; height: 36px; background: #4a7a00; border-radius: 8px; cursor: pointer; display: none; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(74,122,0,0.3); transition: background 0.2s; } .panel-toggle-btn:hover { background: #3d6b00; } .panel-toggle-btn.visible { display: flex; } .panel-toggle-icon { width: 18px; height: 14px; display: flex; flex-direction: column; justify-content: space-between; } .panel-toggle-icon::before, .panel-toggle-icon::after, .panel-toggle-icon span { content: ''; display: block; height: 2px; background: #fff; border-radius: 1px; } .panel-section { padding: 12px 16px; border-bottom: 1px solid #f0f0f0; } .panel-section:last-child { border-bottom: none; } .panel-section-title { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; font-weight: 600; font-size: 13px; color: #555; text-transform: uppercase; letter-spacing: 0.3px; } .panel-editor-mode { display: flex; align-items: center; gap: 10px; } .panel-section-hint { margin-bottom: 10px; font-size: 12px; line-height: 1.4; color: #7a7a7a; } .panel-card { background: #fafafa; border: 1px solid #e8e8e8; border-radius: 8px; padding: 14px; } .form-group { margin-bottom: 12px; } .form-group-inline { display: flex; align-items: center; } .form-group-inline .form-label { display: inline-block; width: 140px; min-width: 140px; margin-bottom: 0; padding: 0 4px; box-sizing: border-box; } .form-group-inline .form-select { width: 180px; margin-left: 10px; } .form-label { display: block; font-size: 12px; font-weight: 500; color: #666; margin-bottom: 4px; } .form-select { width: 100%; height: 32px; padding: 0 8px; border: 1px solid #d0d0d0; border-radius: 6px; background: #fff; font-size: 13px; color: #333; outline: none; box-sizing: border-box; transition: border-color 0.15s; cursor: pointer; } .form-select:focus { border-color: #4a7a00; box-shadow: 0 0 0 2px rgba(74,122,0,0.15); } .form-input { width: 100%; height: 30px; padding: 0 8px; border: 1px solid #d0d0d0; border-radius: 6px; background: #fff; font-size: 13px; color: #333; outline: none; box-sizing: border-box; transition: border-color 0.15s; } .form-input:focus { border-color: #4a7a00; box-shadow: 0 0 0 2px rgba(74,122,0,0.15); } .form-checkbox { display: flex; align-items: center; gap: 6px; cursor: pointer; margin: 8px 0; font-size: 13px; color: #444; } .form-checkbox input[type="checkbox"] { width: 16px; height: 16px; accent-color: #4a7a00; cursor: pointer; } .sparkline-options-area { max-height: 220px; overflow-y: auto; margin: 8px 0; } .sparkline-options-area table { width: 100%; border-collapse: collapse; } .sparkline-options-area td { padding: 3px 4px; vertical-align: middle; font-size: 12px; } .sparkline-options-area .opt-label { width: 140px; color: #666; white-space: nowrap; } .sparkline-options-area .group-header td { font-weight: 600; color: #4a7a00; padding-top: 8px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.3px; } .ruleOptionsInput { border: 1px solid #d0d0d0; height: 28px; line-height: 28px; padding: 0 6px; border-radius: 4px; font-size: 12px; box-sizing: border-box; outline: none; width: 180px; transition: border-color 0.15s; } .ruleOptionsInput:focus { border-color: #4a7a00; } .ruleOptionsFormulaTextBox { display: inline-block; border: 1px solid #d0d0d0; width: 180px; height: 28px; vertical-align: middle; border-radius: 4px; box-sizing: border-box; overflow: hidden; } .ruleOptionsFormulaTextBox > table { width: 100% !important; table-layout: fixed; } .ruleOptionsFormulaTextBox div[gcuielement="gcAttachedFormulaTextBox"] { width: 100% !important; } .ruleOptionsFormulaTextBox td:first-child > div { width: calc(100% - 2px) !important; } .panel-actions { margin-top: 12px; display: flex; gap: 8px; } .btn { height: 34px; padding: 0 20px; border: none; border-radius: 6px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.15s; } .btn-primary { background: #4a7a00; color: #fff; } .btn-primary:hover { background: #3d6b00; } .btn-link { background: none; border: none; color: #4a7a00; cursor: pointer; font-size: 12px; padding: 0; font-weight: 500; } .btn-link:hover { text-decoration: underline; } .btn-icon { width: 28px; height: 28px; padding: 0; border: 1px solid #d0d0d0; border-radius: 6px; background: #fff; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: all 0.15s; } .btn-icon:hover { background: #f0f0f0; border-color: #bbb; } .rule-order-btns { display: flex; gap: 4px; } .icon-up, .icon-down { display: block; width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; } .icon-up { border-bottom: 6px solid #666; } .icon-down { border-top: 6px solid #666; } .rule-list-container { border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; background: #fff; } .rule-list-header { display: flex; background: #f5f5f5; border-bottom: 1px solid #e0e0e0; font-size: 11px; font-weight: 600; color: #777; text-transform: uppercase; letter-spacing: 0.3px; } .rule-list-body { max-height: 240px; overflow-y: auto; } .rl-col { padding: 8px 10px; box-sizing: border-box; } .rl-col-type { width: 40%; } .rl-col-range { width: 28%; } .rl-col-stop { width: 18%; text-align: center; } .rl-col-action { width: 14%; text-align: center; } .ruleListItem { display: flex; align-items: center; border-bottom: 1px solid #f0f0f0; cursor: pointer; transition: background 0.1s; } .ruleListItem:last-child { border-bottom: none; } .ruleListItem:hover { background: #f8fbf0; } .ruleListItem.selected { background: #eef5e0; } .ruleDescription { width: 40%; padding: 6px 10px; font-size: 12px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; box-sizing: border-box; } .ruleFormulas { width: 28%; padding: 6px 6px; font-size: 11px; color: #4a7a00; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; box-sizing: border-box; cursor: text; border-radius: 4px; transition: background 0.15s, color 0.15s; } .ruleFormulas:hover { background: #f1f7e6; } .ruleFormulas.editing { padding: 4px 6px; background: #eef5e0; } .ruleStopIfTrue { width: 18%; text-align: center; padding: 6px 4px; box-sizing: border-box; } .ruleStopIfTrue input[type="checkbox"] { accent-color: #4a7a00; } .ruleRemove { width: 14%; text-align: center; padding: 6px 4px; box-sizing: border-box; } .removeRuleBtn { width: 24px; height: 24px; border: none; background: none; color: #ccc; font-size: 16px; cursor: pointer; border-radius: 4px; line-height: 24px; transition: all 0.15s; } .removeRuleBtn:hover { background: #fee; color: #e55; } .ruleRangeInput { width: 100%; height: 28px; padding: 0 8px; border: 1px solid #8fb44a; border-radius: 4px; background: #fff; color: #333; font-size: 11px; box-sizing: border-box; outline: none; } .ruleRangeInput:focus { box-shadow: 0 0 0 2px rgba(74,122,0,0.15); } .sparkline-badge { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; margin-right: 6px; vertical-align: middle; color: #fff; } .badge-bullet { background: #4a7a00; } .badge-pie { background: #c8a000; } .badge-hbar { background: #6fa300; } .badge-line { background: #8bc34a; } .badge-column { background: #b5cc18; color: #333; } .badge-winloss { background: #7030A0; } .badge-lollipop { background: #8b0000; } .badge-default { background: #999; }