From 05d7fc12e162ead20ee7fc53b6b27178539dddb1 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Tue, 17 Sep 2019 14:24:50 +0200 Subject: [PATCH 1/3] PAYARA-4097 fixed grid layout, JS file and data structure cleanup --- appserver/monitoring-console/webapp/pom.xml | 7 +- .../webapp/src/main/webapp/index.html | 30 +- .../webapp/{mc-render.js => mc-line-chart.js} | 32 +- .../webapp/src/main/webapp/mc-main.js | 61 ++ .../src/main/webapp/{mc-ui.js => mc-model.js} | 232 ++++---- .../main/webapp/{mc-page.js => mc-view.js} | 214 ++++--- .../src/main/webapp/monitoring-console.css | 1 - .../src/main/webapp/monitoring-console.js | 529 ++++++++++-------- 8 files changed, 624 insertions(+), 482 deletions(-) rename appserver/monitoring-console/webapp/src/main/webapp/{mc-render.js => mc-line-chart.js} (88%) create mode 100644 appserver/monitoring-console/webapp/src/main/webapp/mc-main.js rename appserver/monitoring-console/webapp/src/main/webapp/{mc-ui.js => mc-model.js} (83%) rename appserver/monitoring-console/webapp/src/main/webapp/{mc-page.js => mc-view.js} (63%) diff --git a/appserver/monitoring-console/webapp/pom.xml b/appserver/monitoring-console/webapp/pom.xml index 6e8498dc013..ebaa0591d16 100644 --- a/appserver/monitoring-console/webapp/pom.xml +++ b/appserver/monitoring-console/webapp/pom.xml @@ -113,9 +113,10 @@ true src/main/webapp/monitoring-console.js - src/main/webapp/mc-ui.js - src/main/webapp/mc-render.js - src/main/webapp/mc-page.js + src/main/webapp/mc-main.js + src/main/webapp/mc-model.js + src/main/webapp/mc-line-chart.js + src/main/webapp/mc-view.js diff --git a/appserver/monitoring-console/webapp/src/main/webapp/index.html b/appserver/monitoring-console/webapp/src/main/webapp/index.html index 71d09d6124a..1b6f8830267 100644 --- a/appserver/monitoring-console/webapp/src/main/webapp/index.html +++ b/appserver/monitoring-console/webapp/src/main/webapp/index.html @@ -47,12 +47,12 @@ - - + + - +
@@ -62,19 +62,19 @@ - +     - - - - + + + +     - - - - + + + +
@@ -101,10 +101,10 @@ Layout Span - + Column - + @@ -125,7 +125,7 @@
diff --git a/appserver/monitoring-console/webapp/src/main/webapp/mc-render.js b/appserver/monitoring-console/webapp/src/main/webapp/mc-line-chart.js similarity index 88% rename from appserver/monitoring-console/webapp/src/main/webapp/mc-render.js rename to appserver/monitoring-console/webapp/src/main/webapp/mc-line-chart.js index 87147b9aed7..10f6572acfc 100644 --- a/appserver/monitoring-console/webapp/src/main/webapp/mc-render.js +++ b/appserver/monitoring-console/webapp/src/main/webapp/mc-line-chart.js @@ -40,7 +40,7 @@ /*jshint esversion: 8 */ -var MonitoringConsoleRender = (function() { +MonitoringConsole.LineChart = (function() { const DEFAULT_BG_COLORS = [ 'rgba(153, 102, 255, 0.2)', @@ -60,8 +60,8 @@ var MonitoringConsoleRender = (function() { ]; function createMinimumLineDataset(data, points, lineColor) { - var min = data.observedMin; - var minPoints = [{t:points[0].t, y:min}, {t:points[points.length-1].t, y:min}]; + let min = data.observedMin; + let minPoints = [{t:points[0].t, y:min}, {t:points[points.length-1].t, y:min}]; return { data: minPoints, @@ -75,8 +75,8 @@ var MonitoringConsoleRender = (function() { } function createMaximumLineDataset(data, points, lineColor) { - var max = data.observedMax; - var maxPoints = [{t:points[0].t, y:max}, {t:points[points.length-1].t, y:max}]; + let max = data.observedMax; + let maxPoints = [{t:points[0].t, y:max}, {t:points[points.length-1].t, y:max}]; return { data: maxPoints, @@ -89,8 +89,8 @@ var MonitoringConsoleRender = (function() { } function createAverageLineDataset(data, points, lineColor) { - var avg = data.observedSum / data.observedValues; - var avgPoints = [{t:points[0].t, y:avg}, {t:points[points.length-1].t, y:avg}]; + let avg = data.observedSum / data.observedValues; + let avgPoints = [{t:points[0].t, y:avg}, {t:points[points.length-1].t, y:avg}]; return { data: avgPoints, @@ -117,7 +117,7 @@ var MonitoringConsoleRender = (function() { if (!points1d) return []; let points2d = new Array(points1d.length / 2); - for (var i = 0; i < points2d.length; i++) { + for (let i = 0; i < points2d.length; i++) { points2d[i] = { t: new Date(points1d[i*2]), y: points1d[i*2+1] }; } return points2d; @@ -127,7 +127,7 @@ var MonitoringConsoleRender = (function() { if (!points1d) return []; let points2d = new Array((points1d.length / 2) - 1); - for (var i = 0; i < points2d.length; i++) { + for (let i = 0; i < points2d.length; i++) { let t0 = points1d[i*2]; let t1 = points1d[i*2+2]; let y0 = points1d[i*2+1]; @@ -144,7 +144,7 @@ var MonitoringConsoleRender = (function() { if (widget.options.perSec) { return [ createMainLineDataset(data, createInstancePerSecPoints(data.points), lineColor, bgColor) ]; } - let points = createInstancePoints(data.points) + let points = createInstancePoints(data.points); let datasets = []; datasets.push(createMainLineDataset(data, points, lineColor, bgColor)); if (points.length > 0 && widget.options.drawAvgLine) { @@ -160,12 +160,12 @@ var MonitoringConsoleRender = (function() { } return { - chart: function(update) { - var data = update.data; - var widget = update.widget; - var chart = update.chart(); - var datasets = []; - for (var j = 0; j < data.length; j++) { + onDataUpdate: function(update) { + let data = update.data; + let widget = update.widget; + let chart = update.chart(); + let datasets = []; + for (let j = 0; j < data.length; j++) { datasets = datasets.concat( createInstanceDatasets(widget, data[j], DEFAULT_LINE_COLORS[j], DEFAULT_BG_COLORS[j])); } diff --git a/appserver/monitoring-console/webapp/src/main/webapp/mc-main.js b/appserver/monitoring-console/webapp/src/main/webapp/mc-main.js new file mode 100644 index 00000000000..5094ed644c9 --- /dev/null +++ b/appserver/monitoring-console/webapp/src/main/webapp/mc-main.js @@ -0,0 +1,61 @@ +/* + DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + + Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + + The contents of this file are subject to the terms of either the GNU + General Public License Version 2 only ("GPL") or the Common Development + and Distribution License("CDDL") (collectively, the "License"). You + may not use this file except in compliance with the License. You can + obtain a copy of the License at + https://github.com/payara/Payara/blob/master/LICENSE.txt + See the License for the specific + language governing permissions and limitations under the License. + + When distributing the software, include this License Header Notice in each + file and include the License file at glassfish/legal/LICENSE.txt. + + GPL Classpath Exception: + The Payara Foundation designates this particular file as subject to the "Classpath" + exception as provided by the Payara Foundation in the GPL Version 2 section of the License + file that accompanied this code. + + Modifications: + If applicable, add the following below the License Header, with the fields + enclosed by brackets [] replaced by your own identifying information: + "Portions Copyright [year] [name of copyright owner]" + + Contributor(s): + If you wish your version of this file to be governed by only the CDDL or + only the GPL Version 2, indicate your decision by adding "[Contributor] + elects to include this software in this distribution under the [CDDL or GPL + Version 2] license." If you don't indicate a single choice of license, a + recipient has the option to distribute your version of this file under + either the CDDL, the GPL Version 2 or to extend the choice of license to + its licensees as provided above. However, if you add GPL Version 2 code + and therefore, elected the GPL Version 2 license, then the option applies + only if the new code is made subject to such option by the copyright + holder. +*/ + +/*jshint esversion: 8 */ + +Chart.defaults.global.defaultFontColor = "#fff"; + +/** + * The different parts of the Monitoring Console are added as the below properties by the individual files. + */ +var MonitoringConsole = { + /** + * Functions to update the actual HTML page of the MC + **/ + View: undefined, + /** + * Functions of manipulate the model of the MC (often returns a layout that is applied to the View) + **/ + Model: undefined, + /** + * Functions specifically to take the data and prepare the display of a line chart using the underlying charting library. + **/ + LineChart: undefined, +}; diff --git a/appserver/monitoring-console/webapp/src/main/webapp/mc-ui.js b/appserver/monitoring-console/webapp/src/main/webapp/mc-model.js similarity index 83% rename from appserver/monitoring-console/webapp/src/main/webapp/mc-ui.js rename to appserver/monitoring-console/webapp/src/main/webapp/mc-model.js index 8154041062f..19987aff060 100644 --- a/appserver/monitoring-console/webapp/src/main/webapp/mc-ui.js +++ b/appserver/monitoring-console/webapp/src/main/webapp/mc-model.js @@ -40,71 +40,12 @@ /*jshint esversion: 8 */ -Chart.defaults.global.defaultFontColor = "#fff"; - -/** - * A utility with 'static' helper functions that have no side effect. - * - * Extracting such function into this object should help organise the code and allow context independent testing - * of the helper functions in the browser. - * - * The MonitoringConsole object is dependent on this object but not vice versa. - */ -var MonitoringConsoleUtils = (function() { - - return { - - getSpan: function(widget, numberOfColumns, currentColumn) { - let span = widget.grid && widget.grid.span ? widget.grid.span : 1; - if (typeof span === 'string') { - if (span === 'full') { - span = numberOfColumns; - } else { - span = parseInt(span); - } - } - if (span > numberOfColumns - currentColumn) { - span = numberOfColumns - currentColumn; - } - return span; - }, - - getPageId: function(name) { - return name.replace(/[^-a-zA-Z0-9]/g, '_').toLowerCase(); - }, - - getTimeLabel: function(value, index, values) { - if (values.length == 0 || index == 0) - return value; - let span = values[values.length -1].value - values[0].value; - if (span < 120000) { // less then two minutes - let lastMinute = new Date(values[index-1].value).getMinutes(); - return new Date(values[index].value).getMinutes() != lastMinute ? value : ''+new Date(values[index].value).getSeconds(); - } - return value; - }, - - readTextFile: function(file) { - return new Promise(function(resolve, reject){ - var reader = new FileReader(); - reader.onload = function(evt){ - resolve(evt.target.result); - }; - reader.onerror = function(err) { - reject(err); - }; - reader.readAsText(file); - }); - }, - }; -})(); - /** * The object that manages the internal state of the monitoring console page. * - * It depends on the MonitoringConsoleUtils object. + * It depends on the MonitoringConsole.Utils object. */ -var MonitoringConsole = (function() { +MonitoringConsole.Model = (function() { /** * Key used in local stage for the userInterface */ @@ -127,6 +68,10 @@ var MonitoringConsole = (function() { settings: {}, }; + function getPageId(name) { + return name.replace(/[^-a-zA-Z0-9]/g, '_').toLowerCase(); + } + /** * Internal API for managing set model of the user interface. @@ -156,7 +101,7 @@ var MonitoringConsole = (function() { */ function sanityCheckPage(page) { if (!page.id) - page.id = MonitoringConsoleUtils.getPageId(page.name); + page.id = getPageId(page.name); if (!page.widgets) page.widgets = {}; if (!page.numberOfColumns || page.numberOfColumns < 1) @@ -203,7 +148,7 @@ var MonitoringConsole = (function() { function doCreate(name) { if (!name) throw "New page must have a unique name"; - var id = MonitoringConsoleUtils.getPageId(name); + var id = getPageId(name); if (pages[id]) throw "A page with name "+name+" already exist"; let page = sanityCheckPage({name: name}); @@ -249,6 +194,106 @@ var MonitoringConsole = (function() { let ui = { pages: pages, settings: settings }; return prettyPrint ? JSON.stringify(ui, null, 2) : JSON.stringify(ui); } + + function readTextFile(file) { + return new Promise(function(resolve, reject) { + let reader = new FileReader(); + reader.onload = function(evt){ + resolve(evt.target.result); + }; + reader.onerror = function(err) { + reject(err); + }; + reader.readAsText(file); + }); + } + + function doLayout(columns) { + let page = pages[currentPageId]; + if (!page) + return []; + if (columns) + page.numberOfColumns = columns; + let numberOfColumns = page.numberOfColumns || 1; + let widgets = page.widgets; + // init temporary and result data structure + let widgetsByColumn = new Array(numberOfColumns); + var layout = new Array(numberOfColumns); + for (let col = 0; col < numberOfColumns; col++) { + widgetsByColumn[col] = []; + layout[col] = []; + } + // organise widgets in columns + Object.values(widgets).forEach(function(widget) { + let column = widget.grid && widget.grid.column ? widget.grid.column : 0; + widgetsByColumn[Math.min(Math.max(column, 0), widgetsByColumn.length - 1)].push(widget); + }); + // order columns by item position + for (let col = 0; col < numberOfColumns; col++) { + widgetsByColumn[col] = widgetsByColumn[col].sort(function (a, b) { + if (!a.grid || !a.grid.item) + return -1; + if (!b.grid || !b.grid.item) + return 1; + return a.grid.item - b.grid.item; + }); + } + // do layout by marking cells with item (left top corner in case of span), null (empty) and undefined (spanned) + for (let col = 0; col < numberOfColumns; col++) { + let columnWidgets = widgetsByColumn[col]; + for (let item = 0; item < columnWidgets.length; item++) { + let widget = columnWidgets[item]; + let span = getSpan(widget, numberOfColumns, col); + let info = { span: span, widget: widget}; + let column0 = layout[col]; + let row0 = getEmptyRowIndex(column0, span); + for (let spanX = 0; spanX < span; spanX++) { + let column = layout[col + spanX]; + if (spanX == 0) { + if (!widget.grid) + widget.grid = { column: col, span: span }; // init grid + widget.grid.item = column.length; // update item position + } else { + while (column.length < row0) + column.push(null); // null marks empty cells + } + for (let spanY = 0; spanY < span; spanY++) { + column.push(spanX === 0 && spanY === 0 ? info : undefined); + } + } + } + } + // give the layout a uniform row number + let maxRows = layout.map(column => column.length).reduce((acc, cur) => acc ? Math.max(acc, cur) : cur); + for (let col = 0; col < numberOfColumns; col++) { + while (layout[col].length < maxRows) { + layout[col].push(null); + } + } + return layout; + } + + function getSpan(widget, numberOfColumns, currentColumn) { + let span = widget.grid && widget.grid.span ? widget.grid.span : 1; + if (typeof span === 'string') { + if (span === 'full') { + span = numberOfColumns; + } else { + span = parseInt(span); + } + } + if (span > numberOfColumns - currentColumn) { + span = numberOfColumns - currentColumn; + } + return span; + } + + /** + * @return {number} row position in column where n rows are still empty ('null' marks empty) + */ + function getEmptyRowIndex(column, n) { + return Math.max(column.length, column.findIndex((elem,index,array) => array.slice(index, index + n).every(e => e === null))); + } return { currentPage: function() { @@ -273,7 +318,7 @@ var MonitoringConsole = (function() { if (userInterface instanceof FileList) { let file = userInterface[0]; if (file) { - let json = await MonitoringConsoleUtils.readTextFile(file); + let json = await readTextFile(file); doImport(JSON.parse(json)); } } else { @@ -302,7 +347,7 @@ var MonitoringConsole = (function() { }, renamePage: function(name) { - let pageId = MonitoringConsoleUtils.getPageId(name); + let pageId = getPageId(name); if (pages[pageId]) throw "Page with name already exist"; let page = pages[currentPageId]; @@ -390,49 +435,7 @@ var MonitoringConsole = (function() { }, arrange: function(columns) { - let page = pages[currentPageId]; - if (!page) - return []; - if (columns) - page.numberOfColumns = columns; - let numberOfColumns = page.numberOfColumns || 1; - let widgets = page.widgets; - let configsByColumn = new Array(numberOfColumns); - for (let col = 0; col < numberOfColumns; col++) - configsByColumn[col] = []; - // insert order widgets - Object.values(widgets).forEach(function(widget) { - let column = widget.grid && widget.grid.column ? widget.grid.column : 0; - configsByColumn[Math.min(Math.max(column, 0), configsByColumn.length - 1)].push(widget); - }); - // build up rows with columns, occupy spans with empty - var layout = new Array(numberOfColumns); - for (let col = 0; col < numberOfColumns; col++) - layout[col] = []; - for (let col = 0; col < numberOfColumns; col++) { - let orderedConfigs = configsByColumn[col].sort(function (a, b) { - if (!a.grid || !a.grid.item) - return -1; - if (!b.grid || !b.grid.item) - return 1; - return a.grid.item - b.grid.item; - }); - orderedConfigs.forEach(function(widget) { - let span = MonitoringConsoleUtils.getSpan(widget, numberOfColumns, col); - let info = { span: span, widget: widget}; - for (let spanX = 0; spanX < span; spanX++) { - let column = layout[col + spanX]; - if (spanX == 0) { - if (!widget.grid) - widget.grid = { column: col, span: span }; // init grid - widget.grid.item = column.length; // update item position - } - for (let spanY = 0; spanY < span; spanY++) { - column.push(spanX === 0 && spanY === 0 ? info : null); - } - } - }); - } + let layout = doLayout(columns); doStore(); return layout; }, @@ -492,6 +495,17 @@ var MonitoringConsole = (function() { options.scales.xAxes[0].ticks.maxRotation = rotation; options.legend.display = widget.options.showLegend === true; } + + function getTimeLabel(value, index, values) { + if (values.length == 0 || index == 0) + return value; + let span = values[values.length -1].value - values[0].value; + if (span < 120000) { // less then two minutes + let lastMinute = new Date(values[index-1].value).getMinutes(); + return new Date(values[index].value).getMinutes() != lastMinute ? value : ''+new Date(values[index].value).getSeconds(); + } + return value; + } return { /** @@ -520,7 +534,7 @@ var MonitoringConsole = (function() { round: 'second', }, ticks: { - callback: MonitoringConsoleUtils.getTimeLabel, + callback: getTimeLabel, minRotation: 90, maxRotation: 90, } diff --git a/appserver/monitoring-console/webapp/src/main/webapp/mc-page.js b/appserver/monitoring-console/webapp/src/main/webapp/mc-view.js similarity index 63% rename from appserver/monitoring-console/webapp/src/main/webapp/mc-page.js rename to appserver/monitoring-console/webapp/src/main/webapp/mc-view.js index 79a44b4e6eb..34c893eca58 100644 --- a/appserver/monitoring-console/webapp/src/main/webapp/mc-page.js +++ b/appserver/monitoring-console/webapp/src/main/webapp/mc-view.js @@ -40,15 +40,18 @@ /*jshint esversion: 8 */ -var MonitoringConsolePage = (function() { +/** + * + **/ +MonitoringConsole.View = (function() { function download(filename, text) { - var pom = document.createElement('a'); + let pom = document.createElement('a'); pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); pom.setAttribute('download', filename); if (document.createEvent) { - var event = document.createEvent('MouseEvents'); + let event = document.createEvent('MouseEvents'); event.initEvent('click', true, true); pom.dispatchEvent(event); } @@ -58,21 +61,21 @@ var MonitoringConsolePage = (function() { } function renderPageTabs() { - var bar = $("#pagesTabs"); + let bar = $("#pagesTabs"); bar.empty(); - MonitoringConsole.listPages().forEach(function(page) { - var tabId = page.id + '-tab'; - var css = "page-tab" + (page.active ? ' page-selected' : ''); + MonitoringConsole.Model.listPages().forEach(function(page) { + let tabId = page.id + '-tab'; + let css = "page-tab" + (page.active ? ' page-selected' : ''); //TODO when clicking active page tab the options could open/close - var newTab = $('', {id: tabId, "class": css, text: page.name}); + let newTab = $('', {id: tabId, "class": css, text: page.name}); newTab.click(function() { - onPageChange(MonitoringConsole.Page.changeTo(page.id)); + onPageChange(MonitoringConsole.Model.Page.changeTo(page.id)); }); bar.append(newTab); }); - var addPage = $('', {id: 'addPageTab', 'class': 'page-tab'}).html('+'); + let addPage = $('', {id: 'addPageTab', 'class': 'page-tab'}).html('+'); addPage.click(function() { - onPageChange(MonitoringConsole.Page.create('(Unnamed)')); + onPageChange(MonitoringConsole.Model.Page.create('(Unnamed)')); }); bar.append(addPage); } @@ -82,12 +85,12 @@ var MonitoringConsolePage = (function() { * This fuction creates this box including the canvas element the chart is drawn upon. */ function renderChartBox(cell) { - var boxId = cell.widget.target + '-box'; - var box = $('#'+boxId); + let boxId = cell.widget.target + '-box'; + let box = $('#'+boxId); if (box.length > 0) return box.first(); box = $('
', { id: boxId, "class": "chart-box" }); - var win = $(window); + let win = $(window); box.append($('',{ id: cell.widget.target })); return box; } @@ -95,35 +98,26 @@ var MonitoringConsolePage = (function() { /** * This function refleshes the page with the given layout. */ - function renderPage(layout) { - var table = $("", { id: 'chart-grid'}); - var numberOfColumns = layout.length; - var maxRow = 0; - for (var col = 0; col < numberOfColumns; col++) { - maxRow = Math.max(maxRow, layout[col].length); - } - var rowHeight = Math.round(($(window).height() - 100) / numberOfColumns); - for (var row = 0; row < maxRow; row++) { - var tr = $(""); - for (var col = 0; col < numberOfColumns; col++) { - if (layout[col].length <= row) { - tr.append($("
")); - } else { - var cell = layout[col][row]; - if (cell) { - var span = cell.span; - var td = $("", { colspan: span, rowspan: span, 'class': 'box', style: 'height: '+(span * rowHeight)+"px;"}); - td.append(renderChartCaption(cell)); - var status = $('
', { "class": 'status-nodata'}); - status.append($('
', {text: 'No Data'})); - td.append(status); - td.append(renderChartBox(cell)); - tr.append(td); - } else if (row == 0 && layout[col].length == 0) { - // a column with no content, span all rows - var td = $("
", { rowspan: maxRow }); - tr.append(td); - } + function onPageUpdate(layout) { + let numberOfColumns = layout.length; + let maxRows = layout[0].length; + let table = $("", { id: 'chart-grid', 'class': 'columns-'+numberOfColumns + ' rows-'+maxRows }); + let rowHeight = Math.round(($(window).height() - 100) / numberOfColumns); + for (let row = 0; row < maxRows; row++) { + let tr = $(""); + for (let col = 0; col < numberOfColumns; col++) { + let cell = layout[col][row]; + if (cell) { + let span = cell.span; + let td = $("
", { colspan: span, rowspan: span, 'class': 'widget', style: 'height: '+(span * rowHeight)+"px;"}); + td.append(renderChartCaption(cell)); + let status = $('
', { "class": 'status-nodata'}); + status.append($('
', {text: 'No Data'})); + td.append(status); + td.append(renderChartBox(cell)); + tr.append(td); + } else if (cell === null) { + tr.append($("
")); } } table.append(tr); @@ -137,18 +131,18 @@ var MonitoringConsolePage = (function() { } function renderChartCaption(cell) { - var bar = $('
', {"class": "caption-bar"}); - var series = cell.widget.series; - var endOfTags = series.lastIndexOf(' '); - var text = endOfTags <= 0 + let bar = $('
', {"class": "caption-bar"}); + let series = cell.widget.series; + let endOfTags = series.lastIndexOf(' '); + let text = endOfTags <= 0 ? camelCaseToWords(series) : ''+series.substring(0, endOfTags)+' '+camelCaseToWords(series.substring(endOfTags + 1)); if (cell.widget.options.perSec) { text += ' (per second)'; } - var caption = $('

', {title: 'Select '+series}).html(text); + let caption = $('

', {title: 'Select '+series}).html(text); caption.click(function() { - if (MonitoringConsole.Page.Widgets.Selection.toggle(series)) { + if (MonitoringConsole.Model.Page.Widgets.Selection.toggle(series)) { bar.parent().addClass('chart-selected'); } else { bar.parent().removeClass('chart-selected'); @@ -156,34 +150,34 @@ var MonitoringConsolePage = (function() { renderChartOptions(); }); bar.append(caption); - var btnClose = $('