Sparkline Rules

The sparkline conditional format rule renders sparkline charts directly in cells. It supports various sparkline types such as Pie, Line, Column, Bullet, HBar, WinLoss, Lollipop Variance, and more.

SpreadJS provides a SparklineRule as part of its conditional formatting, which renders sparkline charts in cells based on conditional formatting. You can use the addSparklineRule API to create a sparkline rule. The rule supports built-in identifiers $CF_RANGE$ (refers to the cell range of the rule) and @ (represents the current cell's index or value). For the configurable options of each individual Sparkline rule, please refer to GC.Spread.Sheets.Sparklines.ISparklineOptions. For example: Additionally, Sparkline configurable options support parameter arrays. The parameter order must align with the sequence of parameters defined in the Sparkline function's description.
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" /> <link rel="stylesheet" type="text/css" href="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css"> <script src="$DEMOROOT$/en/purejs/node_modules/@mescius/spread-sheets/dist/gc.spread.sheets.all.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; }