diff --git a/apps/spreadsheeteditor/main/app/controller/Toolbar.js b/apps/spreadsheeteditor/main/app/controller/Toolbar.js index 58032aed3..dc7b04cb4 100644 --- a/apps/spreadsheeteditor/main/app/controller/Toolbar.js +++ b/apps/spreadsheeteditor/main/app/controller/Toolbar.js @@ -62,7 +62,8 @@ define([ 'spreadsheeteditor/main/app/view/ScaleDialog', 'spreadsheeteditor/main/app/view/FormatRulesManagerDlg', 'spreadsheeteditor/main/app/view/SlicerAddDialog', - 'spreadsheeteditor/main/app/view/AdvancedSeparatorDialog' + 'spreadsheeteditor/main/app/view/AdvancedSeparatorDialog', + 'spreadsheeteditor/main/app/view/CreateSparklineDialog' ], function () { 'use strict'; SSE.Controllers.Toolbar = Backbone.Controller.extend(_.extend({ @@ -79,6 +80,7 @@ define([ 'Toolbar': { 'change:compact': this.onClickChangeCompact.bind(me), 'add:chart' : this.onSelectChart, + 'add:spark' : this.onSelectSpark, 'insert:textart': this.onInsertTextart, 'change:scalespn': this.onClickChangeScaleInMenu.bind(me), 'click:customscale': this.onScaleClick.bind(me), @@ -191,7 +193,8 @@ define([ pgmargins: undefined, pgorient: undefined, lock_doc: undefined, - cf_locked: [] + cf_locked: [], + selectedCells: 0 }; this.binding = {}; @@ -416,6 +419,7 @@ define([ this.api.asc_registerCallback('asc_onContextMenu', _.bind(this.onContextMenu, this)); Common.NotificationCenter.on('storage:image-load', _.bind(this.openImageFromStorage, this)); Common.NotificationCenter.on('storage:image-insert', _.bind(this.insertImageFromStorage, this)); + this.api.asc_registerCallback('asc_onSelectionMathChanged', _.bind(this.onApiMathChanged, this)); } this.api.asc_registerCallback('asc_onInitEditorStyles', _.bind(this.onApiInitEditorStyles, this)); this.api.asc_registerCallback('asc_onCoAuthoringDisconnect',_.bind(this.onApiCoAuthoringDisconnect, this)); @@ -1078,58 +1082,84 @@ define([ if (!this.editMode) return; var me = this, info = me.api.asc_getCellInfo(), - seltype = info.asc_getSelectionType(), - isSpark = (group == 'menu-chart-group-sparkcolumn' || group == 'menu-chart-group-sparkline' || group == 'menu-chart-group-sparkwin'); + seltype = info.asc_getSelectionType(); if (me.api) { var win, props; - if (isSpark && (seltype==Asc.c_oAscSelectionType.RangeCells || seltype==Asc.c_oAscSelectionType.RangeCol || - seltype==Asc.c_oAscSelectionType.RangeRow || seltype==Asc.c_oAscSelectionType.RangeMax)) { + var ischartedit = ( seltype == Asc.c_oAscSelectionType.RangeChart || seltype == Asc.c_oAscSelectionType.RangeChartText); + props = me.api.asc_getChartObject(true); // don't lock chart object + if (props) { + (ischartedit) ? props.changeType(type) : props.putType(type); + var range = props.getRange(), + isvalid = (!_.isEmpty(range)) ? me.api.asc_checkDataRange(Asc.c_oAscSelectionDialogType.Chart, range, true, props.getInRows(), props.getType()) : Asc.c_oAscError.ID.No; + if (isvalid == Asc.c_oAscError.ID.No) { + (ischartedit) ? me.api.asc_editChartDrawingObject(props) : me.api.asc_addChartDrawingObject(props); + } else { + var msg = me.txtInvalidRange; + switch (isvalid) { + case isvalid == Asc.c_oAscError.ID.StockChartError: + msg = me.errorStockChart; + break; + case isvalid == Asc.c_oAscError.ID.MaxDataSeriesError: + msg = me.errorMaxRows; + break; + case isvalid == Asc.c_oAscError.ID.ComboSeriesError: + msg = me.errorComboSeries; + break; + } + Common.UI.warning({ + msg: msg, + callback: function() { + _.defer(function(btn) { + Common.NotificationCenter.trigger('edit:complete', me.toolbar); + }) + } + }); + } + } + } + Common.NotificationCenter.trigger('edit:complete', this.toolbar); + }, + + onSelectSpark: function(type) { + if (!this.editMode) return; + var me = this, + info = me.api.asc_getCellInfo(), + seltype = info.asc_getSelectionType(); + + if (me.api) { + if (seltype==Asc.c_oAscSelectionType.RangeCells || seltype==Asc.c_oAscSelectionType.RangeCol || + seltype==Asc.c_oAscSelectionType.RangeRow || seltype==Asc.c_oAscSelectionType.RangeMax) { var sparkLineInfo = info.asc_getSparklineInfo(); if (!!sparkLineInfo) { var props = new Asc.sparklineGroup(); props.asc_setType(type); this.api.asc_setSparklineGroup(sparkLineInfo.asc_getId(), props); } else { - // add sparkline - } - } else if (!isSpark) { - var ischartedit = ( seltype == Asc.c_oAscSelectionType.RangeChart || seltype == Asc.c_oAscSelectionType.RangeChartText); - props = me.api.asc_getChartObject(true); // don't lock chart object - if (props) { - (ischartedit) ? props.changeType(type) : props.putType(type); - var range = props.getRange(), - isvalid = (!_.isEmpty(range)) ? me.api.asc_checkDataRange(Asc.c_oAscSelectionDialogType.Chart, range, true, props.getInRows(), props.getType()) : Asc.c_oAscError.ID.No; - if (isvalid == Asc.c_oAscError.ID.No) { - (ischartedit) ? me.api.asc_editChartDrawingObject(props) : me.api.asc_addChartDrawingObject(props); - } else { - var msg = me.txtInvalidRange; - switch (isvalid) { - case isvalid == Asc.c_oAscError.ID.StockChartError: - msg = me.errorStockChart; - break; - case isvalid == Asc.c_oAscError.ID.MaxDataSeriesError: - msg = me.errorMaxRows; - break; - case isvalid == Asc.c_oAscError.ID.ComboSeriesError: - msg = me.errorComboSeries; - break; - } - Common.UI.warning({ - msg: msg, - callback: function() { - _.defer(function(btn) { - Common.NotificationCenter.trigger('edit:complete', me.toolbar); - }) + var me = this; + (new SSE.Views.CreateSparklineDialog( + { + api: me.api, + props: {selectedCells: me._state.selectedCells}, + handler: function(result, settings) { + if (result == 'ok' && settings) { + me.view && me.view.fireEvent('insertspark', me.view); + if (settings.destination) + me.api.asc_addSparklineGroup(type, settings.source, settings.destination); + } + Common.NotificationCenter.trigger('edit:complete', me); } - }); - } + })).show(); } } } Common.NotificationCenter.trigger('edit:complete', this.toolbar); }, + onApiMathChanged: function(info) { + this._state.selectedCells = info.asc_getCount(); // not empty cells + }, + onInsertTextart: function (data) { if (this.api) { this.toolbar.fireEvent('inserttextart', this.toolbar); diff --git a/apps/spreadsheeteditor/main/app/template/Toolbar.template b/apps/spreadsheeteditor/main/app/template/Toolbar.template index 1b359be7d..55a1bf9a0 100644 --- a/apps/spreadsheeteditor/main/app/template/Toolbar.template +++ b/apps/spreadsheeteditor/main/app/template/Toolbar.template @@ -130,6 +130,7 @@ <span class="btn-slot text x-huge" id="slot-btn-instext"></span> <span class="btn-slot text x-huge" id="slot-btn-instextart"></span> <span class="btn-slot text x-huge" id="slot-btn-inschart"></span> + <span class="btn-slot text x-huge" id="slot-btn-inssparkline"></span> </div> <div class="separator long"></div> <div class="group"> diff --git a/apps/spreadsheeteditor/main/app/view/CreateSparklineDialog.js b/apps/spreadsheeteditor/main/app/view/CreateSparklineDialog.js new file mode 100644 index 000000000..271fe84a4 --- /dev/null +++ b/apps/spreadsheeteditor/main/app/view/CreateSparklineDialog.js @@ -0,0 +1,251 @@ +/* + * + * (c) Copyright Ascensio System SIA 2010-2021 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing content are licensed under the terms of the + * Creative Commons Attribution-ShareAlike 4.0 International. See the License + * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + * + */ + +/** + * CreateSparklineDialog.js + * + * Created by Julia Radzhabova on 23.03.2021 + * Copyright (c) 2021 Ascensio System SIA. All rights reserved. + * + */ +define([ + 'common/main/lib/util/utils', + 'common/main/lib/component/InputField', + 'common/main/lib/view/AdvancedSettingsWindow' +], function () { 'use strict'; + + SSE.Views.CreateSparklineDialog = Common.Views.AdvancedSettingsWindow.extend(_.extend({ + options: { + contentWidth: 310, + height: 195 + }, + + initialize : function(options) { + var me = this; + + _.extend(this.options, { + title: this.textTitle, + template: [ + '<div class="box" style="height:' + (me.options.height - 85) + 'px;">', + '<div class="content-panel" style="padding: 0 10px;"><div class="inner-content">', + '<div class="settings-panel active">', + '<table cols="1" style="width: 100%;">', + '<tr>', + '<td style="padding-bottom: 2px;">', + '<label class="input-label">' + me.textDataRange + '</label>', + '</td>', + '</tr>', + '<tr>', + '<td class="padding-large">', + '<div id="create-spark-input-source" class="input-row" style=""></div>', + '</td>', + '</tr>', + '<tr>', + '<td>', + '<label class="input-label">' + me.textDestination + '</label>', + '</td>', + '</tr>', + '<tr>', + '<td>', + '<div id="create-spark-input-dest" class="input-row" style=""></div>', + '</td>', + '</tr>', + '</table>', + '</div></div>', + '</div>', + '</div>' + ].join('') + }, options); + + this.api = options.api; + this.props = options.props; + + this.options.handler = function(result, value) { + if ( result != 'ok' || this.isRangeValid() ) { + if (options.handler) + options.handler.call(this, result, value); + return; + } + return true; + }; + + this.dataSourceValid = ''; + this.dataDestValid = ''; + + Common.Views.AdvancedSettingsWindow.prototype.initialize.call(this, this.options); + }, + + render: function() { + Common.Views.AdvancedSettingsWindow.prototype.render.call(this); + var me = this; + + this.txtSourceRange = new Common.UI.InputFieldBtn({ + el : $('#create-spark-input-source'), + name : 'range', + style : 'width: 100%;', + btnHint : this.textSelectData, + allowBlank : true, + validateOnChange: true + }); + this.txtSourceRange.on('button:click', _.bind(this.onSelectData, this, 'source')); + + this.txtDestRange = new Common.UI.InputFieldBtn({ + el : $('#create-spark-input-dest'), + name : 'range', + style : 'width: 100%;', + btnHint : this.textSelectData, + allowBlank : true, + validateOnChange: true, + validateOnBlur: false + }); + this.txtDestRange.on('button:click', _.bind(this.onSelectData, this, 'dest')); + + this.afterRender(); + }, + + getFocusedComponents: function() { + return [this.txtSourceRange, this.txtDestRange]; + }, + + afterRender: function() { + this._setDefaults(this.props); + }, + + _setDefaults: function (props) { + var cells = props ? props.selectedCells : 0; + var me = this; + if (cells>0) { + var range = this.api.asc_getActiveRangeStr(Asc.referenceType.R); + this.txtSourceRange.setValue(range); + this.dataSourceValid = range; + setTimeout(function(){me.txtDestRange.focus();}, 100); + } else { + var range = this.api.asc_getActiveRangeStr(Asc.referenceType.A); + this.txtDestRange.setValue(range); + this.dataDestValid = range; + setTimeout(function(){me.txtSourceRange.focus();}, 100); + } + this.txtSourceRange.validation = function(value) { + var isvalid = me.api.asc_checkDataRange(Asc.c_oAscSelectionDialogType.Chart, value, false); + return (isvalid==Asc.c_oAscError.ID.DataRangeError) ? me.textInvalidRange : true; + }; + this.txtDestRange.validation = function(value) { + var isvalid = me.api.asc_checkDataRange(Asc.c_oAscSelectionDialogType.FormatTable, value, false); + return (isvalid==Asc.c_oAscError.ID.DataRangeError) ? me.textInvalidRange : true; + }; + }, + + getSettings: function () { + var source = this.txtSourceRange.getValue(), + dest = this.txtDestRange.getValue(); + + return {source: source, destination: dest}; + }, + + isRangeValid: function() { + var isvalid = true, + txtError = ''; + + if (_.isEmpty(this.txtSourceRange.getValue())) { + isvalid = false; + txtError = this.txtEmpty; + } else { + isvalid = this.api.asc_checkDataRange(Asc.c_oAscSelectionDialogType.Chart, this.txtSourceRange.getValue()); + isvalid = (isvalid == Asc.c_oAscError.ID.No); + !isvalid && (txtError = this.textInvalidRange); + } + if (!isvalid) { + this.txtSourceRange.showError([txtError]); + this.txtSourceRange.focus(); + return isvalid; + } + + if (_.isEmpty(this.txtDestRange.getValue())) { + isvalid = false; + txtError = this.txtEmpty; + } else { + isvalid = this.api.asc_checkDataRange(Asc.c_oAscSelectionDialogType.FormatTable, this.txtDestRange.getValue()); + isvalid = (isvalid == Asc.c_oAscError.ID.No); + !isvalid && (txtError = this.textInvalidRange); + } + if (!isvalid) { + this.txtDestRange.showError([txtError]); + this.txtDestRange.focus(); + return isvalid; + } + + return isvalid; + }, + + onSelectData: function(type) { + var me = this, + txtRange = (type=='source') ? me.txtSourceRange : me.txtDestRange; + + if (me.api) { + var handlerDlg = function(dlg, result) { + if (result == 'ok') { + var txt = dlg.getSettings(); + (type=='source') ? (me.dataSourceValid = txt) : (me.dataDestValid = txt); + txtRange.setValue(txt); + txtRange.checkValidate(); + } + }; + + var win = new SSE.Views.CellRangeDialog({ + handler: handlerDlg + }).on('close', function() { + me.show(); + _.delay(function(){ + txtRange.focus(); + },1); + }); + + var xy = me.$window.offset(); + me.hide(); + win.show(xy.left + 160, xy.top + 125); + win.setSettings({ + api : me.api, + range : (!_.isEmpty(txtRange.getValue()) && (txtRange.checkValidate()==true)) ? txtRange.getValue() : ((type=='source') ? me.dataSourceValid : me.dataDestValid), + type : (type=='source') ? Asc.c_oAscSelectionDialogType.Chart : Asc.c_oAscSelectionDialogType.FormatTable + }); + } + }, + + textTitle: 'Create Sparklines', + textDataRange: 'Source data range', + textSelectData: 'Select data', + textDestination: 'Choose, where to place the sparklines', + txtEmpty: 'This field is required', + textInvalidRange: 'Invalid cells range' + }, SSE.Views.CreateSparklineDialog || {})) +}); \ No newline at end of file diff --git a/apps/spreadsheeteditor/main/app/view/Toolbar.js b/apps/spreadsheeteditor/main/app/view/Toolbar.js index e3999e20b..db0cfc733 100644 --- a/apps/spreadsheeteditor/main/app/view/Toolbar.js +++ b/apps/spreadsheeteditor/main/app/view/Toolbar.js @@ -722,6 +722,15 @@ define([ menu : true }); + me.btnInsertSparkline = new Common.UI.Button({ + id : 'tlbtn-insertsparkline', + cls : 'btn-toolbar x-huge icon-top', + iconCls : 'toolbar__icon btn-sparkline', + lock : [_set.editCell, _set.selChart, _set.selChartText, _set.selImage, _set.selShape, _set.selSlicer, _set.multiselect, _set.lostConnect, _set.coAuth, _set.coAuthText, _set.editPivot], + caption : me.capInsertSpark, + menu : true + }); + me.btnInsertShape = new Common.UI.Button({ id : 'tlbtn-insertshape', cls : 'btn-toolbar x-huge icon-top', @@ -1461,7 +1470,7 @@ define([ me.btnInsertText, me.btnInsertTextArt, me.btnSortUp, me.btnSortDown, me.btnSetAutofilter, me.btnClearAutofilter, me.btnTableTemplate, me.btnPercentStyle, me.btnCurrencyStyle, me.btnDecDecimal, me.btnAddCell, me.btnDeleteCell, me.btnCondFormat, me.cmbNumberFormat, me.btnBorders, me.btnInsertImage, me.btnInsertHyperlink, - me.btnInsertChart, me.btnColorSchemas, + me.btnInsertChart, me.btnColorSchemas, me.btnInsertSparkline, me.btnCopy, me.btnPaste, me.listStyles, me.btnPrint, /*me.btnSave,*/ me.btnClearStyle, me.btnCopyStyle, me.btnPageMargins, me.btnPageSize, me.btnPageOrient, me.btnPrintArea, me.btnPrintTitles, me.btnImgAlign, me.btnImgBackward, me.btnImgForward, me.btnImgGroup, me.btnScale @@ -1661,6 +1670,7 @@ define([ _injectComponent('#slot-btn-colorschemas', this.btnColorSchemas); _injectComponent('#slot-btn-search', this.btnSearch); _injectComponent('#slot-btn-inschart', this.btnInsertChart); + _injectComponent('#slot-btn-inssparkline', this.btnInsertSparkline); _injectComponent('#slot-field-styles', this.listStyles); _injectComponent('#slot-btn-chart', this.btnEditChart); _injectComponent('#slot-btn-chart-data', this.btnEditChartData); @@ -1720,6 +1730,7 @@ define([ _updateHint(this.btnInsertTable, this.tipInsertTable); _updateHint(this.btnInsertImage, this.tipInsertImage); _updateHint(this.btnInsertChart, this.tipInsertChartSpark); + _updateHint(this.btnInsertSparkline, this.tipInsertSpark); _updateHint(this.btnInsertText, this.tipInsertText); _updateHint(this.btnInsertTextArt, this.tipInsertTextart); _updateHint(this.btnInsertHyperlink, this.tipInsertHyperlink + Common.Utils.String.platformKey('Ctrl+K')); @@ -1919,6 +1930,34 @@ define([ this.btnInsertChart.menu.on('show:before', onShowBefore); } + if ( this.btnInsertSparkline ) { + this.btnInsertSparkline.setMenu(new Common.UI.Menu({ + style: 'width: 166px;padding: 5px 0 10px;', + items: [ + { template: _.template('<div id="id-toolbar-menu-insertspark" class="menu-insertchart"></div>') } + ] + })); + + var onShowBefore = function(menu) { + var picker = new Common.UI.DataView({ + el: $('#id-toolbar-menu-insertspark'), + parentMenu: menu, + showLast: false, + restoreHeight: 50, + // groups: new Common.UI.DataViewGroupStore(Common.define.chartData.getSparkGroupData()), + store: new Common.UI.DataViewStore(Common.define.chartData.getSparkData()), + itemTemplate: _.template('<div id="<%= id %>" class="item-chartlist"><svg width="40" height="40" class=\"icon\"><use xlink:href=\"#chart-<%= iconCls %>\"></use></svg></div>') + }); + picker.on('item:click', function (picker, item, record, e) { + if (record) + me.fireEvent('add:spark', [record.get('type')]); + if (e.type !== 'click') menu.hide(); + }); + menu.off('show:before', onShowBefore); + }; + this.btnInsertSparkline.menu.on('show:before', onShowBefore); + } + if (this.btnInsertTextArt) { var onShowBeforeTextArt = function (menu) { var collection = SSE.getCollection('Common.Collections.TextArt'); @@ -2648,6 +2687,8 @@ define([ tipEditChartData: 'Select Data', tipEditChartType: 'Change Chart Type', textAutoColor: 'Automatic', - textItems: 'Items' + textItems: 'Items', + tipInsertSpark: 'Insert sparkline', + capInsertSpark: 'Sparklines' }, SSE.Views.Toolbar || {})); }); \ No newline at end of file diff --git a/apps/spreadsheeteditor/main/locale/en.json b/apps/spreadsheeteditor/main/locale/en.json index 356da4318..91f67c292 100644 --- a/apps/spreadsheeteditor/main/locale/en.json +++ b/apps/spreadsheeteditor/main/locale/en.json @@ -1647,6 +1647,12 @@ "SSE.Views.CreatePivotDialog.textSelectData": "Select data", "SSE.Views.CreatePivotDialog.textTitle": "Create Pivot Table", "SSE.Views.CreatePivotDialog.txtEmpty": "This field is required", + "SSE.Views.CreateSparklineDialog.textTitle": "Create Sparklines", + "SSE.Views.CreateSparklineDialog.textDataRange": "Source data range", + "SSE.Views.CreateSparklineDialog.textSelectData": "Select data", + "SSE.Views.CreateSparklineDialog.textDestination": "Choose, where to place the sparklines", + "SSE.Views.CreateSparklineDialog.txtEmpty": "This field is required", + "SSE.Views.CreateSparklineDialog.textInvalidRange": "Invalid cells range", "SSE.Views.DataTab.capBtnGroup": "Group", "SSE.Views.DataTab.capBtnTextCustomSort": "Custom Sort", "SSE.Views.DataTab.capBtnTextDataValidation": "Data Validation", @@ -3283,6 +3289,8 @@ "SSE.Views.Toolbar.txtTime": "Time", "SSE.Views.Toolbar.txtUnmerge": "Unmerge Cells", "SSE.Views.Toolbar.txtYen": "¥ Yen", + "SSE.Views.Toolbar.tipInsertSpark": "Insert sparkline", + "SSE.Views.Toolbar.capInsertSpark": "Sparklines", "SSE.Views.Top10FilterDialog.textType": "Show", "SSE.Views.Top10FilterDialog.txtBottom": "Bottom", "SSE.Views.Top10FilterDialog.txtBy": "by", diff --git a/apps/spreadsheeteditor/main/resources/img/toolbar/1.25x/big/btn-sparkline.png b/apps/spreadsheeteditor/main/resources/img/toolbar/1.25x/big/btn-sparkline.png new file mode 100644 index 000000000..e03dc660a Binary files /dev/null and b/apps/spreadsheeteditor/main/resources/img/toolbar/1.25x/big/btn-sparkline.png differ diff --git a/apps/spreadsheeteditor/main/resources/img/toolbar/1.5x/big/btn-sparkline.png b/apps/spreadsheeteditor/main/resources/img/toolbar/1.5x/big/btn-sparkline.png new file mode 100644 index 000000000..dd49df8fb Binary files /dev/null and b/apps/spreadsheeteditor/main/resources/img/toolbar/1.5x/big/btn-sparkline.png differ diff --git a/apps/spreadsheeteditor/main/resources/img/toolbar/1.75x/big/btn-sparkline.png b/apps/spreadsheeteditor/main/resources/img/toolbar/1.75x/big/btn-sparkline.png new file mode 100644 index 000000000..8b7a09c05 Binary files /dev/null and b/apps/spreadsheeteditor/main/resources/img/toolbar/1.75x/big/btn-sparkline.png differ diff --git a/apps/spreadsheeteditor/main/resources/img/toolbar/1x/big/btn-sparkline.png b/apps/spreadsheeteditor/main/resources/img/toolbar/1x/big/btn-sparkline.png new file mode 100644 index 000000000..25fb558ea Binary files /dev/null and b/apps/spreadsheeteditor/main/resources/img/toolbar/1x/big/btn-sparkline.png differ diff --git a/apps/spreadsheeteditor/main/resources/img/toolbar/2x/big/btn-sparkline.png b/apps/spreadsheeteditor/main/resources/img/toolbar/2x/big/btn-sparkline.png new file mode 100644 index 000000000..9a59a5514 Binary files /dev/null and b/apps/spreadsheeteditor/main/resources/img/toolbar/2x/big/btn-sparkline.png differ