From f21d8932122cb7e036e135c3dfdf33b8ad423706 Mon Sep 17 00:00:00 2001 From: Joseph White Date: Wed, 10 Aug 2022 22:04:54 -0400 Subject: [PATCH 01/47] Voltron Destroy --- html/gui/js/modules/job_viewer/ChartPanel.js | 74 +++++++++++++++++--- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 1155186a45..ef95d6c198 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -83,7 +83,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } }, - // The HighCharts chart instance. + // The chart instance. chart: null, /** @@ -123,6 +123,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }); this.displayTimezone = 'UTC'; + }, // initComponent setHighchartTimezone: function() { @@ -146,7 +147,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { // This is here so that when the chart is / panel is loaded // via one of it's child nodes that it triggers a re-load. if (reload === true) { - this.chart.showLoading(); + tab.getEl().mask('Loading...'); this.store.load(); } }, // activate @@ -172,18 +173,20 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { * @param rawHeight */ resize: function(panel, adjWidth, adjHeight, rawWidth, rawHeight) { - if (this.chart !== null && this.chart !== undefined) { - this.chart.setSize(adjWidth, adjHeight); + if (panel.chart) { + Plotly.relayout(panel.id, {width: adjWidth, height: adjHeight}); } this.options.chart.width = adjWidth; this.options.chart.height = adjHeight; }, // resize destroy: function () { +/* if (this.chart) { this.chart.destroy(); this.chart = null; } +*/ }, export_option_selected: function (exportParams) { @@ -304,12 +307,65 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } } - if (panel.chart) { - panel.chart.destroy(); + if (record) { + let data = []; + + for (let sid = 0; sid < chartOptions.series.length; sid++) { + let x = []; + let y = []; + for(let i=0; i < chartOptions.series[sid].data.length; i++) { + x.push(moment(chartOptions.series[sid].data[i].x).format('Y-MM-DD HH:mm:ss z')); + y.push(chartOptions.series[sid].data[i].y); + } + data.push({x: x, y: y, name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter'}); + } + panel.getEl().unmask(); + if (panel.chart) { + Plotly.react(this.id, data, {title: record.data.schema.description, hovermode:'closest'}, {displayModeBar: false } ); + } else { + Plotly.newPlot(this.id, data, {title: record.data.schema.description, hovermode:'closest'}, {displayModeBar: false } ); + } + + if (!panel.chart) { + panel.chart = document.getElementById(this.id); + panel.chart.on('plotly_click', function(data){ + var pts = ''; + for(var i=0; i < data.points.length; i++){ + pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; + } + console.log('Closest point clicked:\n\n'+pts); + console.log(data); + + var userOptions = data.points[0].data.chartSeries; + if (!userOptions || !userOptions.dtype) { + return; + } + + var drilldown; + /* + * The drilldown data are stored on each point for envelope + * plots and for the series for simple plots. + */ + if (userOptions.dtype == 'index') { + drilldown = { + dtype: userOptions.index, + value: event.point.options[userOptions.index] + }; + } else { + drilldown = { + dtype: userOptions.dtype, + value: userOptions[userOptions.dtype] + }; + } + var path = self.path.concat([drilldown]); + var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); + Ext.History.add(token); + }); } - panel.chart = new Highcharts.Chart(chartOptions); + } + if (!record) { - panel.chart.showLoading(); + panel.getEl().mask('Loading...'); } panel.fireEvent('record_loaded'); if (CCR.isType(panel.layout, CCR.Types.Object) && panel.rendered) panel.doLayout(); @@ -340,7 +396,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { /** * Helper function that adds an explicit 'load' listener to the provided - * data store. This listener will ensure that each time the store receives + * this.series.userOptions;data store. This listener will ensure that each time the store receives * a load event, if there is at least one record, then this components * 'load_record' event will be fired with a reference to the first record * returned. From 2b59a84547f14d8789af97457f50e13af49598df Mon Sep 17 00:00:00 2001 From: Joseph White Date: Thu, 11 Aug 2022 21:13:43 -0400 Subject: [PATCH 02/47] Up --- html/gui/js/modules/job_viewer/ChartPanel.js | 58 +++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index ef95d6c198..8ac718ecc2 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -317,13 +317,65 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { x.push(moment(chartOptions.series[sid].data[i].x).format('Y-MM-DD HH:mm:ss z')); y.push(chartOptions.series[sid].data[i].y); } - data.push({x: x, y: y, name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter'}); + data.push({ + x: x, + y: y, + line: { + color: chartOptions.colors[sid % 10] + }, + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L %Z}

" + + chartOptions.series[sid].name + ": %{y}" + + "", + name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter'}); } panel.getEl().unmask(); + + let layout = { + hoverlabel: { + bgcolor: 'white' + }, + xaxis: { + title: 'Time (' + record.data.schema.timezone + ')', + titlefont: { + family: 'Arial, sans-serif', + size: '12pt', + color: '#5078a0' + }, + color: '#606060', + ticks: 'outside', + ticklen: 10, + tickcolor: '#c0cfe0', + linecolor: '#c0cfe0', + showgrid: false + }, + yaxis: { + title: '' + record.data.schema.units + '', + titlefont: { + family: 'Arial, sans-serif', + size: '12pt', + color: '#5078a0' + }, + color: '#606060', + rangemode: 'tozero', + linecolor: '#c0cfe0' + }, + title: { + text: record.data.schema.description, + color: '#444a6e', + size: '16pt' + }, + hovermode: 'closest', + showlegend: false, + margin: { + t: 50 + } + }; + if (panel.chart) { - Plotly.react(this.id, data, {title: record.data.schema.description, hovermode:'closest'}, {displayModeBar: false } ); + Plotly.react(this.id, data, layout, {displayModeBar: false } ); } else { - Plotly.newPlot(this.id, data, {title: record.data.schema.description, hovermode:'closest'}, {displayModeBar: false } ); + Plotly.newPlot(this.id, data, layout, {displayModeBar: false } ); } if (!panel.chart) { From 3c7c38e94b1af6c1edec914c411b49c014da84c6 Mon Sep 17 00:00:00 2001 From: Joseph White Date: Thu, 11 Aug 2022 21:24:44 -0400 Subject: [PATCH 03/47] up --- html/gui/js/modules/job_viewer/ChartPanel.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 8ac718ecc2..0b07b7cc5a 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -339,7 +339,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { title: 'Time (' + record.data.schema.timezone + ')', titlefont: { family: 'Arial, sans-serif', - size: '12pt', + size: 12, color: '#5078a0' }, color: '#606060', @@ -353,7 +353,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { title: '' + record.data.schema.units + '', titlefont: { family: 'Arial, sans-serif', - size: '12pt', + size: 12, color: '#5078a0' }, color: '#606060', @@ -362,8 +362,10 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, title: { text: record.data.schema.description, - color: '#444a6e', - size: '16pt' + font: { + color: '#444b6e', + size: 16 + } }, hovermode: 'closest', showlegend: false, From d5bbcc48c35ba061d9ae83be8914dcf0529b9759 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Fri, 10 Feb 2023 15:13:09 +0000 Subject: [PATCH 04/47] Process of changing analytic charts over to plotly --- .../modules/job_viewer/AnalyticChartPanel.js | 95 +++++++++++++------ 1 file changed, 67 insertions(+), 28 deletions(-) diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 1a76a3f8b4..39fa40a473 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -84,12 +84,42 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { value: 1, color: '#50B432' } - ] - }, + ], + + layout: { + autosize: true, + yaxis: { + range: [0, 1], + lineColor: '#c0c0c0', + title: '', + titlefont: { + color: '#5078a0' + } + }, + xAxis: { + ticklen: 1, + showticklabels: { + enabled: false + } + }, + showlegend: false, + margin: { + l: 1, + r: 1, + b: 1, + t: 1, + pad: 5 + } + }, + + traces: [], + + }, // The instance of Highcharts used as this components primary display. chart: null, + // private member that stores the error message object errorMsg: null, @@ -120,12 +150,13 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { Ext.apply(this.chartOptions, this._DEFAULT_CONFIG.chartOptions); - this.chartOptions.chart.renderTo = this.id; + //this.chartOptions.chart.renderTo = this.id; - if (this.chart) { + /*if (this.chart) { this.chart.destroy(); - } - this.chart = new Highcharts.Chart(this.chartOptions); + }*/ + Plotly.newPlot(this.id, [], this.layout, {displayModeBar: false} ); + }, // render @@ -138,7 +169,8 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ update_data: function(data) { this._updateData(data); - this.chart.redraw(true); + var test = this.traces; + Plotly.update(this.id, this.traces, this.layout, {displayModeBar: false} ); }, // update_data /** @@ -147,10 +179,8 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ reset: function() { if (this.chart) { - while (this.chart.series.length > 0) { - this.chart.series[0].remove(false); - } - this.chart.redraw(); + this.traces = []; + Plotly.update(this.id, [], this.layout, {displayModeBar: false} ); } }, // reset @@ -161,6 +191,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { } }, + /** * Attempt to resize this components HighCharts instance such that it * falls with in the new adjWidth and adjHeight. @@ -173,7 +204,9 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (this.chart) { - this.chart.reflow(); + this.layout['width'] = adjWidth; + this.layout['height'] = adjHeight; + Plotly.update(this.id, this.traces, this.layout, {displayModeBar: false, responsive: true}); if (this.errorMsg) { this.updateErrorMessage(this.errorMsg.text.textStr); } @@ -195,11 +228,12 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { this.errorMsg = null; } if (errorStr) { - this.errorMsg = { text: this.chart.renderer.text(errorStr, this.chart.plotLeft + 23, this.chart.plotTop + 10) + this.layout['images'] = [{source: '/gui/images/about_16.png'}] + /*this.errorMsg = { text: this.chart.renderer.text(errorStr, this.chart.plotLeft + 23, this.chart.plotTop + 10) .css({ width: this.chart.chartWidth - this.chart.plotLeft - 23 }) .add() }; var box = this.errorMsg.text.getBBox(); - this.errorMsg.image = this.chart.renderer.image('/gui/images/about_16.png', box.x - 23, box.y - 1, 16, 16).add(); + this.errorMsg.image = this.chart.renderer.image('/gui/images/about_16.png', box.x - 23, box.y - 1, 16, 16).add();*/ } }, @@ -215,29 +249,34 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { var brightFactor = 0.4; var color, nColor; - while (this.chart.series.length > 0) { - this.chart.series[0].remove(true); - } + this.traces = []; - this.chart.plotBackground = null; - this.chart.chartBackground = null; + //this.chart.layout.push(); + //this.chart.chartBackground = null; if (data.error == '') { color = this._getDataColor(data.value); - nColor = new Highcharts.Color(color).brighten(brightFactor); - this.chart.options.chart.plotBackgroundColor = 'rgba(' + nColor.rgba + ')'; - - this.chart.addSeries({ - name: data.name ? data.name : '', - data: [data.value], - color: color - }, true, true); + //nColor = new Highcharts.Color(color).brighten(brightFactor); + //this.layout.push({plot_bgcolor: 'rgba(' + nColor.rgba + ')'}); + this.traces.push( + { + name: data.name ? data.name : '', + data: [data.value], + marker:{ + color: color + }, + type: 'bar' + } + ); + //this.layout.push({title: data.name ? data.name : ''}); + //data.push({x: [data.value], type: 'bar', color: color}); } this.updateErrorMessage(data.error); - + console.log(this.traces); this._updateTitle(data); this.ownerCt.doLayout(false, true); + }, // _updateData From 0af6789d9f1cc6c3892566431c15324e9d54e358 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Sat, 11 Feb 2023 15:41:44 +0000 Subject: [PATCH 05/47] More updates to analytic chart --- .../modules/job_viewer/AnalyticChartPanel.js | 62 +++++++++++++------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 39fa40a473..b344eecc45 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -22,7 +22,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { }, chart: { type: 'bar', - height: 65, options: { }, reflow: false @@ -87,20 +86,31 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { ], layout: { - autosize: true, + autosize: false, + height: 64, yaxis: { - range: [0, 1], lineColor: '#c0c0c0', title: '', + fixedrange: true, titlefont: { color: '#5078a0' } }, - xAxis: { - ticklen: 1, + xaxis: { + range: [0, 1], + //rangebreaks: 0.2, + //dtick: 0.2, + //domain: [0, 1], + lineColor: '#c0c0c0', + titlefont: { + color: '#5078a0' + }, + ticklen: 0.2, + fixedrange:true, showticklabels: { - enabled: false + enabled: true } + //type: 'linear' }, showlegend: false, margin: { @@ -108,7 +118,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { r: 1, b: 1, t: 1, - pad: 5 + pad: 1 } }, @@ -155,7 +165,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { /*if (this.chart) { this.chart.destroy(); }*/ - Plotly.newPlot(this.id, [], this.layout, {displayModeBar: false} ); + Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, {displayModeBar: false} ); }, // render @@ -170,7 +180,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { update_data: function(data) { this._updateData(data); var test = this.traces; - Plotly.update(this.id, this.traces, this.layout, {displayModeBar: false} ); + Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, {displayModeBar: false} ); }, // update_data /** @@ -180,7 +190,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { reset: function() { if (this.chart) { this.traces = []; - Plotly.update(this.id, [], this.layout, {displayModeBar: false} ); + Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, {displayModeBar: false} ); } }, // reset @@ -204,12 +214,13 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (this.chart) { - this.layout['width'] = adjWidth; - this.layout['height'] = adjHeight; - Plotly.update(this.id, this.traces, this.layout, {displayModeBar: false, responsive: true}); + this._DEFAULT_CONFIG.layout['width'] = adjWidth; + this._DEFAULT_CONFIG.layout['height'] = adjHeight; + Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this.layout, {displayModeBar: false, responsive: true}); if (this.errorMsg) { this.updateErrorMessage(this.errorMsg.text.textStr); } + console.log(this.layout); } } // resize @@ -228,7 +239,17 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { this.errorMsg = null; } if (errorStr) { - this.layout['images'] = [{source: '/gui/images/about_16.png'}] + this._DEFAULT_CONFIG.layout['images'] = [ + { + "source": '/gui/images/about_16.png', + "xref": "paper", + "yref": "paper", + "sizex": 1, + "sizey": 1, + "x": 0, + "y": 1 + } + ] /*this.errorMsg = { text: this.chart.renderer.text(errorStr, this.chart.plotLeft + 23, this.chart.plotTop + 10) .css({ width: this.chart.chartWidth - this.chart.plotLeft - 23 }) .add() }; @@ -249,7 +270,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { var brightFactor = 0.4; var color, nColor; - this.traces = []; + this._DEFAULT_CONFIG.traces = []; //this.chart.layout.push(); //this.chart.chartBackground = null; @@ -259,22 +280,25 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { color = this._getDataColor(data.value); //nColor = new Highcharts.Color(color).brighten(brightFactor); //this.layout.push({plot_bgcolor: 'rgba(' + nColor.rgba + ')'}); - this.traces.push( + this._DEFAULT_CONFIG.traces.push( { + x: [data.value], name: data.name ? data.name : '', - data: [data.value], + //width: 1, marker:{ color: color }, - type: 'bar' + type: 'bar', + orientation: 'h' } ); //this.layout.push({title: data.name ? data.name : ''}); //data.push({x: [data.value], type: 'bar', color: color}); } this.updateErrorMessage(data.error); - console.log(this.traces); + console.log(this._DEFAULT_CONFIG.traces); this._updateTitle(data); + console.log(this._DEFAULT_CONFIG.layout); this.ownerCt.doLayout(false, true); From cbc01ec5dae81d83d2275fb84dbc078c4e015fe6 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 14 Feb 2023 16:08:27 -0500 Subject: [PATCH 06/47] Fixed margin and background colors for analytic charts --- .../modules/job_viewer/AnalyticChartPanel.js | 187 ++++++++++++++---- 1 file changed, 148 insertions(+), 39 deletions(-) diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index b344eecc45..d7fe5659b0 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -22,6 +22,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { }, chart: { type: 'bar', + height: 65, options: { }, reflow: false @@ -69,58 +70,154 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { colorSteps: [ { value: .25, - color: '#FF0000' + color: 'rgb(255,0,0)' }, { value: .50, - color: '#FFB336' + color: 'rgb(255,179,54)' }, { value: .75, - color: '#DDDF00' + color: '#rgb(221,223,0)' }, { value: 1, - color: '#50B432' + color: 'rgb(80,180,50)' } ], - layout: { - autosize: false, - height: 64, - yaxis: { - lineColor: '#c0c0c0', - title: '', - fixedrange: true, - titlefont: { - color: '#5078a0' - } + + layout: { + 'hoverlabel': { + 'bgcolor': 'white' + }, + 'paper_bgcolor': 'white', + 'plot_bgcolor': 'white', + 'height': 60, + + 'colorway': ["#1199FF", + "#DB4230", + "#4E665D", + "#F4A221", + "#66FF00", + "#33ABAB", + "#A88D95", + "#789ABC", + "#FF99CC", + "#00CCFF", + "#FFBC71", + "#A57E81", + "#8D4DFF", + "#FF6666", + "#CC99FF", + "#2F7ED8", + "#0D233A", + "#8BBC21", + "#910000", + "#1AADCE", + "#492970", + "#F28F43", + "#77A1E5", + "#3366FF", + "#FF6600", + "#808000", + "#CC99FF", + "#008080", + "#CC6600", + "#9999FF", + "#99FF99", + "#969696", + "#FF00FF", + "#FFCC00", + "#666699", + "#00FFFF", + "#00CCFF", + "#993366", + "#3AAAAA", + "#C0C0C0", + "#FF99CC", + "#FFCC99", + "#CCFFCC", + "#CCFFFF", + "#99CCFF", + "#339966", + "#FF9966", + "#69BBED", + "#33FF33", + "#6666FF", + "#FF66FF", + "#99ABAB", + "#AB8722", + "#AB6565", + "#990099", + "#999900", + "#CC3300", + "#669999", + "#993333", + "#339966", + "#C42525", + "#A6C96A", + "#111111"], + 'xaxis': { + 'showticklabels': false, + 'range': [0,1], + 'titlefont': { + 'family': 'Arial, sans-serif', + 'size': 12, + 'color': '#5078a0' }, - xaxis: { - range: [0, 1], - //rangebreaks: 0.2, - //dtick: 0.2, - //domain: [0, 1], - lineColor: '#c0c0c0', - titlefont: { - color: '#5078a0' - }, - ticklen: 0.2, - fixedrange:true, - showticklabels: { - enabled: true - } - //type: 'linear' + 'color': '#606060', + 'ticks': 'inside', + 'tick0': 0.0, + 'dtick': 0.2, + 'ticklen': 2, + 'tickcolor': 'white', + 'gridcolor': '#c0c0c0', + 'linecolor': 'white', + 'zeroline' : false, + 'showgrid': true, + 'zerolinecolor': 'black', + 'showline': false, + 'zerolinewidth': 0, + 'tickformat': "%Y-%m-%d" + }, + 'yaxis': { + 'showticklabels': false, + 'titlefont': { + 'family': 'Arial, sans-serif', + 'size': 12, + 'color': '#1199FF' }, - showlegend: false, - margin: { - l: 1, - r: 1, - b: 1, - t: 1, - pad: 1 + 'color': '#606060', + 'showgrid' : false, + 'gridcolor': 'white', + 'linecolor': 'white', + 'zeroline': false, + 'zerolinecolor': 'white', + 'showline': false, + 'rangemode': 'tozero', + 'zerolinewidth': 0 + }, + 'title': { + 'font': { + 'color': '#444b6e', + 'size': 16 } - }, + }, + 'hovermode': 'closest', + 'showlegend': false, + 'legend': { + 'orientation': 'h', + 'y': -0.2 + }, + 'margin': { + 't': 10, + 'l': 7.5, + 'r': 7.5, + 'b': 10, + 'pad': 0 + } + }, traces: [], @@ -256,6 +353,9 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { var box = this.errorMsg.text.getBBox(); this.errorMsg.image = this.chart.renderer.image('/gui/images/about_16.png', box.x - 23, box.y - 1, 16, 16).add();*/ } + else { + this._DEFAULT_CONFIG.layout['images'] = []; + } }, /** @@ -274,19 +374,28 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { //this.chart.layout.push(); //this.chart.chartBackground = null; + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = 'white'; + if (data.error == '') { color = this._getDataColor(data.value); + bg_color = color.substring(0,3) + 'a' + color.substring(3, color.length-1) + ',0.4)'; + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = bg_color; + //nColor = new Highcharts.Color(color).brighten(brightFactor); //this.layout.push({plot_bgcolor: 'rgba(' + nColor.rgba + ')'}); this._DEFAULT_CONFIG.traces.push( { x: [data.value], name: data.name ? data.name : '', - //width: 1, + width: [0.5], marker:{ - color: color + color: color, + line:{ + color: 'white', + width: 1.5 + } }, type: 'bar', orientation: 'h' From 1898fdc4869f54f9db3d390f5bd9490f251c321f Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Wed, 15 Feb 2023 17:21:11 -0500 Subject: [PATCH 07/47] Updates to analytic chart formatting and error charts --- .../modules/job_viewer/AnalyticChartPanel.js | 224 +++++++----------- 1 file changed, 86 insertions(+), 138 deletions(-) diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index d7fe5659b0..421d3203dc 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -87,141 +87,77 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { ], - layout: { - 'hoverlabel': { - 'bgcolor': 'white' - }, - 'paper_bgcolor': 'white', - 'plot_bgcolor': 'white', - 'height': 60, - - 'colorway': ["#1199FF", - "#DB4230", - "#4E665D", - "#F4A221", - "#66FF00", - "#33ABAB", - "#A88D95", - "#789ABC", - "#FF99CC", - "#00CCFF", - "#FFBC71", - "#A57E81", - "#8D4DFF", - "#FF6666", - "#CC99FF", - "#2F7ED8", - "#0D233A", - "#8BBC21", - "#910000", - "#1AADCE", - "#492970", - "#F28F43", - "#77A1E5", - "#3366FF", - "#FF6600", - "#808000", - "#CC99FF", - "#008080", - "#CC6600", - "#9999FF", - "#99FF99", - "#969696", - "#FF00FF", - "#FFCC00", - "#666699", - "#00FFFF", - "#00CCFF", - "#993366", - "#3AAAAA", - "#C0C0C0", - "#FF99CC", - "#FFCC99", - "#CCFFCC", - "#CCFFFF", - "#99CCFF", - "#339966", - "#FF9966", - "#69BBED", - "#33FF33", - "#6666FF", - "#FF66FF", - "#99ABAB", - "#AB8722", - "#AB6565", - "#990099", - "#999900", - "#CC3300", - "#669999", - "#993333", - "#339966", - "#C42525", - "#A6C96A", - "#111111"], - 'xaxis': { - 'showticklabels': false, - 'range': [0,1], - 'titlefont': { - 'family': 'Arial, sans-serif', - 'size': 12, - 'color': '#5078a0' + layout: { + 'hoverlabel': { + 'bgcolor': 'white' }, - 'color': '#606060', - 'ticks': 'inside', - 'tick0': 0.0, - 'dtick': 0.2, - 'ticklen': 2, - 'tickcolor': 'white', - 'gridcolor': '#c0c0c0', - 'linecolor': 'white', - 'zeroline' : false, - 'showgrid': true, - 'zerolinecolor': 'black', - 'showline': false, - 'zerolinewidth': 0, - 'tickformat': "%Y-%m-%d" - }, - 'yaxis': { - 'showticklabels': false, - 'titlefont': { - 'family': 'Arial, sans-serif', - 'size': 12, - 'color': '#1199FF' + 'paper_bgcolor': 'white', + 'plot_bgcolor': 'white', + 'height': 60, + 'xaxis': { + 'showticklabels': false, + 'range': [0,1], + 'titlefont': { + 'family': 'Arial, sans-serif', + 'size': 12, + 'color': '#5078a0' + }, + 'color': '#606060', + 'ticks': 'inside', + 'tick0': 0.0, + 'dtick': 0.2, + 'ticklen': 2, + 'tickcolor': 'white', + 'gridcolor': '#c0c0c0', + 'linecolor': 'white', + 'zeroline' : false, + 'showgrid': true, + 'zerolinecolor': 'black', + 'showline': false, + 'zerolinewidth': 0, + 'tickformat': "%Y-%m-%d" }, - 'color': '#606060', - 'showgrid' : false, - 'gridcolor': 'white', - 'linecolor': 'white', - 'zeroline': false, - 'zerolinecolor': 'white', - 'showline': false, - 'rangemode': 'tozero', - 'zerolinewidth': 0 - }, - 'title': { - 'font': { - 'color': '#444b6e', - 'size': 16 + 'yaxis': { + 'showticklabels': false, + 'titlefont': { + 'family': 'Arial, sans-serif', + 'size': 12, + 'color': '#1199FF' + }, + 'color': '#606060', + 'showgrid' : false, + 'gridcolor': 'white', + 'linecolor': 'white', + 'zeroline': false, + 'zerolinecolor': 'white', + 'showline': false, + 'rangemode': 'tozero', + 'zerolinewidth': 0 + }, + 'title': { + 'font': { + 'color': '#444b6e', + 'size': 16 + } + }, + 'hovermode': 'closest', + 'showlegend': false, + 'legend': { + 'orientation': 'h', + 'y': -0.2 + }, + 'margin': { + 't': 10, + 'l': 7.5, + 'r': 7.5, + 'b': 10, + 'pad': 0 } }, - 'hovermode': 'closest', - 'showlegend': false, - 'legend': { - 'orientation': 'h', - 'y': -0.2 - }, - 'margin': { - 't': 10, - 'l': 7.5, - 'r': 7.5, - 'b': 10, - 'pad': 0 - } - }, traces: [], - }, + }, // The instance of Highcharts used as this components primary display. chart: null, @@ -256,12 +192,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { render: function() { Ext.apply(this.chartOptions, this._DEFAULT_CONFIG.chartOptions); - - //this.chartOptions.chart.renderTo = this.id; - - /*if (this.chart) { - this.chart.destroy(); - }*/ Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, {displayModeBar: false} ); @@ -276,7 +206,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ update_data: function(data) { this._updateData(data); - var test = this.traces; Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, {displayModeBar: false} ); }, // update_data @@ -317,7 +246,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { if (this.errorMsg) { this.updateErrorMessage(this.errorMsg.text.textStr); } - console.log(this.layout); } } // resize @@ -341,12 +269,30 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { "source": '/gui/images/about_16.png', "xref": "paper", "yref": "paper", + "sizex": 0.5, + "sizey": 0.5, + "x": 0, + "y": 1.2 + } + ] + this._DEFAULT_CONFIG.layout['annotations'] = [ + { + "text": errorStr, + "align": "left", + "xref": "paper", + "yref": "paper", "sizex": 1, "sizey": 1, - "x": 0, - "y": 1 + "x" : 0.5, + "y" : 0.5, + "showarrow": false + + + } ] + this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = false; + /*this.errorMsg = { text: this.chart.renderer.text(errorStr, this.chart.plotLeft + 23, this.chart.plotTop + 10) .css({ width: this.chart.chartWidth - this.chart.plotLeft - 23 }) .add() }; @@ -355,6 +301,8 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { } else { this._DEFAULT_CONFIG.layout['images'] = []; + this._DEFAULT_CONFIG.layout['annotations'] = []; + this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = true; } }, From 643858da4de29cf020a897475ba43146830f3367 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 9 May 2023 02:04:22 -0400 Subject: [PATCH 08/47] Gantt chart Plotly converstion. Also includes drilldown fix for node/peers and fixes rendering of envelope plots. --- .../modules/job_viewer/AnalyticChartPanel.js | 45 ++--- html/gui/js/modules/job_viewer/ChartPanel.js | 82 ++++++--- html/gui/js/modules/job_viewer/ChartTab.js | 5 +- html/gui/js/modules/job_viewer/GanttChart.js | 161 ++++++++++++++---- html/gui/js/modules/job_viewer/JobViewer.js | 4 + 5 files changed, 208 insertions(+), 89 deletions(-) diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 421d3203dc..84e6d1577d 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -78,7 +78,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { }, { value: .75, - color: '#rgb(221,223,0)' + color: 'rgb(221,223,0)' }, { value: 1, @@ -115,7 +115,8 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { 'zerolinecolor': 'black', 'showline': false, 'zerolinewidth': 0, - 'tickformat': "%Y-%m-%d" + 'tickformat': "%Y-%m-%d", + 'fixedrange': true }, 'yaxis': { 'showticklabels': false, @@ -126,13 +127,14 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { }, 'color': '#606060', 'showgrid' : false, - 'gridcolor': 'white', + 'gridcolor': '#c0c0c0', 'linecolor': 'white', 'zeroline': false, 'zerolinecolor': 'white', 'showline': false, 'rangemode': 'tozero', - 'zerolinewidth': 0 + 'zerolinewidth': 0, + 'fixedrange': true }, 'title': { 'font': { @@ -140,7 +142,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { 'size': 16 } }, - 'hovermode': 'closest', + 'hovermode': false, 'showlegend': false, 'legend': { 'orientation': 'h', @@ -156,6 +158,10 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { }, traces: [], + config: { + displayModeBar: false, + + }, }, @@ -192,8 +198,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { render: function() { Ext.apply(this.chartOptions, this._DEFAULT_CONFIG.chartOptions); - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, {displayModeBar: false} ); - + Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // render @@ -206,7 +211,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ update_data: function(data) { this._updateData(data); - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, {displayModeBar: false} ); + Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // update_data /** @@ -216,7 +221,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { reset: function() { if (this.chart) { this.traces = []; - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, {displayModeBar: false} ); + Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); } }, // reset @@ -242,7 +247,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { if (this.chart) { this._DEFAULT_CONFIG.layout['width'] = adjWidth; this._DEFAULT_CONFIG.layout['height'] = adjHeight; - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this.layout, {displayModeBar: false, responsive: true}); + Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); if (this.errorMsg) { this.updateErrorMessage(this.errorMsg.text.textStr); } @@ -275,9 +280,10 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { "y": 1.2 } ] + console.log("new annontation"); this._DEFAULT_CONFIG.layout['annotations'] = [ { - "text": errorStr, + "text": wordwrap.wrap(errorStr, {width: 20}), "align": "left", "xref": "paper", "yref": "paper", @@ -286,18 +292,9 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { "x" : 0.5, "y" : 0.5, "showarrow": false - - - } ] this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = false; - - /*this.errorMsg = { text: this.chart.renderer.text(errorStr, this.chart.plotLeft + 23, this.chart.plotTop + 10) - .css({ width: this.chart.chartWidth - this.chart.plotLeft - 23 }) - .add() }; - var box = this.errorMsg.text.getBBox(); - this.errorMsg.image = this.chart.renderer.image('/gui/images/about_16.png', box.x - 23, box.y - 1, 16, 16).add();*/ } else { this._DEFAULT_CONFIG.layout['images'] = []; @@ -320,8 +317,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { this._DEFAULT_CONFIG.traces = []; - //this.chart.layout.push(); - //this.chart.chartBackground = null; this._DEFAULT_CONFIG.layout['plot_bgcolor'] = 'white'; @@ -331,8 +326,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { bg_color = color.substring(0,3) + 'a' + color.substring(3, color.length-1) + ',0.4)'; this._DEFAULT_CONFIG.layout['plot_bgcolor'] = bg_color; - //nColor = new Highcharts.Color(color).brighten(brightFactor); - //this.layout.push({plot_bgcolor: 'rgba(' + nColor.rgba + ')'}); this._DEFAULT_CONFIG.traces.push( { x: [data.value], @@ -349,13 +342,9 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { orientation: 'h' } ); - //this.layout.push({title: data.name ? data.name : ''}); - //data.push({x: [data.value], type: 'bar', color: color}); } this.updateErrorMessage(data.error); - console.log(this._DEFAULT_CONFIG.traces); this._updateTitle(data); - console.log(this._DEFAULT_CONFIG.layout); this.ownerCt.doLayout(false, true); diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 0b07b7cc5a..6e1570e326 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -9,7 +9,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { // The default chart config options. _DEFAULT_CONFIG: { - chartPrefix: 'CreateChartPanel', + chartPrefix: 'CreateChartPanel', chartOptions: { chart: { type: 'line', @@ -311,26 +311,59 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { let data = []; for (let sid = 0; sid < chartOptions.series.length; sid++) { - let x = []; - let y = []; + if (chartOptions.series[sid].name === "Range") continue; + let x = []; + let y = []; + for(let i=0; i < chartOptions.series[sid].data.length; i++) { x.push(moment(chartOptions.series[sid].data[i].x).format('Y-MM-DD HH:mm:ss z')); y.push(chartOptions.series[sid].data[i].y); } - data.push({ - x: x, - y: y, - line: { - color: chartOptions.colors[sid % 10] - }, - hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L %Z}

" + - chartOptions.series[sid].name + ": %{y}" + - "", - name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter'}); - } - panel.getEl().unmask(); + if (chartOptions.series[sid].name === "Median" || chartOptions.series[sid].name === "Minimum"){ + data.push({ + x: x, + y: y, + fill: 'tonexty', + fillcolor: 'rgba(47, 126, 216, 0.5)', + /*marker: { + size: 20, + line: { + color: chartOptions.colors[sid % 10] + } + },*/ + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L %Z} " + this.displayTimezone + "
" + + chartOptions.series[sid].name + ": %{y}" + + "", + name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter+marker'}); + } + else{ + data.push({ + x: x, + y: y, + marker: { + size: 20, + line: { + color: chartOptions.colors[sid % 10] + } + }, + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L %Z} " + this.displayTimezone + "
" + + ' ' + chartOptions.series[sid].name + ":%{y}" + + "", + name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'lines+marker'}); + } + } + panel.getEl().unmask(); + console.log("data"); + console.log(data); + console.log("chart options data"); + console.log(chartOptions); + console.log("record"); + console.log(record); + console.log("SVG"); + console.log(document.getElementsByClassName("main-svg")); let layout = { hoverlabel: { bgcolor: 'white' @@ -344,7 +377,8 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, color: '#606060', ticks: 'outside', - ticklen: 10, + autorange: true, + ticklen: 10, tickcolor: '#c0cfe0', linecolor: '#c0cfe0', showgrid: false @@ -357,7 +391,8 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { color: '#5078a0' }, color: '#606060', - rangemode: 'tozero', + autorange: true, + rangemode: 'nonnegative', linecolor: '#c0cfe0' }, title: { @@ -382,7 +417,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { if (!panel.chart) { panel.chart = document.getElementById(this.id); - panel.chart.on('plotly_click', function(data){ + panel.chart.on('plotly_click', function(data, event){ var pts = ''; for(var i=0; i < data.points.length; i++){ pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; @@ -390,7 +425,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { console.log('Closest point clicked:\n\n'+pts); console.log(data); - var userOptions = data.points[0].data.chartSeries; + var userOptions = data.points[0].data.chartSeries if (!userOptions || !userOptions.dtype) { return; } @@ -401,9 +436,11 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { * plots and for the series for simple plots. */ if (userOptions.dtype == 'index') { + var nodeidIndex = data.points[0].data.chartSeries.data.findIndex(({y}) => y === data.points[0].y); + if (nodeidIndex === -1) return; drilldown = { dtype: userOptions.index, - value: event.point.options[userOptions.index] + value: userOptions.data[nodeidIndex].nodeid }; } else { drilldown = { @@ -416,8 +453,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { Ext.History.add(token); }); } - } - + } if (!record) { panel.getEl().mask('Loading...'); } diff --git a/html/gui/js/modules/job_viewer/ChartTab.js b/html/gui/js/modules/job_viewer/ChartTab.js index 0009a4218d..88b22df468 100644 --- a/html/gui/js/modules/job_viewer/ChartTab.js +++ b/html/gui/js/modules/job_viewer/ChartTab.js @@ -99,8 +99,9 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { var chartOptions = jQuery.extend(true, {}, defaultChartSettings, self.chartSettings); - self.chart = new Highcharts.Chart(chartOptions); - self.chart.showLoading(); + //self.chart = new Highcharts.Chart(chartOptions); + //self.chart.showLoading(); + Plotly.newPlot(this.id, [], {}); var storeParams; if (self.panelSettings.pageSize) { diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index e3a6b969e5..915087a7b4 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -34,48 +34,137 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, return; } var record = store.getAt(0); - + console.log("record"); + console.log(record); this.updateTimezone(record.data.schema.timezone); + var i; + var data = []; + var categories = []; + var count = 0; + var rect = []; + var yvals = [0]; + let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + for (i = 0; i < record.data.series.length; i++) { + var j = 0; + for (j = 0; j < record.data.series[i].data.length; j++){ + low_data = moment(record.data.series[i].data[j].low).format('Y-MM-DD HH:mm:ss z'); + high_data = moment(record.data.series[i].data[j].high).format('Y-MM-DD HH:mm:ss z'); + categories.push(record.data.categories[count]); + var runtime = []; + let template = record.data.series[i].name + ": " + "" + moment(record.data.series[i].data[j].low).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment(record.data.series[i].data[j].high).format('ddd, MMM DD, HH:mm:ss z') + " "; + var tooltip = [template]; + var ticks = [count]; + var start_time = record.data.series[i].data[j].low; + + // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. + // Points on the scatter plot are created every min to create better coverage for tooltip information + while (start_time < record.data.series[i].data[j].high){ + ticks.push(count); + tooltip.push(template); + runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); + start_time += (60 * 1000); + } + runtime.push(high_data); + + rect.push({ + x0: low_data, + x1: high_data, + y0: count-0.25, + y1: count+0.25, + type: 'rect', + xref: 'x', + yref: 'y', + fillcolor: colors[i % 10], + color: colors[i % 10] + }); - var clickEvent = function (evt) { - var info = { - action: 'show', - title: 'Peers of ' + record.data.schema.ref.text, - realm: evt.point.ref.realm, - jobref: evt.point.ref.jobid - }; - Ext.History.add('job_viewer?' + Ext.urlEncode(info)); - }; + + var info = {} - while (this.chart.series.length > 0) { - this.chart.series[0].remove(false); - } + if (i > 0){ + info = { + realm: record.data.series[i].data[j].ref.realm, + recordid: store.baseParams.recordid, + jobref: record.data.series[i].data[j].ref.jobid, + infoid: 3 + }; + } + + peer = { + x: runtime, + y: ticks, + type: 'scatter', + + marker:{ + color: 'rgb(155,255,255)' + }, + orientation: 'h', + hovertemplate: record.data.categories[count] + '
%{text} ', + text: tooltip, + chartSeries: info + }; - this.chart.colorCounter = 0; - this.chart.symbolCounter = 0; - this.chart.xAxis[0].setCategories(record.data.categories, false); - this.chart.yAxis[0].update({ - title: { - text: 'Time (' + record.data.schema.timezone + ')' - }, - min: record.data.series[0].data[0].low, - max: record.data.series[0].data[0].high - }, false); + data.push(peer); + count++; + yvals.push(count); + } + + } + let layout = { + hoverlabel: { + bgcolor: 'white' + }, + xaxis: { + title: 'Time (' + record.data.schema.timezone + ')', + titlefont: { + family: 'Arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + ticks: 'outside', + tickcolor: '#c0cfe0', + linecolor: '#c0cfe0', + type: 'date', + range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] + }, + yaxis: { + autorange: 'reversed', + ticktext: categories, + zeroline: false, + tickvals: yvals + }, + title: { + text: record.data.schema.description, + font: { + color: '#444b6e', + size: 16 + } + }, + hovermode: 'closest', + showlegend: false, + margin: { + l: 175, + b: 150 - var i; - for (i = 0; i < record.data.series.length; i++) { - if (i > 0) { - // Only add clicks for the other peer jobs not this job. - record.data.series[i].cursor = 'pointer'; - record.data.series[i].events = { - click: clickEvent - }; - } - this.chart.addSeries(record.data.series[i], false); - } + } + }; + + layout['shapes'] = rect; + Plotly.newPlot(this.id, data, layout, {displayModeBar: false}); + + var panel = document.getElementById(this.id); + panel.on('plotly_click', function(data){ + var userOptions = data.points[0].data.chartSeries; + userOptions['action'] = 'show'; + console.log('useroptions'); + console.log(userOptions); + console.log('url'); + console.log(Ext.urlEncode(userOptions)); + Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); - this.chart.hideLoading(); - this.chart.redraw(); + }); + }); } }); diff --git a/html/gui/js/modules/job_viewer/JobViewer.js b/html/gui/js/modules/job_viewer/JobViewer.js index dcad5be084..745ea850d2 100644 --- a/html/gui/js/modules/job_viewer/JobViewer.js +++ b/html/gui/js/modules/job_viewer/JobViewer.js @@ -965,6 +965,10 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { export_option_selected: function (exportParams) { var chartPanel = this.getActiveJobSubPanel(); if (chartPanel) { + var test = document.getElementsByClassName("main-svg"); + console.log("SVG"); + console.log(test); + console.log(test[test.length-1]); chartPanel.fireEvent('export_option_selected', exportParams); } }, From 119edf39f695b456492bef98e601d21d9f1bad73 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Wed, 10 May 2023 13:49:13 -0400 Subject: [PATCH 09/47] Removed Highcharts timezone dependency (now done through moment.js), also fixed some tooltip inconsistencies with Gantt Chart and Timeseries charts compared to production. --- html/gui/js/modules/job_viewer/ChartPanel.js | 40 +++++++++++--------- html/gui/js/modules/job_viewer/GanttChart.js | 9 +++-- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 6e1570e326..7969dd5805 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -240,7 +240,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { chartOptions.series = record.data.series; chartOptions.yAxis.title.text = record.data.schema.units; chartOptions.xAxis.title.text = 'Time (' + record.data.schema.timezone + ')'; - chartOptions.credits.text = record.data.schema.source + '. Powered by XDMoD/Highcharts'; + chartOptions.credits.text = record.data.schema.source + '. Powered by XDMoD/Plotly'; chartOptions.title.text = record.data.schema.description; chartOptions.dataurl = record.store.proxy.url; this.dataurl = record.store.proxy.url; @@ -309,14 +309,16 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { if (record) { let data = []; + let tz = moment.tz.zone(record.data.schema.timezone).abbr(chartOptions.series[0].data[0].x); for (let sid = 0; sid < chartOptions.series.length; sid++) { if (chartOptions.series[sid].name === "Range") continue; let x = []; let y = []; + let colors = chartOptions.colors[sid % 10]; for(let i=0; i < chartOptions.series[sid].data.length; i++) { - x.push(moment(chartOptions.series[sid].data[i].x).format('Y-MM-DD HH:mm:ss z')); + x.push(moment.tz(chartOptions.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); y.push(chartOptions.series[sid].data[i].y); } @@ -325,16 +327,18 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { x: x, y: y, fill: 'tonexty', - fillcolor: 'rgba(47, 126, 216, 0.5)', - /*marker: { + fillcolor: 'rgb(47, 126, 216)', + marker: { size: 20, - line: { - color: chartOptions.colors[sid % 10] - } - },*/ + color: colors + }, + line: { + width: 2, + color: colors + }, hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L %Z} " + this.displayTimezone + "
" + - chartOptions.series[sid].name + ": %{y}" + + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + "" + chartOptions.series[sid].name + ": %{y}" + "", name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter+marker'}); } @@ -343,14 +347,16 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { x: x, y: y, marker: { - size: 20, - line: { - color: chartOptions.colors[sid % 10] - } - }, + size: 20, + color: colors + }, + line: { + width: 2, + color: colors + }, hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L %Z} " + this.displayTimezone + "
" + - ' ' + chartOptions.series[sid].name + ":%{y}" + + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + "" + chartOptions.series[sid].name + ":%{y}" + "", name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'lines+marker'}); } diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index 915087a7b4..3d3342faf5 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -47,11 +47,11 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, for (i = 0; i < record.data.series.length; i++) { var j = 0; for (j = 0; j < record.data.series[i].data.length; j++){ - low_data = moment(record.data.series[i].data[j].low).format('Y-MM-DD HH:mm:ss z'); - high_data = moment(record.data.series[i].data[j].high).format('Y-MM-DD HH:mm:ss z'); + low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); categories.push(record.data.categories[count]); var runtime = []; - let template = record.data.series[i].name + ": " + "" + moment(record.data.series[i].data[j].low).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment(record.data.series[i].data[j].high).format('ddd, MMM DD, HH:mm:ss z') + " "; + let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; var tooltip = [template]; var ticks = [count]; var start_time = record.data.series[i].data[j].low; @@ -99,7 +99,8 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, color: 'rgb(155,255,255)' }, orientation: 'h', - hovertemplate: record.data.categories[count] + '
%{text} ', + hovertemplate: record.data.categories[count] + '
'+ + "" + '%{text} ', text: tooltip, chartSeries: info }; From 6b55daf1b1478778ed8faa68301fda36c954162f Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Wed, 10 May 2023 17:25:10 -0400 Subject: [PATCH 10/47] Basic support for hover events to better model highcharts orginal hover functionality. --- html/gui/js/modules/job_viewer/ChartPanel.js | 60 ++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 7969dd5805..994b7eceda 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -458,6 +458,66 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); Ext.History.add(token); }); + panel.chart.on('plotly_hover', function(data){ + var pts = ''; + for(var i=0; i < data.points.length; i++){ + pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; + } + console.log('Closest point clicked:\n\n'+pts); + console.log(data); + + + var update = { + line:{ + width: 3 + }, + }; + var time = moment(data.points[0].x).subtract(1000, 's').format('Y-MM-DD HH:mm:ss.SSS '); + console.log("time"); + console.log(time); + var layoutUpdate = { + shapes: [ + { + type: 'circle', + xref: 'x', + yref: 'y', + x0: time, + y0: data.points[0].y - 0.5, + x1: moment(time).add(600, 's').format('Y-MM-DD HH:mm:ss.SSS '), + y1: data.points[0].y + 0.5, + line: { + color: data.points[0].data.marker.color + }, + fillcolor: data.points[0].data.marker.color + + }, + + + + ] + + }; + + + console.log("shape created"); + console.log(layoutUpdate); + Plotly.restyle(panel.chart, update, data.points[0].curveNumber); + Plotly.relayout(panel.chart, layoutUpdate); + }); + panel.chart.on('plotly_unhover', function(data){ + var pts = ''; + for(var i=0; i < data.points.length; i++){ + pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; + } + console.log('Closest point clicked:\n\n'+pts); + console.log(data); + + var update = {'line':{width: 2}}; + var layoutUpdate = {shapes: []}; + Plotly.restyle(panel.chart, update, data.points[0].curveNumber); + Plotly.relayout(panel.chart, layoutUpdate); + }); + } } if (!record) { From 2fef931e77bf57b8a5e73cb73fc1ac5c00a2dbd6 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Mon, 15 May 2023 11:46:03 -0400 Subject: [PATCH 11/47] Updates to chart responsiveness, axis formatting, and hover events. --- .../modules/job_viewer/AnalyticChartPanel.js | 29 +--- html/gui/js/modules/job_viewer/ChartPanel.js | 136 ++++++++---------- html/gui/js/modules/job_viewer/GanttChart.js | 2 +- 3 files changed, 62 insertions(+), 105 deletions(-) diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 84e6d1577d..ee194f1e4e 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -97,11 +97,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { 'xaxis': { 'showticklabels': false, 'range': [0,1], - 'titlefont': { - 'family': 'Arial, sans-serif', - 'size': 12, - 'color': '#5078a0' - }, 'color': '#606060', 'ticks': 'inside', 'tick0': 0.0, @@ -115,16 +110,10 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { 'zerolinecolor': 'black', 'showline': false, 'zerolinewidth': 0, - 'tickformat': "%Y-%m-%d", 'fixedrange': true }, 'yaxis': { 'showticklabels': false, - 'titlefont': { - 'family': 'Arial, sans-serif', - 'size': 12, - 'color': '#1199FF' - }, 'color': '#606060', 'showgrid' : false, 'gridcolor': '#c0c0c0', @@ -136,18 +125,9 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { 'zerolinewidth': 0, 'fixedrange': true }, - 'title': { - 'font': { - 'color': '#444b6e', - 'size': 16 - } - }, 'hovermode': false, 'showlegend': false, - 'legend': { - 'orientation': 'h', - 'y': -0.2 - }, + 'autosize': true, 'margin': { 't': 10, 'l': 7.5, @@ -160,7 +140,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { traces: [], config: { displayModeBar: false, - }, }, @@ -211,7 +190,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ update_data: function(data) { this._updateData(data); - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // update_data /** @@ -221,7 +200,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { reset: function() { if (this.chart) { this.traces = []; - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); } }, // reset @@ -247,7 +226,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { if (this.chart) { this._DEFAULT_CONFIG.layout['width'] = adjWidth; this._DEFAULT_CONFIG.layout['height'] = adjHeight; - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); if (this.errorMsg) { this.updateErrorMessage(this.errorMsg.text.textStr); } diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 994b7eceda..f3bfcb50e5 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -309,16 +309,26 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { if (record) { let data = []; - let tz = moment.tz.zone(record.data.schema.timezone).abbr(chartOptions.series[0].data[0].x); - + var tz = moment.tz.zone(record.data.schema.timezone).abbr(chartOptions.series[0].data[0].x); + var ymin, ymax; + if (chartOptions.series[0].name === "Range"){ + ymin = chartOptions.series[1].data[0].y; + ymax = ymin; + } + else{ + ymin = chartOptions.series[0].data[0].y; + ymax = ymin; + } for (let sid = 0; sid < chartOptions.series.length; sid++) { - if (chartOptions.series[sid].name === "Range") continue; + if (chartOptions.series[sid].name === "Range") { + tz = moment.tz.zone(record.data.schema.timezone).abbr(chartOptions.series[1].data[0].x); + continue; + } let x = []; let y = []; let colors = chartOptions.colors[sid % 10]; - for(let i=0; i < chartOptions.series[sid].data.length; i++) { - x.push(moment.tz(chartOptions.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); + x.push(moment.tz(chartOptions.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); y.push(chartOptions.series[sid].data[i].y); } @@ -327,9 +337,9 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { x: x, y: y, fill: 'tonexty', - fillcolor: 'rgb(47, 126, 216)', + fillcolor: '#2f7ed8', marker: { - size: 20, + size: 0.1, color: colors }, line: { @@ -340,14 +350,14 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + "" + chartOptions.series[sid].name + ": %{y}" + "", - name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter+marker'}); + name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}); } else{ data.push({ x: x, y: y, marker: { - size: 20, + size: 0.1, color: colors }, line: { @@ -358,9 +368,14 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + "" + chartOptions.series[sid].name + ":%{y}" + "", - name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'lines+marker'}); - } + name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}); } + var tempyMin = Math.min(...y); + var tempyMax = Math.max(...y); + if (tempyMin < ymin) ymin = tempyMin; + if (tempyMax > ymax) ymax = tempyMax; + } + panel.getEl().unmask(); console.log("data"); console.log(data); @@ -369,7 +384,6 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { console.log("record"); console.log(record); console.log("SVG"); - console.log(document.getElementsByClassName("main-svg")); let layout = { hoverlabel: { bgcolor: 'white' @@ -383,11 +397,11 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, color: '#606060', ticks: 'outside', - autorange: true, ticklen: 10, tickcolor: '#c0cfe0', linecolor: '#c0cfe0', - showgrid: false + automargin: true, + showgrid: false }, yaxis: { title: '' + record.data.schema.units + '', @@ -397,8 +411,10 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { color: '#5078a0' }, color: '#606060', - autorange: true, + range: [0, ymax + (ymax * 0.2)], rangemode: 'nonnegative', + gridcolor: 'lightgray', + automargin: true, linecolor: '#c0cfe0' }, title: { @@ -410,32 +426,26 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, hovermode: 'closest', showlegend: false, + autosize: true, margin: { t: 50 } }; - + console.log('layout'); + console.log(layout); if (panel.chart) { - Plotly.react(this.id, data, layout, {displayModeBar: false } ); + Plotly.react(this.id, data, layout, {displayModeBar: false} ); } else { - Plotly.newPlot(this.id, data, layout, {displayModeBar: false } ); + Plotly.newPlot(this.id, data, layout, {displayModeBar: false} ); } if (!panel.chart) { panel.chart = document.getElementById(this.id); panel.chart.on('plotly_click', function(data, event){ - var pts = ''; - for(var i=0; i < data.points.length; i++){ - pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; - } - console.log('Closest point clicked:\n\n'+pts); - console.log(data); - var userOptions = data.points[0].data.chartSeries if (!userOptions || !userOptions.dtype) { return; } - var drilldown; /* * The drilldown data are stored on each point for envelope @@ -459,63 +469,31 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { Ext.History.add(token); }); panel.chart.on('plotly_hover', function(data){ - var pts = ''; - for(var i=0; i < data.points.length; i++){ - pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; - } - console.log('Closest point clicked:\n\n'+pts); - console.log(data); - - + if (!data.points) return; + setTimeout(() => {}, 50); + console.log(data); + let idx = data.points[0].pointNumber; + var sizes = Array(data.points[0].data.x.length).fill(1); + sizes[idx] = 12; var update = { - line:{ - width: 3 - }, - }; - var time = moment(data.points[0].x).subtract(1000, 's').format('Y-MM-DD HH:mm:ss.SSS '); - console.log("time"); - console.log(time); - var layoutUpdate = { - shapes: [ - { - type: 'circle', - xref: 'x', - yref: 'y', - x0: time, - y0: data.points[0].y - 0.5, - x1: moment(time).add(600, 's').format('Y-MM-DD HH:mm:ss.SSS '), - y1: data.points[0].y + 0.5, - line: { - color: data.points[0].data.marker.color - }, - fillcolor: data.points[0].data.marker.color - - }, - - - - ] - - }; - - - console.log("shape created"); - console.log(layoutUpdate); - Plotly.restyle(panel.chart, update, data.points[0].curveNumber); - Plotly.relayout(panel.chart, layoutUpdate); + 'marker.size': [sizes], + 'line.width': 3 + }; + Plotly.restyle(panel.chart, update, data.points[0].curveNumber); }); panel.chart.on('plotly_unhover', function(data){ - var pts = ''; - for(var i=0; i < data.points.length; i++){ - pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; - } - console.log('Closest point clicked:\n\n'+pts); - console.log(data); - - var update = {'line':{width: 2}}; - var layoutUpdate = {shapes: []}; + if (!data.points) return; + var update = { + line:{ + width: 2, + color: data.points[0].data.line.color + }, + marker:{ + size: 0.1, + color: data.points[0].data.marker.color + } + }; Plotly.restyle(panel.chart, update, data.points[0].curveNumber); - Plotly.relayout(panel.chart, layoutUpdate); }); } diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index 3d3342faf5..5f43967806 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -152,7 +152,7 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, }; layout['shapes'] = rect; - Plotly.newPlot(this.id, data, layout, {displayModeBar: false}); + Plotly.newPlot(this.id, data, layout, {displayModeBar: false, responsive: true}); var panel = document.getElementById(this.id); panel.on('plotly_click', function(data){ From 833436c661127f9444855b070ddf08358b0634ad Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 9 May 2023 02:04:22 -0400 Subject: [PATCH 12/47] Gantt chart Plotly converstion. Also includes drilldown fix for node/peers and fixes rendering of envelope plots. --- .../modules/job_viewer/AnalyticChartPanel.js | 45 ++--- html/gui/js/modules/job_viewer/ChartPanel.js | 82 ++++++--- html/gui/js/modules/job_viewer/ChartTab.js | 5 +- html/gui/js/modules/job_viewer/GanttChart.js | 161 ++++++++++++++---- html/gui/js/modules/job_viewer/JobViewer.js | 4 + 5 files changed, 208 insertions(+), 89 deletions(-) diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 421d3203dc..84e6d1577d 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -78,7 +78,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { }, { value: .75, - color: '#rgb(221,223,0)' + color: 'rgb(221,223,0)' }, { value: 1, @@ -115,7 +115,8 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { 'zerolinecolor': 'black', 'showline': false, 'zerolinewidth': 0, - 'tickformat': "%Y-%m-%d" + 'tickformat': "%Y-%m-%d", + 'fixedrange': true }, 'yaxis': { 'showticklabels': false, @@ -126,13 +127,14 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { }, 'color': '#606060', 'showgrid' : false, - 'gridcolor': 'white', + 'gridcolor': '#c0c0c0', 'linecolor': 'white', 'zeroline': false, 'zerolinecolor': 'white', 'showline': false, 'rangemode': 'tozero', - 'zerolinewidth': 0 + 'zerolinewidth': 0, + 'fixedrange': true }, 'title': { 'font': { @@ -140,7 +142,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { 'size': 16 } }, - 'hovermode': 'closest', + 'hovermode': false, 'showlegend': false, 'legend': { 'orientation': 'h', @@ -156,6 +158,10 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { }, traces: [], + config: { + displayModeBar: false, + + }, }, @@ -192,8 +198,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { render: function() { Ext.apply(this.chartOptions, this._DEFAULT_CONFIG.chartOptions); - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, {displayModeBar: false} ); - + Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // render @@ -206,7 +211,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ update_data: function(data) { this._updateData(data); - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, {displayModeBar: false} ); + Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // update_data /** @@ -216,7 +221,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { reset: function() { if (this.chart) { this.traces = []; - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, {displayModeBar: false} ); + Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); } }, // reset @@ -242,7 +247,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { if (this.chart) { this._DEFAULT_CONFIG.layout['width'] = adjWidth; this._DEFAULT_CONFIG.layout['height'] = adjHeight; - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this.layout, {displayModeBar: false, responsive: true}); + Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); if (this.errorMsg) { this.updateErrorMessage(this.errorMsg.text.textStr); } @@ -275,9 +280,10 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { "y": 1.2 } ] + console.log("new annontation"); this._DEFAULT_CONFIG.layout['annotations'] = [ { - "text": errorStr, + "text": wordwrap.wrap(errorStr, {width: 20}), "align": "left", "xref": "paper", "yref": "paper", @@ -286,18 +292,9 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { "x" : 0.5, "y" : 0.5, "showarrow": false - - - } ] this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = false; - - /*this.errorMsg = { text: this.chart.renderer.text(errorStr, this.chart.plotLeft + 23, this.chart.plotTop + 10) - .css({ width: this.chart.chartWidth - this.chart.plotLeft - 23 }) - .add() }; - var box = this.errorMsg.text.getBBox(); - this.errorMsg.image = this.chart.renderer.image('/gui/images/about_16.png', box.x - 23, box.y - 1, 16, 16).add();*/ } else { this._DEFAULT_CONFIG.layout['images'] = []; @@ -320,8 +317,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { this._DEFAULT_CONFIG.traces = []; - //this.chart.layout.push(); - //this.chart.chartBackground = null; this._DEFAULT_CONFIG.layout['plot_bgcolor'] = 'white'; @@ -331,8 +326,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { bg_color = color.substring(0,3) + 'a' + color.substring(3, color.length-1) + ',0.4)'; this._DEFAULT_CONFIG.layout['plot_bgcolor'] = bg_color; - //nColor = new Highcharts.Color(color).brighten(brightFactor); - //this.layout.push({plot_bgcolor: 'rgba(' + nColor.rgba + ')'}); this._DEFAULT_CONFIG.traces.push( { x: [data.value], @@ -349,13 +342,9 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { orientation: 'h' } ); - //this.layout.push({title: data.name ? data.name : ''}); - //data.push({x: [data.value], type: 'bar', color: color}); } this.updateErrorMessage(data.error); - console.log(this._DEFAULT_CONFIG.traces); this._updateTitle(data); - console.log(this._DEFAULT_CONFIG.layout); this.ownerCt.doLayout(false, true); diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 0b07b7cc5a..6e1570e326 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -9,7 +9,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { // The default chart config options. _DEFAULT_CONFIG: { - chartPrefix: 'CreateChartPanel', + chartPrefix: 'CreateChartPanel', chartOptions: { chart: { type: 'line', @@ -311,26 +311,59 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { let data = []; for (let sid = 0; sid < chartOptions.series.length; sid++) { - let x = []; - let y = []; + if (chartOptions.series[sid].name === "Range") continue; + let x = []; + let y = []; + for(let i=0; i < chartOptions.series[sid].data.length; i++) { x.push(moment(chartOptions.series[sid].data[i].x).format('Y-MM-DD HH:mm:ss z')); y.push(chartOptions.series[sid].data[i].y); } - data.push({ - x: x, - y: y, - line: { - color: chartOptions.colors[sid % 10] - }, - hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L %Z}

" + - chartOptions.series[sid].name + ": %{y}" + - "", - name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter'}); - } - panel.getEl().unmask(); + if (chartOptions.series[sid].name === "Median" || chartOptions.series[sid].name === "Minimum"){ + data.push({ + x: x, + y: y, + fill: 'tonexty', + fillcolor: 'rgba(47, 126, 216, 0.5)', + /*marker: { + size: 20, + line: { + color: chartOptions.colors[sid % 10] + } + },*/ + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L %Z} " + this.displayTimezone + "
" + + chartOptions.series[sid].name + ": %{y}" + + "", + name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter+marker'}); + } + else{ + data.push({ + x: x, + y: y, + marker: { + size: 20, + line: { + color: chartOptions.colors[sid % 10] + } + }, + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L %Z} " + this.displayTimezone + "
" + + ' ' + chartOptions.series[sid].name + ":%{y}" + + "", + name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'lines+marker'}); + } + } + panel.getEl().unmask(); + console.log("data"); + console.log(data); + console.log("chart options data"); + console.log(chartOptions); + console.log("record"); + console.log(record); + console.log("SVG"); + console.log(document.getElementsByClassName("main-svg")); let layout = { hoverlabel: { bgcolor: 'white' @@ -344,7 +377,8 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, color: '#606060', ticks: 'outside', - ticklen: 10, + autorange: true, + ticklen: 10, tickcolor: '#c0cfe0', linecolor: '#c0cfe0', showgrid: false @@ -357,7 +391,8 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { color: '#5078a0' }, color: '#606060', - rangemode: 'tozero', + autorange: true, + rangemode: 'nonnegative', linecolor: '#c0cfe0' }, title: { @@ -382,7 +417,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { if (!panel.chart) { panel.chart = document.getElementById(this.id); - panel.chart.on('plotly_click', function(data){ + panel.chart.on('plotly_click', function(data, event){ var pts = ''; for(var i=0; i < data.points.length; i++){ pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; @@ -390,7 +425,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { console.log('Closest point clicked:\n\n'+pts); console.log(data); - var userOptions = data.points[0].data.chartSeries; + var userOptions = data.points[0].data.chartSeries if (!userOptions || !userOptions.dtype) { return; } @@ -401,9 +436,11 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { * plots and for the series for simple plots. */ if (userOptions.dtype == 'index') { + var nodeidIndex = data.points[0].data.chartSeries.data.findIndex(({y}) => y === data.points[0].y); + if (nodeidIndex === -1) return; drilldown = { dtype: userOptions.index, - value: event.point.options[userOptions.index] + value: userOptions.data[nodeidIndex].nodeid }; } else { drilldown = { @@ -416,8 +453,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { Ext.History.add(token); }); } - } - + } if (!record) { panel.getEl().mask('Loading...'); } diff --git a/html/gui/js/modules/job_viewer/ChartTab.js b/html/gui/js/modules/job_viewer/ChartTab.js index 0009a4218d..88b22df468 100644 --- a/html/gui/js/modules/job_viewer/ChartTab.js +++ b/html/gui/js/modules/job_viewer/ChartTab.js @@ -99,8 +99,9 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { var chartOptions = jQuery.extend(true, {}, defaultChartSettings, self.chartSettings); - self.chart = new Highcharts.Chart(chartOptions); - self.chart.showLoading(); + //self.chart = new Highcharts.Chart(chartOptions); + //self.chart.showLoading(); + Plotly.newPlot(this.id, [], {}); var storeParams; if (self.panelSettings.pageSize) { diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index e3a6b969e5..915087a7b4 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -34,48 +34,137 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, return; } var record = store.getAt(0); - + console.log("record"); + console.log(record); this.updateTimezone(record.data.schema.timezone); + var i; + var data = []; + var categories = []; + var count = 0; + var rect = []; + var yvals = [0]; + let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + for (i = 0; i < record.data.series.length; i++) { + var j = 0; + for (j = 0; j < record.data.series[i].data.length; j++){ + low_data = moment(record.data.series[i].data[j].low).format('Y-MM-DD HH:mm:ss z'); + high_data = moment(record.data.series[i].data[j].high).format('Y-MM-DD HH:mm:ss z'); + categories.push(record.data.categories[count]); + var runtime = []; + let template = record.data.series[i].name + ": " + "" + moment(record.data.series[i].data[j].low).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment(record.data.series[i].data[j].high).format('ddd, MMM DD, HH:mm:ss z') + " "; + var tooltip = [template]; + var ticks = [count]; + var start_time = record.data.series[i].data[j].low; + + // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. + // Points on the scatter plot are created every min to create better coverage for tooltip information + while (start_time < record.data.series[i].data[j].high){ + ticks.push(count); + tooltip.push(template); + runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); + start_time += (60 * 1000); + } + runtime.push(high_data); + + rect.push({ + x0: low_data, + x1: high_data, + y0: count-0.25, + y1: count+0.25, + type: 'rect', + xref: 'x', + yref: 'y', + fillcolor: colors[i % 10], + color: colors[i % 10] + }); - var clickEvent = function (evt) { - var info = { - action: 'show', - title: 'Peers of ' + record.data.schema.ref.text, - realm: evt.point.ref.realm, - jobref: evt.point.ref.jobid - }; - Ext.History.add('job_viewer?' + Ext.urlEncode(info)); - }; + + var info = {} - while (this.chart.series.length > 0) { - this.chart.series[0].remove(false); - } + if (i > 0){ + info = { + realm: record.data.series[i].data[j].ref.realm, + recordid: store.baseParams.recordid, + jobref: record.data.series[i].data[j].ref.jobid, + infoid: 3 + }; + } + + peer = { + x: runtime, + y: ticks, + type: 'scatter', + + marker:{ + color: 'rgb(155,255,255)' + }, + orientation: 'h', + hovertemplate: record.data.categories[count] + '
%{text} ', + text: tooltip, + chartSeries: info + }; - this.chart.colorCounter = 0; - this.chart.symbolCounter = 0; - this.chart.xAxis[0].setCategories(record.data.categories, false); - this.chart.yAxis[0].update({ - title: { - text: 'Time (' + record.data.schema.timezone + ')' - }, - min: record.data.series[0].data[0].low, - max: record.data.series[0].data[0].high - }, false); + data.push(peer); + count++; + yvals.push(count); + } + + } + let layout = { + hoverlabel: { + bgcolor: 'white' + }, + xaxis: { + title: 'Time (' + record.data.schema.timezone + ')', + titlefont: { + family: 'Arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + ticks: 'outside', + tickcolor: '#c0cfe0', + linecolor: '#c0cfe0', + type: 'date', + range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] + }, + yaxis: { + autorange: 'reversed', + ticktext: categories, + zeroline: false, + tickvals: yvals + }, + title: { + text: record.data.schema.description, + font: { + color: '#444b6e', + size: 16 + } + }, + hovermode: 'closest', + showlegend: false, + margin: { + l: 175, + b: 150 - var i; - for (i = 0; i < record.data.series.length; i++) { - if (i > 0) { - // Only add clicks for the other peer jobs not this job. - record.data.series[i].cursor = 'pointer'; - record.data.series[i].events = { - click: clickEvent - }; - } - this.chart.addSeries(record.data.series[i], false); - } + } + }; + + layout['shapes'] = rect; + Plotly.newPlot(this.id, data, layout, {displayModeBar: false}); + + var panel = document.getElementById(this.id); + panel.on('plotly_click', function(data){ + var userOptions = data.points[0].data.chartSeries; + userOptions['action'] = 'show'; + console.log('useroptions'); + console.log(userOptions); + console.log('url'); + console.log(Ext.urlEncode(userOptions)); + Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); - this.chart.hideLoading(); - this.chart.redraw(); + }); + }); } }); diff --git a/html/gui/js/modules/job_viewer/JobViewer.js b/html/gui/js/modules/job_viewer/JobViewer.js index dcad5be084..745ea850d2 100644 --- a/html/gui/js/modules/job_viewer/JobViewer.js +++ b/html/gui/js/modules/job_viewer/JobViewer.js @@ -965,6 +965,10 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { export_option_selected: function (exportParams) { var chartPanel = this.getActiveJobSubPanel(); if (chartPanel) { + var test = document.getElementsByClassName("main-svg"); + console.log("SVG"); + console.log(test); + console.log(test[test.length-1]); chartPanel.fireEvent('export_option_selected', exportParams); } }, From 5fa6be1668ae2baf79ac475dff9d2949e1e92427 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Wed, 10 May 2023 13:49:13 -0400 Subject: [PATCH 13/47] Removed Highcharts timezone dependency (now done through moment.js), also fixed some tooltip inconsistencies with Gantt Chart and Timeseries charts compared to production. --- html/gui/js/modules/job_viewer/ChartPanel.js | 40 +++++++++++--------- html/gui/js/modules/job_viewer/GanttChart.js | 9 +++-- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 6e1570e326..7969dd5805 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -240,7 +240,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { chartOptions.series = record.data.series; chartOptions.yAxis.title.text = record.data.schema.units; chartOptions.xAxis.title.text = 'Time (' + record.data.schema.timezone + ')'; - chartOptions.credits.text = record.data.schema.source + '. Powered by XDMoD/Highcharts'; + chartOptions.credits.text = record.data.schema.source + '. Powered by XDMoD/Plotly'; chartOptions.title.text = record.data.schema.description; chartOptions.dataurl = record.store.proxy.url; this.dataurl = record.store.proxy.url; @@ -309,14 +309,16 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { if (record) { let data = []; + let tz = moment.tz.zone(record.data.schema.timezone).abbr(chartOptions.series[0].data[0].x); for (let sid = 0; sid < chartOptions.series.length; sid++) { if (chartOptions.series[sid].name === "Range") continue; let x = []; let y = []; + let colors = chartOptions.colors[sid % 10]; for(let i=0; i < chartOptions.series[sid].data.length; i++) { - x.push(moment(chartOptions.series[sid].data[i].x).format('Y-MM-DD HH:mm:ss z')); + x.push(moment.tz(chartOptions.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); y.push(chartOptions.series[sid].data[i].y); } @@ -325,16 +327,18 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { x: x, y: y, fill: 'tonexty', - fillcolor: 'rgba(47, 126, 216, 0.5)', - /*marker: { + fillcolor: 'rgb(47, 126, 216)', + marker: { size: 20, - line: { - color: chartOptions.colors[sid % 10] - } - },*/ + color: colors + }, + line: { + width: 2, + color: colors + }, hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L %Z} " + this.displayTimezone + "
" + - chartOptions.series[sid].name + ": %{y}" + + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + "" + chartOptions.series[sid].name + ": %{y}" + "", name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter+marker'}); } @@ -343,14 +347,16 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { x: x, y: y, marker: { - size: 20, - line: { - color: chartOptions.colors[sid % 10] - } - }, + size: 20, + color: colors + }, + line: { + width: 2, + color: colors + }, hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L %Z} " + this.displayTimezone + "
" + - ' ' + chartOptions.series[sid].name + ":%{y}" + + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + "" + chartOptions.series[sid].name + ":%{y}" + "", name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'lines+marker'}); } diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index 915087a7b4..3d3342faf5 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -47,11 +47,11 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, for (i = 0; i < record.data.series.length; i++) { var j = 0; for (j = 0; j < record.data.series[i].data.length; j++){ - low_data = moment(record.data.series[i].data[j].low).format('Y-MM-DD HH:mm:ss z'); - high_data = moment(record.data.series[i].data[j].high).format('Y-MM-DD HH:mm:ss z'); + low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); categories.push(record.data.categories[count]); var runtime = []; - let template = record.data.series[i].name + ": " + "" + moment(record.data.series[i].data[j].low).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment(record.data.series[i].data[j].high).format('ddd, MMM DD, HH:mm:ss z') + " "; + let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; var tooltip = [template]; var ticks = [count]; var start_time = record.data.series[i].data[j].low; @@ -99,7 +99,8 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, color: 'rgb(155,255,255)' }, orientation: 'h', - hovertemplate: record.data.categories[count] + '
%{text} ', + hovertemplate: record.data.categories[count] + '
'+ + "" + '%{text} ', text: tooltip, chartSeries: info }; From eafc4e76fc55747d937b758bcbca5d05e83ef318 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Wed, 10 May 2023 17:25:10 -0400 Subject: [PATCH 14/47] Basic support for hover events to better model highcharts orginal hover functionality. --- html/gui/js/modules/job_viewer/ChartPanel.js | 60 ++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 7969dd5805..994b7eceda 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -458,6 +458,66 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); Ext.History.add(token); }); + panel.chart.on('plotly_hover', function(data){ + var pts = ''; + for(var i=0; i < data.points.length; i++){ + pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; + } + console.log('Closest point clicked:\n\n'+pts); + console.log(data); + + + var update = { + line:{ + width: 3 + }, + }; + var time = moment(data.points[0].x).subtract(1000, 's').format('Y-MM-DD HH:mm:ss.SSS '); + console.log("time"); + console.log(time); + var layoutUpdate = { + shapes: [ + { + type: 'circle', + xref: 'x', + yref: 'y', + x0: time, + y0: data.points[0].y - 0.5, + x1: moment(time).add(600, 's').format('Y-MM-DD HH:mm:ss.SSS '), + y1: data.points[0].y + 0.5, + line: { + color: data.points[0].data.marker.color + }, + fillcolor: data.points[0].data.marker.color + + }, + + + + ] + + }; + + + console.log("shape created"); + console.log(layoutUpdate); + Plotly.restyle(panel.chart, update, data.points[0].curveNumber); + Plotly.relayout(panel.chart, layoutUpdate); + }); + panel.chart.on('plotly_unhover', function(data){ + var pts = ''; + for(var i=0; i < data.points.length; i++){ + pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; + } + console.log('Closest point clicked:\n\n'+pts); + console.log(data); + + var update = {'line':{width: 2}}; + var layoutUpdate = {shapes: []}; + Plotly.restyle(panel.chart, update, data.points[0].curveNumber); + Plotly.relayout(panel.chart, layoutUpdate); + }); + } } if (!record) { From 61d05a9ef152182659ad073061e3a964d311c32d Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Mon, 15 May 2023 11:46:03 -0400 Subject: [PATCH 15/47] Updates to chart responsiveness, axis formatting, and hover events. --- .../modules/job_viewer/AnalyticChartPanel.js | 29 +--- html/gui/js/modules/job_viewer/ChartPanel.js | 136 ++++++++---------- html/gui/js/modules/job_viewer/GanttChart.js | 2 +- 3 files changed, 62 insertions(+), 105 deletions(-) diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 84e6d1577d..ee194f1e4e 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -97,11 +97,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { 'xaxis': { 'showticklabels': false, 'range': [0,1], - 'titlefont': { - 'family': 'Arial, sans-serif', - 'size': 12, - 'color': '#5078a0' - }, 'color': '#606060', 'ticks': 'inside', 'tick0': 0.0, @@ -115,16 +110,10 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { 'zerolinecolor': 'black', 'showline': false, 'zerolinewidth': 0, - 'tickformat': "%Y-%m-%d", 'fixedrange': true }, 'yaxis': { 'showticklabels': false, - 'titlefont': { - 'family': 'Arial, sans-serif', - 'size': 12, - 'color': '#1199FF' - }, 'color': '#606060', 'showgrid' : false, 'gridcolor': '#c0c0c0', @@ -136,18 +125,9 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { 'zerolinewidth': 0, 'fixedrange': true }, - 'title': { - 'font': { - 'color': '#444b6e', - 'size': 16 - } - }, 'hovermode': false, 'showlegend': false, - 'legend': { - 'orientation': 'h', - 'y': -0.2 - }, + 'autosize': true, 'margin': { 't': 10, 'l': 7.5, @@ -160,7 +140,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { traces: [], config: { displayModeBar: false, - }, }, @@ -211,7 +190,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ update_data: function(data) { this._updateData(data); - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // update_data /** @@ -221,7 +200,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { reset: function() { if (this.chart) { this.traces = []; - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); } }, // reset @@ -247,7 +226,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { if (this.chart) { this._DEFAULT_CONFIG.layout['width'] = adjWidth; this._DEFAULT_CONFIG.layout['height'] = adjHeight; - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); if (this.errorMsg) { this.updateErrorMessage(this.errorMsg.text.textStr); } diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 994b7eceda..f3bfcb50e5 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -309,16 +309,26 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { if (record) { let data = []; - let tz = moment.tz.zone(record.data.schema.timezone).abbr(chartOptions.series[0].data[0].x); - + var tz = moment.tz.zone(record.data.schema.timezone).abbr(chartOptions.series[0].data[0].x); + var ymin, ymax; + if (chartOptions.series[0].name === "Range"){ + ymin = chartOptions.series[1].data[0].y; + ymax = ymin; + } + else{ + ymin = chartOptions.series[0].data[0].y; + ymax = ymin; + } for (let sid = 0; sid < chartOptions.series.length; sid++) { - if (chartOptions.series[sid].name === "Range") continue; + if (chartOptions.series[sid].name === "Range") { + tz = moment.tz.zone(record.data.schema.timezone).abbr(chartOptions.series[1].data[0].x); + continue; + } let x = []; let y = []; let colors = chartOptions.colors[sid % 10]; - for(let i=0; i < chartOptions.series[sid].data.length; i++) { - x.push(moment.tz(chartOptions.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); + x.push(moment.tz(chartOptions.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); y.push(chartOptions.series[sid].data[i].y); } @@ -327,9 +337,9 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { x: x, y: y, fill: 'tonexty', - fillcolor: 'rgb(47, 126, 216)', + fillcolor: '#2f7ed8', marker: { - size: 20, + size: 0.1, color: colors }, line: { @@ -340,14 +350,14 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + "" + chartOptions.series[sid].name + ": %{y}" + "", - name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter+marker'}); + name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}); } else{ data.push({ x: x, y: y, marker: { - size: 20, + size: 0.1, color: colors }, line: { @@ -358,9 +368,14 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + "" + chartOptions.series[sid].name + ":%{y}" + "", - name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'lines+marker'}); - } + name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}); } + var tempyMin = Math.min(...y); + var tempyMax = Math.max(...y); + if (tempyMin < ymin) ymin = tempyMin; + if (tempyMax > ymax) ymax = tempyMax; + } + panel.getEl().unmask(); console.log("data"); console.log(data); @@ -369,7 +384,6 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { console.log("record"); console.log(record); console.log("SVG"); - console.log(document.getElementsByClassName("main-svg")); let layout = { hoverlabel: { bgcolor: 'white' @@ -383,11 +397,11 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, color: '#606060', ticks: 'outside', - autorange: true, ticklen: 10, tickcolor: '#c0cfe0', linecolor: '#c0cfe0', - showgrid: false + automargin: true, + showgrid: false }, yaxis: { title: '' + record.data.schema.units + '', @@ -397,8 +411,10 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { color: '#5078a0' }, color: '#606060', - autorange: true, + range: [0, ymax + (ymax * 0.2)], rangemode: 'nonnegative', + gridcolor: 'lightgray', + automargin: true, linecolor: '#c0cfe0' }, title: { @@ -410,32 +426,26 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, hovermode: 'closest', showlegend: false, + autosize: true, margin: { t: 50 } }; - + console.log('layout'); + console.log(layout); if (panel.chart) { - Plotly.react(this.id, data, layout, {displayModeBar: false } ); + Plotly.react(this.id, data, layout, {displayModeBar: false} ); } else { - Plotly.newPlot(this.id, data, layout, {displayModeBar: false } ); + Plotly.newPlot(this.id, data, layout, {displayModeBar: false} ); } if (!panel.chart) { panel.chart = document.getElementById(this.id); panel.chart.on('plotly_click', function(data, event){ - var pts = ''; - for(var i=0; i < data.points.length; i++){ - pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; - } - console.log('Closest point clicked:\n\n'+pts); - console.log(data); - var userOptions = data.points[0].data.chartSeries if (!userOptions || !userOptions.dtype) { return; } - var drilldown; /* * The drilldown data are stored on each point for envelope @@ -459,63 +469,31 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { Ext.History.add(token); }); panel.chart.on('plotly_hover', function(data){ - var pts = ''; - for(var i=0; i < data.points.length; i++){ - pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; - } - console.log('Closest point clicked:\n\n'+pts); - console.log(data); - - + if (!data.points) return; + setTimeout(() => {}, 50); + console.log(data); + let idx = data.points[0].pointNumber; + var sizes = Array(data.points[0].data.x.length).fill(1); + sizes[idx] = 12; var update = { - line:{ - width: 3 - }, - }; - var time = moment(data.points[0].x).subtract(1000, 's').format('Y-MM-DD HH:mm:ss.SSS '); - console.log("time"); - console.log(time); - var layoutUpdate = { - shapes: [ - { - type: 'circle', - xref: 'x', - yref: 'y', - x0: time, - y0: data.points[0].y - 0.5, - x1: moment(time).add(600, 's').format('Y-MM-DD HH:mm:ss.SSS '), - y1: data.points[0].y + 0.5, - line: { - color: data.points[0].data.marker.color - }, - fillcolor: data.points[0].data.marker.color - - }, - - - - ] - - }; - - - console.log("shape created"); - console.log(layoutUpdate); - Plotly.restyle(panel.chart, update, data.points[0].curveNumber); - Plotly.relayout(panel.chart, layoutUpdate); + 'marker.size': [sizes], + 'line.width': 3 + }; + Plotly.restyle(panel.chart, update, data.points[0].curveNumber); }); panel.chart.on('plotly_unhover', function(data){ - var pts = ''; - for(var i=0; i < data.points.length; i++){ - pts = 'x = '+data.points[i].x +'\ny = '+ data.points[i].y.toPrecision(4) + '\n\n'; - } - console.log('Closest point clicked:\n\n'+pts); - console.log(data); - - var update = {'line':{width: 2}}; - var layoutUpdate = {shapes: []}; + if (!data.points) return; + var update = { + line:{ + width: 2, + color: data.points[0].data.line.color + }, + marker:{ + size: 0.1, + color: data.points[0].data.marker.color + } + }; Plotly.restyle(panel.chart, update, data.points[0].curveNumber); - Plotly.relayout(panel.chart, layoutUpdate); }); } diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index 3d3342faf5..5f43967806 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -152,7 +152,7 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, }; layout['shapes'] = rect; - Plotly.newPlot(this.id, data, layout, {displayModeBar: false}); + Plotly.newPlot(this.id, data, layout, {displayModeBar: false, responsive: true}); var panel = document.getElementById(this.id); panel.on('plotly_click', function(data){ From 401e1893214fa784cc8eb2a86304c34ab9728ba1 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 16 May 2023 15:32:43 -0400 Subject: [PATCH 16/47] Update export functionality for Plotly charts. --- .../WarehouseControllerProvider.php | 73 ++++---- html/highchart_template.html | 97 ---------- html/plotly_template.html | 166 ++++++++++++++++++ libraries/charting.php | 28 ++- 4 files changed, 218 insertions(+), 146 deletions(-) delete mode 100644 html/highchart_template.html create mode 100644 html/plotly_template.html diff --git a/classes/Rest/Controllers/WarehouseControllerProvider.php b/classes/Rest/Controllers/WarehouseControllerProvider.php index 6072de3a47..2ab9191d7c 100644 --- a/classes/Rest/Controllers/WarehouseControllerProvider.php +++ b/classes/Rest/Controllers/WarehouseControllerProvider.php @@ -1984,47 +1984,31 @@ private function chartImageResponse($data, $type, $settings) '#f28f43', '#77a1e5', '#c42525', '#a6c96a' ), 'series' => $data['series'], - 'xAxis' => array( - 'type' => 'datetime', - 'minTickInterval' => 1000, - 'labels' => array( - 'style' => array( - 'fontWeight'=> 'normal', - 'fontSize' => $axisLabelFontSize - ), + 'xaxis' => array( + 'tickfont' => array( + 'size' => $axisLabelFontSize ), - 'lineWidth' => $lineWidth, - 'title' => array( - 'style' => array( - 'fontWeight' => 'bold', - 'fontSize' => $axisTitleFontSize, + 'zerolinewidth' => $lineWidth, + 'title' => '' + 'Time (' . $data['schema']['timezone'] . ')' + '', + 'titlefont' => array( + 'size' => $axisTitleFontSize, 'color' => '#5078a0' - ), - 'text' => 'Time (' . $data['schema']['timezone'] . ')' ) ), - 'yAxis' => array( - 'title' => array( - 'style' => array( - 'fontWeight' => 'bold', - 'fontSize' => $axisTitleFontSize, + 'yaxis' => array( + 'title' => '' + 'Time (' . $data['schema']['units'] . ')' + '', + 'titlefont' => array( + 'size' => $axisTitleFontSize, 'color' => '#5078a0' - ), - 'text' => $data['schema']['units'] ), - 'lineWidth' => $lineWidth, - 'labels' => array( - 'style' => array( - 'fontWeight'=> 'normal', - 'fontSize' => $axisLabelFontSize - ), + 'zerolinewidth' => $lineWidth, + 'ticfont' => array( + 'size' => $axisLabelFontSize ), - 'min' => 0.0 + 'rangemode' => 'nonnegative' ), - 'legend' => array( - 'enabled' => false - ), - 'plotOptions' => array( + 'showlegend' => false, + 'plotOptions' => array( 'line' => array( 'lineWidth' => $lineWidth, 'marker' => array( @@ -2032,23 +2016,30 @@ private function chartImageResponse($data, $type, $settings) ) ) ), - 'credits' => array( - 'text' => $data['schema']['source'] . '. Powered by XDMoD/Highcharts', - 'href' => '' - ), - 'exporting' => array( - 'enabled' => false + 'annotations' => array( + 'text' => $data['schema']['source'] . '. Powered by XDMoD/Plotly', + 'xref' => 'paper', + 'yref' => 'paper', + 'x' => 1, + 'xanchor' => 'left', + 'y' => 0, + 'yanchor' => 'top', + 'showarrow' => false ), 'title' => array( - 'style' => array( + 'font' => array( 'color' => '#444b6e', - 'fontSize' => $mainTitleFontSize + 'size' => $mainTitleFontSize ), 'text' => $settings['show_title'] ? $data['schema']['description'] : null ) ); + /*if (strpos($data['schema']['units'], '%') !== false) { + $chartConfig['yAxis']['max'] = 100.0; + }*/ + $globalConfig = array( 'timezone' => $data['schema']['timezone'] ); diff --git a/html/highchart_template.html b/html/highchart_template.html deleted file mode 100644 index 81c16c6471..0000000000 --- a/html/highchart_template.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - Highcharts Template - - - - - - - - - - - - - - - - - - - - - - -
- - - - diff --git a/html/plotly_template.html b/html/plotly_template.html new file mode 100644 index 0000000000..2ed564a1e2 --- /dev/null +++ b/html/plotly_template.html @@ -0,0 +1,166 @@ + + + + + + Plotly Template + + + + + + + + + + + +
+ + + + + +
+ + + + diff --git a/libraries/charting.php b/libraries/charting.php index 684eb76c47..6e98c6a0a9 100644 --- a/libraries/charting.php +++ b/libraries/charting.php @@ -47,18 +47,21 @@ function exportHighchart( $effectiveHeight = (int)($height*$scale); $html_dir = __DIR__ . "/../html"; - $template = file_get_contents($html_dir . "/highchart_template.html"); + $template = file_get_contents($html_dir . "/plotly_template.html"); $template = str_replace('_html_dir_', $html_dir, $template); $template = str_replace('_width_', $effectiveWidth, $template); $template = str_replace('_height_', $effectiveHeight, $template); - $globalChartOptions = array('timezone' => date_default_timezone_get()); + /*$globalChartOptions = array('timezone' => date_default_timezone_get()); if ($globalChartConfig !== null) { $globalChartOptions = array_merge($globalChartOptions, $globalChartConfig); } - $template = str_replace('_globalChartOptions_', json_encode($globalChartOptions), $template); + $template = str_replace('_globalChartOptions_', json_encode($globalChartOptions), $template);*/ + $template = str_replace('_chartOptions_', json_encode($chartConfig), $template); + + // Open the file using the HTTP headers set above $svg = getSvgFromChromium($template, $effectiveWidth, $effectiveHeight); switch($format){ case 'png': @@ -119,8 +122,8 @@ function getSvgFromChromium($html, $width, $height){ @unlink($tmpHtmlFile); throw new \Exception('Unable execute command: "'. $command . '". Details: ' . print_r(error_get_last(), true)); } - fwrite($pipes[0], 'chart.getSVG(inputChartOptions);'); - fclose($pipes[0]); + //fwrite($pipes[0], 'chart.getSVG(inputChartOptions);'); + //fclose($pipes[0]); $out = stream_get_contents($pipes[1]); $err = stream_get_contents($pipes[2]); @@ -130,13 +133,22 @@ function getSvgFromChromium($html, $width, $height){ @unlink($tmpHtmlFile); - $result = json_decode(substr($out, 4, -6), true); + $jsondata = json_decode(substr($out, 4, -6), true); + + $chartSvg = null; + if (isset($jsondata['result']) && isset($jsondata['result']['value'])) { + // chrome headless 99 + $chartSvg = $jsondata['result']['value']; + } elseif (isset($jsondata['result']) && isset($jsondata['result']['result']) && isset($jsondata['result']['result']['value'])) { + // chrome headless 109 + $chartSvg = $jsondata['result']['result']['value']; + } - if ($result === null || !isset($result['result']) || !isset($result['result']['value'])) { + if ($chartSvg === null) { throw new \Exception('Error executing command: "'. $command . '". Details: ' . $return_value . " " . $out . ' Errors: ' . $err); } - return $result['result']['value']; + return $chartSvg; } /** From 4488665b136a5179d34670b504f9ce2b17899fcd Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 16 May 2023 23:42:36 -0400 Subject: [PATCH 17/47] Update charts with one data point for a trace, update analytic chart error message format. --- .../modules/job_viewer/AnalyticChartPanel.js | 16 +++++++++------- html/gui/js/modules/job_viewer/ChartPanel.js | 19 ++++++++++++++++--- libraries/charting.php | 19 ------------------- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index ee194f1e4e..56731c4904 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -251,10 +251,11 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { this._DEFAULT_CONFIG.layout['images'] = [ { "source": '/gui/images/about_16.png', + "align": "left", "xref": "paper", "yref": "paper", - "sizex": 0.5, - "sizey": 0.5, + "sizex": 0.4, + "sizey": 0.4, "x": 0, "y": 1.2 } @@ -262,14 +263,15 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { console.log("new annontation"); this._DEFAULT_CONFIG.layout['annotations'] = [ { - "text": wordwrap.wrap(errorStr, {width: 20}), + "text": '' + errorStr + '', "align": "left", "xref": "paper", "yref": "paper", - "sizex": 1, - "sizey": 1, - "x" : 0.5, - "y" : 0.5, + "font":{ + "size": 11, + }, + "x" : 0.05, + "y" : 1.2, "showarrow": false } ] diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index f3bfcb50e5..81bfcb58ba 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -309,6 +309,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { if (record) { let data = []; + var singleDataPoint = false; var tz = moment.tz.zone(record.data.schema.timezone).abbr(chartOptions.series[0].data[0].x); var ymin, ymax; if (chartOptions.series[0].name === "Range"){ @@ -328,6 +329,9 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { let y = []; let colors = chartOptions.colors[sid % 10]; for(let i=0; i < chartOptions.series[sid].data.length; i++) { + if (chartOptions.series[sid].data.length == 1){ + singleDataPoint = true; + } x.push(moment.tz(chartOptions.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); y.push(chartOptions.series[sid].data[i].y); } @@ -353,7 +357,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}); } else{ - data.push({ + var trace = { x: x, y: y, marker: { @@ -368,7 +372,14 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + "" + chartOptions.series[sid].name + ":%{y}" + "", - name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}); + name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}; + + if (singleDataPoint){ + trace.marker.size = 20; + trace.mode = 'markers'; + delete trace.line; + } + data.push(trace); } var tempyMin = Math.min(...y); var tempyMax = Math.max(...y); @@ -468,12 +479,13 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); Ext.History.add(token); }); + if (!singleDataPoint){ panel.chart.on('plotly_hover', function(data){ if (!data.points) return; setTimeout(() => {}, 50); console.log(data); let idx = data.points[0].pointNumber; - var sizes = Array(data.points[0].data.x.length).fill(1); + var sizes = Array(data.points[0].data.x.length).fill(0.1); sizes[idx] = 12; var update = { 'marker.size': [sizes], @@ -495,6 +507,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }; Plotly.restyle(panel.chart, update, data.points[0].curveNumber); }); + } } } diff --git a/libraries/charting.php b/libraries/charting.php index fd84605e9f..96354f1ba7 100644 --- a/libraries/charting.php +++ b/libraries/charting.php @@ -113,11 +113,6 @@ function getSvgViaChromiumHelper($html, $width, $height){ @unlink($tmpHtmlFile); throw new \Exception('Unable execute command: "'. $command . '". Details: ' . print_r(error_get_last(), true)); } -<<<<<<< HEAD - //fwrite($pipes[0], 'chart.getSVG(inputChartOptions);'); - //fclose($pipes[0]); -======= ->>>>>>> xdmod10.5 $out = stream_get_contents($pipes[1]); $err = stream_get_contents($pipes[2]); @@ -127,22 +122,8 @@ function getSvgViaChromiumHelper($html, $width, $height){ @unlink($tmpHtmlFile); -<<<<<<< HEAD - $jsondata = json_decode(substr($out, 4, -6), true); - - $chartSvg = null; - if (isset($jsondata['result']) && isset($jsondata['result']['value'])) { - // chrome headless 99 - $chartSvg = $jsondata['result']['value']; - } elseif (isset($jsondata['result']) && isset($jsondata['result']['result']) && isset($jsondata['result']['result']['value'])) { - // chrome headless 109 - $chartSvg = $jsondata['result']['result']['value']; - } - -======= $chartSvg = json_decode($out); ->>>>>>> xdmod10.5 if ($chartSvg === null) { throw new \Exception('Error executing command: "'. $command . '". Details: ' . $return_value . " " . $out . ' Errors: ' . $err); } From bd0f587a7cbb4b3624549cffce2564f094619329 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Fri, 19 May 2023 12:34:16 -0400 Subject: [PATCH 18/47] Updates to export w/ plotly. Minor updates to timeseries charts. --- .../chrome-helper/chrome-helper.js | 2 +- html/gui/js/modules/job_viewer/ChartPanel.js | 195 ++---------------- html/plotly_template.html | 29 ++- libraries/charting.php | 4 +- 4 files changed, 36 insertions(+), 194 deletions(-) diff --git a/background_scripts/chrome-helper/chrome-helper.js b/background_scripts/chrome-helper/chrome-helper.js index bc224f6175..19b1af178a 100755 --- a/background_scripts/chrome-helper/chrome-helper.js +++ b/background_scripts/chrome-helper/chrome-helper.js @@ -20,7 +20,7 @@ const args = require('yargs').argv; await page.goto('file://' + args['input-file']); - const innerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); + const innerHtml = await page.evaluate(() => document.querySelector('.plotly').innerHTML); console.log(JSON.stringify(innerHtml)); diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 81bfcb58ba..b523c50cba 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -9,78 +9,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { // The default chart config options. _DEFAULT_CONFIG: { - chartPrefix: 'CreateChartPanel', - chartOptions: { - chart: { - type: 'line', - zoomType: 'x' - }, - // Specify Highcharts v.3 default colors for plotting Job Viewer Timeseries data - colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', - '#f28f43', '#77a1e5', '#c42525', '#a6c96a'], - title: { - style: { - color: '#444b6e', - fontSize: '16px' - }, - text: '' - }, - loading: { - style: { - opacity: 0.7 - } - }, - xAxis: { - type: 'datetime', - minTickInterval: 1000, - title: { - style: { - fontWeight: 'bold', - color: '#5078a0' - }, - text: 'Time' - } - }, - yAxis: { - title: { - style: { - fontWeight: 'bold', - color: '#5078a0' - }, - text: 'Units' - }, - min: 0.0 - }, - legend: { - enabled: false - }, - credits: { - text: '', - href: '' - }, - exporting: { - enabled: false - }, - tooltip: { - dateTimeLabelFormats: { - millisecond:"%A, %b %e, %H:%M:%S.%L %T", - second:"%A, %b %e, %H:%M:%S %T", - minute: "%A, %b %e, %H:%M:%S %T", - hour: "%A, %b %e, %H:%M:%S %T" - } - }, - plotOptions: { - line: { - marker: { - enabled: false - } - }, - series: { - allowPointSelect: false, - animation: false - } - } - } + chartPrefix: 'CreateChartPanel', }, // The chart instance. @@ -95,7 +24,6 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { this.loaded = false; - jQuery.extend(true, this.options, this._DEFAULT_CONFIG.chartOptions); XDMoD.Module.JobViewer.ChartPanel.superclass.initComponent.call(this, arguments); // ADD: store listeners ( if we have a store ) @@ -126,14 +54,6 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, // initComponent - setHighchartTimezone: function() { - Highcharts.setOptions({ - global: { - timezone: this.displayTimezone - } - }); - }, - listeners: { /** @@ -141,7 +61,6 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { */ activate: function(tab, reload) { - this.setHighchartTimezone(); reload = reload || false; // This is here so that when the chart is / panel is loaded @@ -176,8 +95,6 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { if (panel.chart) { Plotly.relayout(panel.id, {width: adjWidth, height: adjHeight}); } - this.options.chart.width = adjWidth; - this.options.chart.height = adjHeight; }, // resize destroy: function () { @@ -234,73 +151,10 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { Ext.History.add(token); }; - var chartOptions = this.options; - chartOptions.chart.renderTo = this.id; if (record !== null && record !== undefined) { - chartOptions.series = record.data.series; - chartOptions.yAxis.title.text = record.data.schema.units; - chartOptions.xAxis.title.text = 'Time (' + record.data.schema.timezone + ')'; - chartOptions.credits.text = record.data.schema.source + '. Powered by XDMoD/Plotly'; - chartOptions.title.text = record.data.schema.description; - chartOptions.dataurl = record.store.proxy.url; this.dataurl = record.store.proxy.url; this.displayTimezone = record.data.schema.timezone; - this.setHighchartTimezone(); - - var hasMultipleXValues = false; - var firstXValue = null; - - for ( var i = 0; i < chartOptions.series.length; i++) { - var series = chartOptions.series[i]; - - var header = '{point.key}
'; - if (series.dtype === 'index') { - header = '[ {point.point.options.qtip} ]: {point.key}
'; - } - - series.tooltip = { - headerFormat: header - }; - - series.point = { - events: { - click: chartClickHandler - } - }; - - // If the data series only contains one point, enable - // point markers so that it is visible. - if (series.data.length === 1) { - series.marker = { - enabled: true - }; - } - - // Check for the presence of multiple x-axis values - // (if this fact has not already been established). - if (!hasMultipleXValues) { - for (var j = 0; j < series.data.length; j++) { - var pointXValue = series.data[j].x; - if (firstXValue === null) { - firstXValue = pointXValue; - continue; - } - - if (firstXValue !== pointXValue) { - hasMultipleXValues = true; - break; - } - } - } - } - - // If there are not multiple x-axis values, manually specify - // a minimum range for the x-axis so that it can be rendered. - if (!hasMultipleXValues) { - chartOptions.xAxis.minRange = 2000; - } - if (record.data.schema.help) { panel.helptext.documentation = record.data.schema.help; this.jobTab.fireEvent("display_help", panel.helptext); @@ -310,33 +164,33 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { if (record) { let data = []; var singleDataPoint = false; - var tz = moment.tz.zone(record.data.schema.timezone).abbr(chartOptions.series[0].data[0].x); + var tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); var ymin, ymax; - if (chartOptions.series[0].name === "Range"){ - ymin = chartOptions.series[1].data[0].y; + if (record.data.series[0].name === "Range"){ + ymin = record.data.series[1].data[0].y; ymax = ymin; } else{ - ymin = chartOptions.series[0].data[0].y; + ymin = record.data.series[0].data[0].y; ymax = ymin; } - for (let sid = 0; sid < chartOptions.series.length; sid++) { - if (chartOptions.series[sid].name === "Range") { - tz = moment.tz.zone(record.data.schema.timezone).abbr(chartOptions.series[1].data[0].x); + for (let sid = 0; sid < record.data.series.length; sid++) { + if (record.data.series[sid].name === "Range") { + tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); continue; } let x = []; let y = []; - let colors = chartOptions.colors[sid % 10]; - for(let i=0; i < chartOptions.series[sid].data.length; i++) { - if (chartOptions.series[sid].data.length == 1){ + let colors = record.data.colors[sid % 10]; + for(let i=0; i < record.data.series[sid].data.length; i++) { + if (record.data.series[sid].data.length == 1){ singleDataPoint = true; } - x.push(moment.tz(chartOptions.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); - y.push(chartOptions.series[sid].data[i].y); + x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); + y.push(record.data.series[sid].data[i].y); } - if (chartOptions.series[sid].name === "Median" || chartOptions.series[sid].name === "Minimum"){ + if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ data.push({ x: x, y: y, @@ -352,9 +206,9 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, hovertemplate: "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - "" + chartOptions.series[sid].name + ": %{y}" + + "" + record.data.series[sid].name + ": %{y}" + "", - name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}); + name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}); } else{ var trace = { @@ -370,9 +224,9 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, hovertemplate: "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - "" + chartOptions.series[sid].name + ":%{y}" + + "" + record.data.series[sid].name + ":%{y}" + "", - name: chartOptions.series[sid].name, chartSeries: chartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}; + name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}; if (singleDataPoint){ trace.marker.size = 20; @@ -388,13 +242,6 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } panel.getEl().unmask(); - console.log("data"); - console.log(data); - console.log("chart options data"); - console.log(chartOptions); - console.log("record"); - console.log(record); - console.log("SVG"); let layout = { hoverlabel: { bgcolor: 'white' @@ -442,8 +289,6 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { t: 50 } }; - console.log('layout'); - console.log(layout); if (panel.chart) { Plotly.react(this.id, data, layout, {displayModeBar: false} ); } else { @@ -479,7 +324,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); Ext.History.add(token); }); - if (!singleDataPoint){ + /*if (!singleDataPoint){ panel.chart.on('plotly_hover', function(data){ if (!data.points) return; setTimeout(() => {}, 50); @@ -507,7 +352,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }; Plotly.restyle(panel.chart, update, data.points[0].curveNumber); }); - } + }*/ } } diff --git a/html/plotly_template.html b/html/plotly_template.html index 2ed564a1e2..d7fc7bff09 100644 --- a/html/plotly_template.html +++ b/html/plotly_template.html @@ -2,21 +2,22 @@ - + Plotly Template - - - + + + + + - - -
+ +
- - -
- diff --git a/libraries/charting.php b/libraries/charting.php index 96354f1ba7..f31421ed6b 100644 --- a/libraries/charting.php +++ b/libraries/charting.php @@ -53,11 +53,11 @@ function exportHighchart( $template = str_replace('_width_', $effectiveWidth, $template); $template = str_replace('_height_', $effectiveHeight, $template); - /*$globalChartOptions = array('timezone' => date_default_timezone_get()); + $globalChartOptions = array('timezone' => date_default_timezone_get()); if ($globalChartConfig !== null) { $globalChartOptions = array_merge($globalChartOptions, $globalChartConfig); } - $template = str_replace('_globalChartOptions_', json_encode($globalChartOptions), $template);*/ + $template = str_replace('_globalChartOptions_', json_encode($globalChartOptions), $template); $template = str_replace('_chartOptions_', json_encode($chartConfig), $template); $svg = getSvgViaChromiumHelper($template, $effectiveWidth, $effectiveHeight); From 6ce79dcdc34a1c97e55acdc067912d5f77764cbc Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Fri, 19 May 2023 12:38:46 -0400 Subject: [PATCH 19/47] Fix missing color from previous change. --- html/gui/js/modules/job_viewer/ChartPanel.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index b523c50cba..17356252db 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -162,6 +162,8 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } if (record) { + let colorChoices = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', + '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; let data = []; var singleDataPoint = false; var tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); @@ -181,7 +183,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } let x = []; let y = []; - let colors = record.data.colors[sid % 10]; + let colors = colorChoices[sid % 10]; for(let i=0; i < record.data.series[sid].data.length; i++) { if (record.data.series[sid].data.length == 1){ singleDataPoint = true; From 8134b18e3c39cd5b20cfcf607c6273cd2754dbbd Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 30 May 2023 15:27:07 -0400 Subject: [PATCH 20/47] Added back highchart template for export in other tabs --- html/highchart_template.html | 97 ++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 html/highchart_template.html diff --git a/html/highchart_template.html b/html/highchart_template.html new file mode 100644 index 0000000000..81c16c6471 --- /dev/null +++ b/html/highchart_template.html @@ -0,0 +1,97 @@ + + + + + + Highcharts Template + + + + + + + + + + + + + + + + + + + + + + +
+ + + + From 6447dfece7535eb7401a3a0cad9456122c08172c Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 30 May 2023 15:32:50 -0400 Subject: [PATCH 21/47] Updates to chart tooltip, chart responsiveness, chart object deletion, and envelope and analytic chart colors --- .../chrome-helper/chrome-helper.js | 10 +- .../modules/job_viewer/AnalyticChartPanel.js | 226 +++++++---------- html/gui/js/modules/job_viewer/ChartPanel.js | 227 ++++++++---------- html/gui/js/modules/job_viewer/ChartTab.js | 23 +- html/gui/js/modules/job_viewer/GanttChart.js | 190 +++++++-------- html/gui/js/modules/job_viewer/JobViewer.js | 4 - libraries/charting.php | 14 +- 7 files changed, 307 insertions(+), 387 deletions(-) diff --git a/background_scripts/chrome-helper/chrome-helper.js b/background_scripts/chrome-helper/chrome-helper.js index 19b1af178a..ff304435cf 100755 --- a/background_scripts/chrome-helper/chrome-helper.js +++ b/background_scripts/chrome-helper/chrome-helper.js @@ -20,7 +20,15 @@ const args = require('yargs').argv; await page.goto('file://' + args['input-file']); - const innerHtml = await page.evaluate(() => document.querySelector('.plotly').innerHTML); + const highchartInnerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); + + if (highchartInnerHtml !== null){ + console.log(JSON.stringify(highchartInnerHtml)); + } + else{ + const plotlyInnerHtml = await page.evaluate(() => document.querySelector('#container').innerHTML); + console.log(JSON.stringify(plotlyInnerHtml)); + } console.log(JSON.stringify(innerHtml)); diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 56731c4904..87f0eb4151 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -16,132 +16,85 @@ Ext.namespace('XDMoD', 'XDMoD.Module', 'XDMoD.Module.JobViewer'); XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { _DEFAULT_CONFIG: { delimiter: ':', - chartOptions: { - exporting: { - enabled: false - }, - chart: { - type: 'bar', - height: 65, - options: { - }, - reflow: false - }, - yAxis: { - max: 1, // display all analytics barplots on a plot ranging from 0 to 1 - min: 0, - gridLineColor: '#c0c0c0', - labels: { - enabled: false - }, - title: { - text: '', - margin: 1, // tighten vertical distance between axis title and labels - style: { - fontWeight: 'bold', - color: '#5078a0' - } - } - }, - xAxis: { - tickLength: 0, - labels: { - enabled: false - } - }, - tooltip: { - enabled: false - }, - credits: { - enabled: false - }, - legend: { - enabled: false - }, - plotOptions: { - bar: { - - }, - series: { - animation: false - } - } - }, colorSteps: [ { value: .25, - color: 'rgb(255,0,0)' + color: '#FF0000', + bg_color: 'rgb(255,102,102)' }, { value: .50, - color: 'rgb(255,179,54)' + color: '#FFB336', + bg_color: 'rgb(255,255,156)' + }, { value: .75, - color: 'rgb(221,223,0)' + color: '#DDDF00', + bg_color: 'rgb(255,255,102)' }, { value: 1, - color: 'rgb(80,180,50)' + color: '#50B432', + bg_color: 'rgb(182,255,152)' } ], layout: { - 'hoverlabel': { - 'bgcolor': 'white' + hoverlabel: { + bgcolor: 'white' }, - 'paper_bgcolor': 'white', - 'plot_bgcolor': 'white', - 'height': 60, - 'xaxis': { - 'showticklabels': false, - 'range': [0,1], - 'color': '#606060', - 'ticks': 'inside', - 'tick0': 0.0, - 'dtick': 0.2, - 'ticklen': 2, - 'tickcolor': 'white', - 'gridcolor': '#c0c0c0', - 'linecolor': 'white', - 'zeroline' : false, - 'showgrid': true, - 'zerolinecolor': 'black', - 'showline': false, - 'zerolinewidth': 0, - 'fixedrange': true + paper_bgcolor: 'white', + plot_bgcolor: 'white', + height: 65, + xaxis: { + showticklabels: false, + range: [0,1], + color: '#606060', + ticks: 'inside', + tick0: 0.0, + dtick: 0.2, + ticklen: 2, + tickcolor: 'white', + gridcolor: '#c0c0c0', + linecolor: 'white', + zeroline : false, + showgrid: true, + zerolinecolor: 'black', + showline: false, + zerolinewidth: 0, + fixedrange: true }, - 'yaxis': { - 'showticklabels': false, - 'color': '#606060', - 'showgrid' : false, - 'gridcolor': '#c0c0c0', - 'linecolor': 'white', - 'zeroline': false, - 'zerolinecolor': 'white', - 'showline': false, - 'rangemode': 'tozero', - 'zerolinewidth': 0, - 'fixedrange': true + yaxis: { + showticklabels: false, + color: '#606060', + showgrid : false, + gridcolor: '#c0c0c0', + linecolor: 'white', + zeroline: false, + zerolinecolor: 'white', + showline: false, + rangemode: 'tozero', + zerolinewidth: 0, + fixedrange: true }, - 'hovermode': false, - 'showlegend': false, - 'autosize': true, - 'margin': { - 't': 10, - 'l': 7.5, - 'r': 7.5, - 'b': 10, - 'pad': 0 + hovermode: false, + shapes: [], + showlegend: false, + margin: { + t: 12.5, + l: 7.5, + r: 7.5, + b: 12.5, + pad: 0 } }, - traces: [], - config: { - displayModeBar: false, - }, - + config: { + displayModeBar: false, + }, + bgColors: [] }, // The instance of Highcharts used as this components primary display. @@ -175,10 +128,8 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * a visible element to hook into are handled here. */ render: function() { - - Ext.apply(this.chartOptions, this._DEFAULT_CONFIG.chartOptions); - Plotly.newPlot(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); - + this.chart = true; + Plotly.newPlot(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // render /** @@ -190,7 +141,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ update_data: function(data) { this._updateData(data); - Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + //Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // update_data /** @@ -199,15 +150,14 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ reset: function() { if (this.chart) { - this.traces = []; + this._DEFAULT_CONFIG.traces = []; Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); } }, // reset - destroy: function () { + beforedestroy: function () { if (this.chart) { - this.chart.destroy(); - this.chart = null; + Plotly.purge(this.id); } }, @@ -224,13 +174,19 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (this.chart) { - this._DEFAULT_CONFIG.layout['width'] = adjWidth; - this._DEFAULT_CONFIG.layout['height'] = adjHeight; - Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + Plotly.relayout(this.id, {width: adjWidth}); + var elements = document.querySelectorAll('.bglayer'); + if (elements){ + for (var i=0; i < elements.length; i++){ + if (elements[i].firstChild){ + elements[i].firstChild.style.fill = this._DEFAULT_CONFIG.bgColors[i]; + } + } + } if (this.errorMsg) { - this.updateErrorMessage(this.errorMsg.text.textStr); + this.updateErrorMessage(this.errorMsg.text.textStr); } - } + } } // resize }, // listeners @@ -260,7 +216,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { "y": 1.2 } ] - console.log("new annontation"); this._DEFAULT_CONFIG.layout['annotations'] = [ { "text": '' + errorStr + '', @@ -292,42 +247,36 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _updateData: function(data) { - - var brightFactor = 0.4; - var color, nColor; - this._DEFAULT_CONFIG.traces = []; this._DEFAULT_CONFIG.layout['plot_bgcolor'] = 'white'; - - + var trace = {}; if (data.error == '') { - color = this._getDataColor(data.value); - bg_color = color.substring(0,3) + 'a' + color.substring(3, color.length-1) + ',0.4)'; - this._DEFAULT_CONFIG.layout['plot_bgcolor'] = bg_color; + var chartColor = this._getDataColor(data.value); + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; + this._DEFAULT_CONFIG.bgColors.push(chartColor.bg_color); - this._DEFAULT_CONFIG.traces.push( - { + trace = { x: [data.value], name: data.name ? data.name : '', width: [0.5], marker:{ - color: color, + color: chartColor.color, line:{ color: 'white', - width: 1.5 + width: 1 } }, type: 'bar', - orientation: 'h' - } - ); + orientation: 'h', + }; } + this._DEFAULT_CONFIG.traces.push(trace); this.updateErrorMessage(data.error); this._updateTitle(data); this.ownerCt.doLayout(false, true); - + Plotly.react(this.id, [trace], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // _updateData @@ -350,6 +299,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _getDataColor: function(data) { + var ret = {}; var color = null; if (typeof data === 'number') { var steps = this.colorSteps; @@ -357,7 +307,11 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { for ( var i = 0; i < count; i++) { var step = steps[i]; if (data <= step.value) { - return step.color; + ret = { + color: step.color, + bg_color: step.bg_color + }; + return ret; } } diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 17356252db..7b127135ad 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -15,6 +15,10 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { // The chart instance. chart: null, + chartWidth: null, + + chartHeight: null, + /** * The component 'constructor'. */ @@ -60,8 +64,6 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { * */ activate: function(tab, reload) { - - reload = reload || false; // This is here so that when the chart is / panel is loaded // via one of it's child nodes that it triggers a re-load. @@ -93,17 +95,17 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { */ resize: function(panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (panel.chart) { - Plotly.relayout(panel.id, {width: adjWidth, height: adjHeight}); + this.chartWidth = adjWidth; + this.chartWidth = adjHeight; + Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); } }, // resize - destroy: function () { -/* + beforedestroy: function () { if (this.chart) { - this.chart.destroy(); - this.chart = null; + Plotly.purge(this.id); + this.chart = false; } -*/ }, export_option_selected: function (exportParams) { @@ -162,86 +164,93 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } if (record) { - let colorChoices = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', - '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + let colorChoices = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', + '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; let data = []; - var singleDataPoint = false; - var tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); - var ymin, ymax; - if (record.data.series[0].name === "Range"){ - ymin = record.data.series[1].data[0].y; - ymax = ymin; - } - else{ - ymin = record.data.series[0].data[0].y; - ymax = ymin; - } + var isEnvelope = false; + var hasSingleDataPoint = false; + var tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); + var ymin, ymax; + ymin = record.data.series[0].data[0].y; + ymax = ymin; for (let sid = 0; sid < record.data.series.length; sid++) { - if (record.data.series[sid].name === "Range") { - tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); - continue; - } - let x = []; - let y = []; - let colors = colorChoices[sid % 10]; + if (record.data.series[sid].name === "Range") { + isEnvelope = true; + ymin = record.data.series[1].data[0].y; + ymax = ymin; + tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); + continue; + } + let x = []; + let y = []; + let qtip = []; + let colors = colorChoices[sid % 10]; for(let i=0; i < record.data.series[sid].data.length; i++) { - if (record.data.series[sid].data.length == 1){ - singleDataPoint = true; - } - x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); + if (record.data.series[sid].data.length == 1){ + hasSingleDataPoint = true; + } + x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); y.push(record.data.series[sid].data[i].y); - } + qtip.push(record.data.series[sid].data[i].qtip); + } - if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ - data.push({ - x: x, + if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ + data.push({ + x: x, y: y, - fill: 'tonexty', - fillcolor: '#2f7ed8', - marker: { - size: 0.1, - color: colors - }, - line: { - width: 2, - color: colors - }, - hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - "" + record.data.series[sid].name + ": %{y}" + + fill: 'tonexty', + fillcolor: '#5EA0E2', + marker: { + size: 0.1, + color: colors + }, + line: { + width: 2, + color: colors + }, + text: qtip, + hovertemplate: "[%{text}] " + + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y: .f}" + "", name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}); - } - else{ - var trace = { - x: x, - y: y, - marker: { - size: 0.1, - color: colors - }, - line: { - width: 2, - color: colors - }, - hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - "" + record.data.series[sid].name + ":%{y}" + - "", - name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}; - - if (singleDataPoint){ - trace.marker.size = 20; - trace.mode = 'markers'; - delete trace.line; - } - data.push(trace); - } - var tempyMin = Math.min(...y); - var tempyMax = Math.max(...y); - if (tempyMin < ymin) ymin = tempyMin; - if (tempyMax > ymax) ymax = tempyMax; - } + } + else{ + var trace = { + x: x, + y: y, + marker: { + size: 0.1, + color: colors + }, + line: { + width: 2, + color: colors + }, + text: qtip, + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y: .f}" + + "", + name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}; + + if (isEnvelope){ + trace.hovertemplate = "[%{text}] " + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y: .f}" + + ""; + } + if (hasSingleDataPoint){ + trace.marker.size = 20; + trace.mode = 'markers'; + delete trace.line; + } + data.push(trace); + } + var tempyMin = Math.min(...y); + var tempyMax = Math.max(...y); + if (tempyMin < ymin) ymin = tempyMin; + if (tempyMax > ymax) ymax = tempyMax; + } panel.getEl().unmask(); let layout = { @@ -257,10 +266,10 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, color: '#606060', ticks: 'outside', - ticklen: 10, + ticklen: 10, tickcolor: '#c0cfe0', linecolor: '#c0cfe0', - automargin: true, + automargin: true, showgrid: false }, yaxis: { @@ -271,10 +280,10 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { color: '#5078a0' }, color: '#606060', - range: [0, ymax + (ymax * 0.2)], + range: [0, ymax + (ymax * 0.2)], rangemode: 'nonnegative', - gridcolor: 'lightgray', - automargin: true, + gridcolor: 'lightgray', + automargin: true, linecolor: '#c0cfe0' }, title: { @@ -286,20 +295,21 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, hovermode: 'closest', showlegend: false, - autosize: true, margin: { t: 50 } }; if (panel.chart) { - Plotly.react(this.id, data, layout, {displayModeBar: false} ); + Plotly.react(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'} ); + this.chart = true; } else { - Plotly.newPlot(this.id, data, layout, {displayModeBar: false} ); + Plotly.newPlot(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'} ); + this.chart = true; } - if (!panel.chart) { - panel.chart = document.getElementById(this.id); - panel.chart.on('plotly_click', function(data, event){ + if (this.chart) { + panel.chart = document.getElementById(this.id); + panel.chart.on('plotly_click', function(data, event){ var userOptions = data.points[0].data.chartSeries if (!userOptions || !userOptions.dtype) { return; @@ -325,39 +335,10 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { var path = self.path.concat([drilldown]); var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); Ext.History.add(token); - }); - /*if (!singleDataPoint){ - panel.chart.on('plotly_hover', function(data){ - if (!data.points) return; - setTimeout(() => {}, 50); - console.log(data); - let idx = data.points[0].pointNumber; - var sizes = Array(data.points[0].data.x.length).fill(0.1); - sizes[idx] = 12; - var update = { - 'marker.size': [sizes], - 'line.width': 3 - }; - Plotly.restyle(panel.chart, update, data.points[0].curveNumber); - }); - panel.chart.on('plotly_unhover', function(data){ - if (!data.points) return; - var update = { - line:{ - width: 2, - color: data.points[0].data.line.color - }, - marker:{ - size: 0.1, - color: data.points[0].data.marker.color - } - }; - Plotly.restyle(panel.chart, update, data.points[0].curveNumber); - }); - }*/ + }); + } + } - } - } if (!record) { panel.getEl().mask('Loading...'); } diff --git a/html/gui/js/modules/job_viewer/ChartTab.js b/html/gui/js/modules/job_viewer/ChartTab.js index 88b22df468..46d898d13d 100644 --- a/html/gui/js/modules/job_viewer/ChartTab.js +++ b/html/gui/js/modules/job_viewer/ChartTab.js @@ -99,10 +99,6 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { var chartOptions = jQuery.extend(true, {}, defaultChartSettings, self.chartSettings); - //self.chart = new Highcharts.Chart(chartOptions); - //self.chart.showLoading(); - Plotly.newPlot(this.id, [], {}); - var storeParams; if (self.panelSettings.pageSize) { storeParams = { @@ -177,28 +173,13 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { XDMoD.Module.JobViewer.ChartTab.superclass.initComponent.call(this, arguments); }, - updateTimezone: function (timezone) { - this.displayTimezone = timezone; - this.setHighchartTimezone(); - }, - - setHighchartTimezone: function () { - Highcharts.setOptions({ - global: { - timezone: this.displayTimezone - } - }); - }, - listeners: { activate: function () { - this.setHighchartTimezone(); Ext.History.add(this.historyToken); }, - destroy: function () { + beforedestroy: function () { if (this.chart) { - this.chart.destroy(); - this.chart = null; + Plotly.purge(this.id); } } } diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index 5f43967806..a098ba20f0 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -26,92 +26,89 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, fields: ['series', 'schema', 'categories'] } }; - + XDMoD.Module.JobViewer.GanttChart.superclass.initComponent.call(this, arguments); - + this.addListener('updateChart', function (store) { - if (store.getCount() < 1) { + if (store.getCount() < 1) { return; - } - var record = store.getAt(0); - console.log("record"); - console.log(record); - this.updateTimezone(record.data.schema.timezone); - var i; - var data = []; - var categories = []; - var count = 0; - var rect = []; - var yvals = [0]; - let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - for (i = 0; i < record.data.series.length; i++) { - var j = 0; - for (j = 0; j < record.data.series[i].data.length; j++){ - low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - categories.push(record.data.categories[count]); - var runtime = []; - let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; - var tooltip = [template]; - var ticks = [count]; - var start_time = record.data.series[i].data[j].low; - - // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. - // Points on the scatter plot are created every min to create better coverage for tooltip information - while (start_time < record.data.series[i].data[j].high){ - ticks.push(count); - tooltip.push(template); - runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); - start_time += (60 * 1000); - } - runtime.push(high_data); - - rect.push({ - x0: low_data, - x1: high_data, - y0: count-0.25, - y1: count+0.25, - type: 'rect', - xref: 'x', - yref: 'y', - fillcolor: colors[i % 10], - color: colors[i % 10] - }); + } - - var info = {} + var record = store.getAt(0); + var i; + var data = []; + var categories = []; + var count = 0; + var rect = []; + var yvals = [0]; + let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + for (i = 0; i < record.data.series.length; i++) { + var j = 0; + for (j = 0; j < record.data.series[i].data.length; j++){ + low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + categories.push(record.data.categories[count]); + var runtime = []; + let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; + var tooltip = [template]; + var ticks = [count]; + var start_time = record.data.series[i].data[j].low; + // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. + // Points on the scatter plot are created every min to create better coverage for tooltip information + while (start_time < record.data.series[i].data[j].high){ + ticks.push(count); + tooltip.push(template); + runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); + start_time += (60 * 1000); + } + runtime.push(high_data); + + rect.push({ + x0: low_data, + x1: high_data, + y0: count-0.25, + y1: count+0.25, + type: 'rect', + xref: 'x', + yref: 'y', + fillcolor: colors[i % 10], + //color: colors[i % 10] + }); - if (i > 0){ - info = { - realm: record.data.series[i].data[j].ref.realm, - recordid: store.baseParams.recordid, - jobref: record.data.series[i].data[j].ref.jobid, - infoid: 3 - }; - } - - peer = { - x: runtime, - y: ticks, - type: 'scatter', - - marker:{ - color: 'rgb(155,255,255)' - }, - orientation: 'h', - hovertemplate: record.data.categories[count] + '
'+ - "" + '%{text} ', - text: tooltip, - chartSeries: info - }; + + var info = {} - data.push(peer); - count++; - yvals.push(count); - } - - } - let layout = { + if (i > 0){ + info = { + realm: record.data.series[i].data[j].ref.realm, + recordid: store.baseParams.recordid, + jobref: record.data.series[i].data[j].ref.jobid, + infoid: 3 + }; + } + + peer = { + x: runtime, + y: ticks, + type: 'scatter', + + marker:{ + color: 'rgb(255,255,255)' + }, + orientation: 'h', + hovertemplate: record.data.categories[count] + '
'+ + "" + '%{text} ', + text: tooltip, + chartSeries: info + }; + + data.push(peer); + count++; + yvals.push(count); + } + + } + let layout = { hoverlabel: { bgcolor: 'white' }, @@ -126,15 +123,15 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, ticks: 'outside', tickcolor: '#c0cfe0', linecolor: '#c0cfe0', - type: 'date', - range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] + type: 'date', + range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] }, - yaxis: { - autorange: 'reversed', - ticktext: categories, - zeroline: false, - tickvals: yvals - }, + yaxis: { + autorange: 'reversed', + ticktext: categories, + zeroline: false, + tickvals: yvals + }, title: { text: record.data.schema.description, font: { @@ -145,24 +142,21 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, hovermode: 'closest', showlegend: false, margin: { - l: 175, - b: 150 + l: 175, + b: 150 } }; - layout['shapes'] = rect; - Plotly.newPlot(this.id, data, layout, {displayModeBar: false, responsive: true}); + layout['shapes'] = rect; + Plotly.newPlot(this.id, data, layout, {displayModeBar: false}); + var panel = document.getElementById(this.id); panel.on('plotly_click', function(data){ - var userOptions = data.points[0].data.chartSeries; + var userOptions = data.points[0].data.chartSeries; userOptions['action'] = 'show'; - console.log('useroptions'); - console.log(userOptions); - console.log('url'); - console.log(Ext.urlEncode(userOptions)); - Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); + Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); }); diff --git a/html/gui/js/modules/job_viewer/JobViewer.js b/html/gui/js/modules/job_viewer/JobViewer.js index 745ea850d2..dcad5be084 100644 --- a/html/gui/js/modules/job_viewer/JobViewer.js +++ b/html/gui/js/modules/job_viewer/JobViewer.js @@ -965,10 +965,6 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { export_option_selected: function (exportParams) { var chartPanel = this.getActiveJobSubPanel(); if (chartPanel) { - var test = document.getElementsByClassName("main-svg"); - console.log("SVG"); - console.log(test); - console.log(test[test.length-1]); chartPanel.fireEvent('export_option_selected', exportParams); } }, diff --git a/libraries/charting.php b/libraries/charting.php index f31421ed6b..5a3366fe1a 100644 --- a/libraries/charting.php +++ b/libraries/charting.php @@ -41,16 +41,22 @@ function exportHighchart( $scale, $format, $globalChartConfig = null, - $fileMetadata = null + $fileMetadata = null, + $isPlotly = false ) { $effectiveWidth = (int)($width*$scale); $effectiveHeight = (int)($height*$scale); $html_dir = __DIR__ . "/../html"; - $template = file_get_contents($html_dir . "/plotly_template.html"); + $template; + if ($isPlotly){ + $template = file_get_contents($html_dir . "/plotly_template.html"); + } + else{ + $template = file_get_contents($html_dir . "/highchart_template.html"); + } $template = str_replace('_html_dir_', $html_dir, $template); - $template = str_replace('_width_', $effectiveWidth, $template); $template = str_replace('_height_', $effectiveHeight, $template); $globalChartOptions = array('timezone' => date_default_timezone_get()); @@ -120,7 +126,7 @@ function getSvgViaChromiumHelper($html, $width, $height){ fclose($pipes[2]); $return_value = proc_close($process); - @unlink($tmpHtmlFile); + //@unlink($tmpHtmlFile); $chartSvg = json_decode($out); From cc27014ed0b3e8504fa773379e0f3b6c6bb36d1a Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 13 Jun 2023 23:36:03 -0400 Subject: [PATCH 22/47] Updates to export, printing, code organization for chart logic, and chart layout consistency --- .../chrome-helper/chrome-helper.js | 21 +- .../WarehouseControllerProvider.php | 499 +++++++++--------- html/gui/js/CCR.js | 263 +++++---- html/gui/js/libraries/PlotlyUtilities.js | 156 ++++++ .../modules/job_viewer/AnalyticChartPanel.js | 186 +++---- html/gui/js/modules/job_viewer/ChartPanel.js | 188 ++----- html/gui/js/modules/job_viewer/ChartTab.js | 8 +- html/gui/js/modules/job_viewer/GanttChart.js | 267 +++++----- html/gui/js/modules/job_viewer/JobViewer.js | 243 ++++----- html/plotly_template.html | 180 ++----- libraries/charting.php | 49 +- 11 files changed, 1038 insertions(+), 1022 deletions(-) create mode 100644 html/gui/js/libraries/PlotlyUtilities.js diff --git a/background_scripts/chrome-helper/chrome-helper.js b/background_scripts/chrome-helper/chrome-helper.js index ff304435cf..1c5899e990 100755 --- a/background_scripts/chrome-helper/chrome-helper.js +++ b/background_scripts/chrome-helper/chrome-helper.js @@ -20,17 +20,24 @@ const args = require('yargs').argv; await page.goto('file://' + args['input-file']); - const highchartInnerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); + var svgInnerHtml; - if (highchartInnerHtml !== null){ - console.log(JSON.stringify(highchartInnerHtml)); + if (args['plotly']){ + // Chart traces and axis values svg + var plotlyChart = await page.evaluate(() => document.querySelector('.user-select-none.svg-container').children[0].outerHTML); + // Chart title and axis titles svg + var plotlyLabels = await page.evaluate(() => document.querySelector('.user-select-none.svg-container').children[2].innerHTML); + + plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); + let plotlyImage = plotlyChart + "" + plotlyLabels + ""; + + svgInnerHtml = plotlyImage.replace(/
||<\/b>/gm,""); //HTML tags in titles throw xml error } - else{ - const plotlyInnerHtml = await page.evaluate(() => document.querySelector('#container').innerHTML); - console.log(JSON.stringify(plotlyInnerHtml)); + else{ + svgInnerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); } - console.log(JSON.stringify(innerHtml)); + console.log(JSON.stringify(svgInnerHtml)); await browser.close(); })(); diff --git a/classes/Rest/Controllers/WarehouseControllerProvider.php b/classes/Rest/Controllers/WarehouseControllerProvider.php index 2ab9191d7c..f919d4786a 100644 --- a/classes/Rest/Controllers/WarehouseControllerProvider.php +++ b/classes/Rest/Controllers/WarehouseControllerProvider.php @@ -97,115 +97,115 @@ class WarehouseControllerProvider extends BaseControllerProvider */ private $_supported_types = array( \DataWarehouse\Query\RawQueryTypes::ACCOUNTING => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::ACCOUNTING, - "dtype" => "infoid", - "text" => "Accounting data", - "url" => "/rest/v1.0/warehouse/search/jobs/accounting", - "documentation" => "Shows information about the job that was obtained from the resource manager. - This includes timing information such as the start and end time of the job as - well as administrative information such as the user that submitted the job and - the account that was charged.", - "type" => "keyvaluedata", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::ACCOUNTING, + "dtype" => "infoid", + "text" => "Accounting data", + "url" => "/rest/v1.0/warehouse/search/jobs/accounting", + "documentation" => "Shows information about the job that was obtained from the resource manager. + This includes timing information such as the start and end time of the job as + well as administrative information such as the user that submitted the job and + the account that was charged.", + "type" => "keyvaluedata", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::BATCH_SCRIPT => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::BATCH_SCRIPT, - "dtype" => "infoid", - "text" => "Job script", - "url" => "/rest/v1.0/warehouse/search/jobs/jobscript", - "documentation" => "Shows the job batch script that was passed to the resource manager when the - job was submitted. The script is displayed verbatim.", - "type" => "utf8-text", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::BATCH_SCRIPT, + "dtype" => "infoid", + "text" => "Job script", + "url" => "/rest/v1.0/warehouse/search/jobs/jobscript", + "documentation" => "Shows the job batch script that was passed to the resource manager when the + job was submitted. The script is displayed verbatim.", + "type" => "utf8-text", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::EXECUTABLE => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::EXECUTABLE, - "dtype" => "infoid", - "text" => "Executable information", - "url" => "/rest/v1.0/warehouse/search/jobs/executable", - "documentation" => "Shows information about the processes that were run on the compute nodes during - the job. This information includes the names of the various processes and may - contain information about the linked libraries, loaded modules and process - environment.", - "type" => "nested", - "leaf" => true), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::EXECUTABLE, + "dtype" => "infoid", + "text" => "Executable information", + "url" => "/rest/v1.0/warehouse/search/jobs/executable", + "documentation" => "Shows information about the processes that were run on the compute nodes during + the job. This information includes the names of the various processes and may + contain information about the linked libraries, loaded modules and process + environment.", + "type" => "nested", + "leaf" => true), \DataWarehouse\Query\RawQueryTypes::PEERS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::PEERS, - "dtype" => "infoid", - "text" => "Peers", - 'url' => '/rest/v1.0/warehouse/search/jobs/peers', - 'documentation' => 'Shows the list of other HPC jobs that ran concurrently using the same shared hardware resources.', - 'type' => 'ganttchart', - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::PEERS, + "dtype" => "infoid", + "text" => "Peers", + 'url' => '/rest/v1.0/warehouse/search/jobs/peers', + 'documentation' => 'Shows the list of other HPC jobs that ran concurrently using the same shared hardware resources.', + 'type' => 'ganttchart', + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::NORMALIZED_METRICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::NORMALIZED_METRICS, - "dtype" => "infoid", - "text" => "Summary metrics", - "url" => "/rest/v1.0/warehouse/search/jobs/metrics", - "documentation" => "shows a table with the performance metrics collected during - the job. These are typically average values over the job. The - label for each row has a tooltip that describes the metric. The - data are grouped into the following categories: -
    -
  • CPU Statistics: information about the cores on which the job was - assigned, such as CPU usage, FLOPs, CPI
  • -
  • File I/O Statistics: information about the data read from and - written to block devices and file system mount points.
  • -
  • Memory Statistics: information about the memory usage on the nodes - on which the job ran.
  • -
  • Network I/O Statistics: information about the data transmitted and - received over the network devices.
  • -
- ", - "type" => "metrics", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::NORMALIZED_METRICS, + "dtype" => "infoid", + "text" => "Summary metrics", + "url" => "/rest/v1.0/warehouse/search/jobs/metrics", + "documentation" => "shows a table with the performance metrics collected during + the job. These are typically average values over the job. The + label for each row has a tooltip that describes the metric. The + data are grouped into the following categories: +
    +
  • CPU Statistics: information about the cores on which the job was + assigned, such as CPU usage, FLOPs, CPI
  • +
  • File I/O Statistics: information about the data read from and + written to block devices and file system mount points.
  • +
  • Memory Statistics: information about the memory usage on the nodes + on which the job ran.
  • +
  • Network I/O Statistics: information about the data transmitted and + received over the network devices.
  • +
+", +"type" => "metrics", +"leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::DETAILED_METRICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::DETAILED_METRICS, - "dtype" => "infoid", - "text" => "Detailed metrics", - "url" => "/rest/v1.0/warehouse/search/jobs/detailedmetrics", - "documentation" => "shows the data generated by the job summarization software. Please - consult the relevant job summarization software documentation for details - about these metrics.", - "type" => "detailedmetrics", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::DETAILED_METRICS, + "dtype" => "infoid", + "text" => "Detailed metrics", + "url" => "/rest/v1.0/warehouse/search/jobs/detailedmetrics", + "documentation" => "shows the data generated by the job summarization software. Please + consult the relevant job summarization software documentation for details + about these metrics.", + "type" => "detailedmetrics", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::ANALYTICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::ANALYTICS, - "dtype" => "infoid", - "text" => "Job analytics", - "url" => "/rest/v1.0/warehouse/search/jobs/analytics", - "documentation" => "Click the help icon on each plot to show the description of the analytic", - "type" => "analytics", - "hidden" => true, - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::ANALYTICS, + "dtype" => "infoid", + "text" => "Job analytics", + "url" => "/rest/v1.0/warehouse/search/jobs/analytics", + "documentation" => "Click the help icon on each plot to show the description of the analytic", + "type" => "analytics", + "hidden" => true, + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS, - "dtype" => "infoid", - "text" => "Timeseries", - "leaf" => false - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS, + "dtype" => "infoid", + "text" => "Timeseries", + "leaf" => false + ), \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE, - "dtype" => "infoid", - "text" => "VM State/Events", - "documentation" => "Show the lifecycle of a VM. Green signifies when a VM is active and red signifies when a VM is stopped.", - "url" => "/rest/v1.0/warehouse/search/cloud/vmstate", - "type" => "vmstate", - "leaf" => true - ) + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE, + "dtype" => "infoid", + "text" => "VM State/Events", + "documentation" => "Show the lifecycle of a VM. Green signifies when a VM is active and red signifies when a VM is stopped.", + "url" => "/rest/v1.0/warehouse/search/cloud/vmstate", + "type" => "vmstate", + "leaf" => true + ) ); /** @@ -1025,7 +1025,7 @@ public function getQuickFilters(Request $request, Application $app) )); } - /** + /** * Attempt to retrieve the the name for the provided dimensionId. * * @param Request $request @@ -1042,15 +1042,15 @@ public function getDimensionName(Request $request, Application $app, $dimensionI $status = $success ? 200 : 404; $payload = $success - ? array( - 'success' => $success, - 'results' => array( - 'name' => $dimensionName - )) - : array( - 'success' => false, - 'message' => "Unable to find a name for dimension: $dimensionId" - ); + ? array( + 'success' => $success, + 'results' => array( + 'name' => $dimensionName + )) + : array( + 'success' => false, + 'message' => "Unable to find a name for dimension: $dimensionId" + ); return $app->json( $payload, @@ -1077,16 +1077,16 @@ public function getDimensionValueName(Request $request, Application $app, $dimen $status = $success ? 200 : 404; $payload = $success - ? array( - 'success' => $success, - 'results' => array( - 'name' => $valueName - ) - ) - : array( - 'success' => $success, - 'message' => "Unable to find a name for dimesion: $dimensionId | value: $valueId" - ); + ? array( + 'success' => $success, + 'results' => array( + 'name' => $valueName + ) + ) + : array( + 'success' => $success, + 'message' => "Unable to find a name for dimesion: $dimensionId | value: $valueId" + ); return $app->json( $payload, @@ -1381,44 +1381,44 @@ public function processJobSearch(Request $request, Application $app, XDUser $use public function processJobSearchByAction(Request $request, Application $app, XDUser $user, $action, $realm, $jobId, $actionName) { switch ($action) { - case 'accounting': - case 'jobscript': - case 'analysis': - case 'metrics': - case 'analytics': - $results = $this->getJobData($app, $user, $realm, $jobId, $action, $actionName); - break; - case 'peers': - $start = $this->getIntParam($request, 'start', true); - $limit = $this->getIntParam($request, 'limit', true); - $results = $this->getJobPeers($app, $user, $realm, $jobId, $start, $limit); - break; - case 'executable': - $results = $this->getJobExecutable($app, $user, $realm, $jobId, $action, $actionName); - break; - case 'detailedmetrics': - $results = $this->getJobSummary($app, $user, $realm, $jobId, $action, $actionName); - break; - case 'timeseries': - $tsId = $this->getStringParam($request, 'tsid', true); - $nodeId = $this->getIntParam($request, 'nodeid', false); - $cpuId = $this->getIntParam($request, 'cpuid', false); - - $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, $tsId, $nodeId, $cpuId); - break; - case 'vmstate': - $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, null, null, null); - break; - default: - $results = $app->json( - array( - 'success' => false, - 'action' => $actionName, - 'message' => "Unable to process the requested operation. Unsupported action $action." - ), - 400 - ); - break; + case 'accounting': + case 'jobscript': + case 'analysis': + case 'metrics': + case 'analytics': + $results = $this->getJobData($app, $user, $realm, $jobId, $action, $actionName); + break; + case 'peers': + $start = $this->getIntParam($request, 'start', true); + $limit = $this->getIntParam($request, 'limit', true); + $results = $this->getJobPeers($app, $user, $realm, $jobId, $start, $limit); + break; + case 'executable': + $results = $this->getJobExecutable($app, $user, $realm, $jobId, $action, $actionName); + break; + case 'detailedmetrics': + $results = $this->getJobSummary($app, $user, $realm, $jobId, $action, $actionName); + break; + case 'timeseries': + $tsId = $this->getStringParam($request, 'tsid', true); + $nodeId = $this->getIntParam($request, 'nodeid', false); + $cpuId = $this->getIntParam($request, 'cpuid', false); + + $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, $tsId, $nodeId, $cpuId); + break; + case 'vmstate': + $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, null, null, null); + break; + default: + $results = $app->json( + array( + 'success' => false, + 'action' => $actionName, + 'message' => "Unable to process the requested operation. Unsupported action $action." + ), + 400 + ); + break; } return $results; @@ -1591,7 +1591,7 @@ private function getJobExecutable(Application $app, \XDUser $user, $realm, $jobI private function arraytostore(array $values) { - return array(array("key" => ".", "value" => "", "expanded" => true, "children" => $this->atosrecurse($values, false) )); + return array(array("key" => ".", "value" => "", "expanded" => true, "children" => $this->atosrecurse($values, false) )); } private function atosrecurse(array $values) @@ -1713,34 +1713,34 @@ private function processJobRequest( switch ($infoId) { - case "" . \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE: - $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; - $info = new $infoclass(); - - $result = array(); - foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { - $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/vmstate"; - $tsid['type'] = "timeseries"; - $tsid['dtype'] = "tsid"; - $result[] = $tsid; - } - return $app->json(array('success' => true, "results" => $result)); - break; - case "" . \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS: - $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; - $info = new $infoclass(); - - $result = array(); - foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { - $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/timeseries"; - $tsid['type'] = "timeseries"; - $tsid['dtype'] = "tsid"; - $result[] = $tsid; - } - return $app->json(array('success' => true, "results" => $result)); - break; - default: - throw new BadRequestException("Node is a leaf"); + case "" . \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE: + $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; + $info = new $infoclass(); + + $result = array(); + foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { + $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/vmstate"; + $tsid['type'] = "timeseries"; + $tsid['dtype'] = "tsid"; + $result[] = $tsid; + } + return $app->json(array('success' => true, "results" => $result)); + break; + case "" . \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS: + $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; + $info = new $infoclass(); + + $result = array(); + foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { + $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/timeseries"; + $tsid['type'] = "timeseries"; + $tsid['dtype'] = "tsid"; + $result[] = $tsid; + } + return $app->json(array('success' => true, "results" => $result)); + break; + default: + throw new BadRequestException("Node is a leaf"); } } @@ -1981,34 +1981,49 @@ private function chartImageResponse($data, $type, $settings) $chartConfig = array( 'colors' => array( '#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', - '#f28f43', '#77a1e5', '#c42525', '#a6c96a' + '#f28f43', '#77a1e5', '#c42525', '#a6c96a' ), - 'series' => $data['series'], + 'width' => $settings['width'], + 'height' => $settings['height'], + 'data' => $data, + 'paper_bgcolor' => 'white', + 'plot_bgcolor' => 'white', 'xaxis' => array( 'tickfont' => array( - 'size' => $axisLabelFontSize + 'size' => $axisLabelFontSize ), 'zerolinewidth' => $lineWidth, - 'title' => '' + 'Time (' . $data['schema']['timezone'] . ')' + '', - 'titlefont' => array( - 'size' => $axisTitleFontSize, - 'color' => '#5078a0' - ) + 'title' => 'Time (' . $data['schema']['timezone'] . ')', + 'titlefont' => array( + 'size' => $axisTitleFontSize, + 'color' => '#5078a0' + ), + 'color' => '#606060', + 'ticks' => 'outside', + 'ticklen' => 10, + 'tickcolor' => '#c0cfe0', + 'linecolor' => '#c0cfe0', + 'automargin' => true, + 'showgrid' => false ), 'yaxis' => array( - 'title' => '' + 'Time (' . $data['schema']['units'] . ')' + '', + 'title' => 'Time (' . $data['schema']['units'] . ')', 'titlefont' => array( - 'size' => $axisTitleFontSize, - 'color' => '#5078a0' + 'size' => $axisTitleFontSize, + 'color' => '#5078a0' ), 'zerolinewidth' => $lineWidth, - 'ticfont' => array( - 'size' => $axisLabelFontSize + 'tickfont' => array( + 'size' => $axisLabelFontSize ), - 'rangemode' => 'nonnegative' + 'rangemode' => 'nonnegative', + 'color' => '#606060', + 'gridcolor' => 'lightgray', + 'automargin' => true, + 'linecolor' => '#c0cfe0' ), 'showlegend' => false, - 'plotOptions' => array( + 'plotOptions' => array( 'line' => array( 'lineWidth' => $lineWidth, 'marker' => array( @@ -2016,16 +2031,22 @@ private function chartImageResponse($data, $type, $settings) ) ) ), - 'annotations' => array( + 'annotations' => array(array( 'text' => $data['schema']['source'] . '. Powered by XDMoD/Plotly', - 'xref' => 'paper', - 'yref' => 'paper', - 'x' => 1, - 'xanchor' => 'left', - 'y' => 0, - 'yanchor' => 'top', - 'showarrow' => false - ), + 'font' => array( + 'color' => '#909090', + 'size' => 10, + 'family' => 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' + ), + 'xref' => 'paper', + 'yref' => 'paper', + 'x' => 1, + 'xanchor' => 'right', + 'yanchor' => 'bottom', + 'y' => 0, + 'yshift' => -80, + 'showarrow' => false + )), 'title' => array( 'font' => array( 'color' => '#444b6e', @@ -2036,15 +2057,11 @@ private function chartImageResponse($data, $type, $settings) ) ); - /*if (strpos($data['schema']['units'], '%') !== false) { - $chartConfig['yAxis']['max'] = 100.0; - }*/ - $globalConfig = array( 'timezone' => $data['schema']['timezone'] ); - $chartImage = \xd_charting\exportHighchart($chartConfig, $settings['width'], $settings['height'], $settings['scale'], $type, $globalConfig, $settings['fileMetadata']); + $chartImage = \xd_charting\exportHighchart($chartConfig, $settings['width'], $settings['height'], $settings['scale'], $type, $globalConfig, $settings['fileMetadata'], true); $chartFilename = $settings['fileMetadata']['title'] . '.' . $type; $mimeOverride = $type == 'svg' ? 'image/svg+xml' : null; @@ -2068,30 +2085,30 @@ private function getJobTimeSeriesData(Application $app, Request $request, \XDUse } switch ($format) { - case 'png': - case 'pdf': - case 'svg': - $exportConfig = array( - 'width' => $this->getIntParam($request, 'width', false, 916), - 'height' => $this->getIntParam($request, 'height', false, 484), - 'scale' => floatval($this->getStringParam($request, 'scale', false, '1')), - 'font_size' => $this->getIntParam($request, 'font_size', false, 3), - 'show_title' => $this->getStringParam($request, 'show_title', false, 'y') === 'y' ? true : false, - 'fileMetadata' => array( - 'author' => $user->getFormalName(), - 'subject' => 'Timeseries data for ' . $results['schema']['source'], - 'title' => $results['schema']['description'] - ) - ); - $response = $this->chartImageResponse($results, $format, $exportConfig); - break; - case 'csv': - $response = $this->chartDataResponse($results); - break; - case 'json': - default: - $response = $app->json(array("success" => true, "data" => array($results))); - break; + case 'png': + case 'pdf': + case 'svg': + $exportConfig = array( + 'width' => $this->getIntParam($request, 'width', false, 916), + 'height' => $this->getIntParam($request, 'height', false, 484), + 'scale' => floatval($this->getStringParam($request, 'scale', false, '1')), + 'font_size' => $this->getIntParam($request, 'font_size', false, 3), + 'show_title' => $this->getStringParam($request, 'show_title', false, 'y') === 'y' ? true : false, + 'fileMetadata' => array( + 'author' => $user->getFormalName(), + 'subject' => 'Timeseries data for ' . $results['schema']['source'], + 'title' => $results['schema']['description'] + ) + ); + $response = $this->chartImageResponse($results, $format, $exportConfig); + break; + case 'csv': + $response = $this->chartDataResponse($results); + break; + case 'json': + default: + $response = $app->json(array("success" => true, "data" => array($results))); + break; } return $response; diff --git a/html/gui/js/CCR.js b/html/gui/js/CCR.js index d8888a1eef..531da5456f 100644 --- a/html/gui/js/CCR.js +++ b/html/gui/js/CCR.js @@ -223,18 +223,18 @@ XDMoD.GlobalToolbar.Contact = function () { id: 'global-toolbar-contact-us-send-message', handler: contactHandler }, - { - text: 'Request Feature', - iconCls: 'bulb_16', - id: 'global-toolbar-contact-us-request-feature', - handler: contactHandler - }, - { - text: 'Submit Support Request', - iconCls: 'help_16', - id: 'global-toolbar-contact-us-submit-support-request', - handler: contactHandler - }] + { + text: 'Request Feature', + iconCls: 'bulb_16', + id: 'global-toolbar-contact-us-request-feature', + handler: contactHandler + }, + { + text: 'Submit Support Request', + iconCls: 'help_16', + id: 'global-toolbar-contact-us-submit-support-request', + handler: contactHandler + }] }) //menu }; }; //XDMoD.GlobalToolbar.Contact @@ -254,31 +254,31 @@ XDMoD.createTour = function () { var tourItems = [ { html: 'Welcome to XDMoD! This tour will guide you through some of the features of XDMoD.' + - ((CCR.xdmod.publicUser) ? ' This tour has additional information after you sign in.' : ''), + ((CCR.xdmod.publicUser) ? ' This tour has additional information after you sign in.' : ''), target: '#tg_summary', position: 't-t' }, { html: 'XDMoD provides a wealth of information. Different functionality is provided by individual tabs listed below.' + - ((CCR.xdmod.publicUser) ? ' Some tabs are only visible after you sign in.' : '') + - '
    ' + - '
  • ' + - dashboardDescription + - '
  • ' + - '
  • ' + - 'Usage - A convenient way to browse all available statistics.' + - '
  • ' + - '
  • ' + - 'Metric Explorer - Allows you to create complex charts containing multiple statistics and optionally apply filters.' + - '
  • ' + - '
  • ' + - 'Report Generator - Create reports that may contain multiple charts. Reports may be downloaded directly or scheduled to be emailed periodically.' + - '
  • ' + - '
  • ' + - 'Job Viewer - A detailed view of individual jobs that provides an overall summary of job accounting data, job performance, and a temporal view of a job\'s CPU, network, and disk I/O utilization.' + - '
  • ' + - '
' + - '* Additional modules might provide additional tabs not mentioned here.', + ((CCR.xdmod.publicUser) ? ' Some tabs are only visible after you sign in.' : '') + + '
    ' + + '
  • ' + + dashboardDescription + + '
  • ' + + '
  • ' + + 'Usage - A convenient way to browse all available statistics.' + + '
  • ' + + '
  • ' + + 'Metric Explorer - Allows you to create complex charts containing multiple statistics and optionally apply filters.' + + '
  • ' + + '
  • ' + + 'Report Generator - Create reports that may contain multiple charts. Reports may be downloaded directly or scheduled to be emailed periodically.' + + '
  • ' + + '
  • ' + + 'Job Viewer - A detailed view of individual jobs that provides an overall summary of job accounting data, job performance, and a temporal view of a job\'s CPU, network, and disk I/O utilization.' + + '
  • ' + + '
' + + '* Additional modules might provide additional tabs not mentioned here.', target: '#main_tab_panel .x-tab-panel-header', position: 'tl-bl', maxWidth: 400, @@ -289,19 +289,19 @@ XDMoD.createTour = function () { tourItems.push( { html: 'The Dashboard tab presents individual component and summary charts that provide an overview of information that is available throughout XDMoD as well as the ability to access more detailed information. ' + - 'In order to provide relevant information, XDMoD accounts have an assigned role (set by the XDMoD system administrator), the content of the dashboard is tailored to your role.' + - '
    ' + - '
  • ' + - 'User - Information such as a list of all jobs that you have run as well as other useful information such as queue wait times. ' + - '
  • ' + - '
  • ' + - 'Principal Investigator - Information about all the jobs running under your projects. ' + - '
  • ' + - '
  • ' + - 'Center Staff - Information on all user jobs run at the center as well as information you can use to gauge how well the center is running. ' + - '
  • ' + - '
' + - 'For more information on XDMoD roles, please refer to the XDMoD User Manual available from the Help menu.', + 'In order to provide relevant information, XDMoD accounts have an assigned role (set by the XDMoD system administrator), the content of the dashboard is tailored to your role.' + + '
    ' + + '
  • ' + + 'User - Information such as a list of all jobs that you have run as well as other useful information such as queue wait times. ' + + '
  • ' + + '
  • ' + + 'Principal Investigator - Information about all the jobs running under your projects. ' + + '
  • ' + + '
  • ' + + 'Center Staff - Information on all user jobs run at the center as well as information you can use to gauge how well the center is running. ' + + '
  • ' + + '
' + + 'For more information on XDMoD roles, please refer to the XDMoD User Manual available from the Help menu.', target: '#tg_summary', position: 't-t' } @@ -310,28 +310,28 @@ XDMoD.createTour = function () { tourItems.push( { html: 'Each component provides a toolbar that is customized to provide controls relevant to that component. ' + - 'Hovering over each control with your mouse will display a tool-tip describing what that control does.' + - '
    ' + - '
  • ' + - '"?" - Display additional information about a component' + - '
  • ' + - '
  • ' + - '"*" - Open a chart in the Metric Explorer.' + - '
  • ' + - '
', + 'Hovering over each control with your mouse will display a tool-tip describing what that control does.' + + '
    ' + + '
  • ' + + '"?" - Display additional information about a component' + + '
  • ' + + '
  • ' + + '"*" - Open a chart in the Metric Explorer.' + + '
  • ' + + '
', target: '.x-panel-header:first', position: 'tl-br', offset: [-10, 0] }, { html: 'The Help button provides you with the following options:' + - '
    ' + - '
  • User Manual - A detailed help document for XDMoD. If help is available for the section of XDMoD you currently are visiting, this' + - 'will automatically navigate to the respective section.' + - '
  • ' + - '
  • XDMoD Tour - start this tour again' + - '
  • ' + - '
', + '
    ' + + '
  • User Manual - A detailed help document for XDMoD. If help is available for the section of XDMoD you currently are visiting, this' + + 'will automatically navigate to the respective section.' + + '
  • ' + + '
  • XDMoD Tour - start this tour again' + + '
  • ' + + '
', target: '#help_button', position: 'tr-bl' } @@ -339,7 +339,7 @@ XDMoD.createTour = function () { if (CCR.xdmod.publicUser !== true) { tourItems.push({ html: 'The My Profile button allows you to view and update your account settings. Your role will be displayed in the title bar of the My Profile window.' + - '

>Information you can update includes your Name, Email Address and Password.', + '

>Information you can update includes your Name, Email Address and Password.', target: '#global-toolbar-profile', position: 'tl-bl' }); @@ -751,8 +751,8 @@ CCR.xdmod.ui.login_prompt = null; CCR.xdmod.ui.createUserManualLink = function (tags) { return '
' + - 'For more information, please refer to the User Manual' + - '
'; + 'For more information, please refer to the User Manual' + + ''; }; //CCR.xdmod.ui.createUserManualLink @@ -1319,24 +1319,24 @@ CCR.xdmod.ui.actionLogin = function (config, animateTarget) { id: 'btn_sign_in', handler: signInWithLocalAccount }, - { - xtype: 'container', - autoEl: 'div', - autoWidth: true, - flex: 2, - height: 38, - id: 'assistancePrompt', - items: [{ - xtype: 'tbtext', - html: 'Forgot your password?', - id: 'forgot_password_link' - }, { - xtype: 'tbtext', - html: 'Don\'t have an account?', - id: 'sign_up_link' + xtype: 'container', + autoEl: 'div', + autoWidth: true, + flex: 2, + height: 38, + id: 'assistancePrompt', + items: [{ + xtype: 'tbtext', + html: 'Forgot your password?', + id: 'forgot_password_link' + }, + { + xtype: 'tbtext', + html: 'Don\'t have an account?', + id: 'sign_up_link' + }] }] - }] } ]; @@ -1861,7 +1861,7 @@ CCR.isType = function (value, type) { return Object.prototype.toString.call(value) === type; } else { return Object.prototype.toString.call(value) === - Object.prototype.toString.call(type); + Object.prototype.toString.call(type); } }; @@ -1892,10 +1892,10 @@ CCR.merge = function (obj1, obj2) { CCR.getParameter = function (name, source) { name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), - results = regex.exec(source); + results = regex.exec(source); return results === null - ? "" - : decodeURIComponent(results[1].replace(/\+/g, " ")); + ? "" + : decodeURIComponent(results[1].replace(/\+/g, " ")); }; /* * Process the location hash string. The string should have the form: @@ -1909,8 +1909,8 @@ CCR.getParameter = function (name, source) { */ CCR.tokenize = function (hash) { var raw = (typeof hash !== 'string') - ? String(hash) - : hash; + ? String(hash) + : hash; var matches = raw.match(/^#?(([^:\\?]*):?([^:\\?]*):?([^:\\?]*)\??(.*))/); @@ -2124,13 +2124,13 @@ CCR._encodeObject = function(value, options) { var propertyValue = value[property]; if (CCR.isType(propertyValue, CCR.Types.Array)) { results.push(property + '=' + encodeURIComponent(CCR._encodeArray(propertyValue, { - wrap: true, - seperator: ':' - }))); + wrap: true, + seperator: ':' + }))); } else if (CCR.isType(propertyValue, CCR.Types.Object)) { results.push(property + '=' + encodeURIComponent(CCR._encodeObject(propertyValue, { - wrap: true - }))); + wrap: true + }))); } else { var key = wrap ? '"' + property + '"' : property; results.push(key + separator + propertyValue); @@ -2153,8 +2153,8 @@ CCR.encode = function (values) { var isArray = CCR.isType(values[property], CCR.Types.Array); var isObject = CCR.isType(values[property], CCR.Types.Object); var value = isArray || isObject - ? encodeURIComponent(CCR.deepEncode(values[property])) - : values[property]; + ? encodeURIComponent(CCR.deepEncode(values[property])) + : values[property]; parameters.push(property + '=' + value); } } @@ -2184,7 +2184,7 @@ CCR.apply = function (lhs, rhs) { for (property in rhs) { if (rhs.hasOwnProperty(property)) { var rhsExists = rhs[property] !== undefined - && rhs[property] !== null; + && rhs[property] !== null; if (rhsExists) { results[property] = rhs[property]; } @@ -2210,7 +2210,7 @@ CCR.toInt = function (value) { /** * Displays a MessageBox to the user with the error styling. - * + * * @param {String} title of the Error Dialog Box. * @param {String} message that will be displayed to the user. * @param {Function} success function that will be executed if the user does not @@ -2232,7 +2232,7 @@ CCR.error = function (title, message, success, failure, buttons) { fn: function(buttonId, text, options) { var compare = CCR.compare; if (compare.strings(buttonId, Ext.MessageBox.buttonText['no']) - || compare.strings(buttonId, Ext.MessageBox.buttonText['cancel'])) { + || compare.strings(buttonId, Ext.MessageBox.buttonText['cancel'])) { failure(buttonId, text, options); } else { success(buttonId, text, options); @@ -2306,11 +2306,11 @@ CCR.getInstance = function(instancePath, classPath, config) { * @return {*} the result of walking the provided 'path'. **/ var getReference = function(path, callback) { - callback = callback !== undefined - ? callback - : function(previous, current) { - return previous[current]; - }; + callback = callback !== undefined + ? callback + : function(previous, current) { + return previous[current]; + }; return path.split('.').reduce( callback, window ); }; @@ -2371,6 +2371,51 @@ CCR.intersect = function (left, right) { return found; }; +/** + * Basic word wrap that will insert
tags at specified line break points. + * + * @param {String} str + * @param {Int} lineBreak + * @return {String} + */ +CCR.wordWrap = function (str, lineBreak) { + var retVal = ""; + var count = 0; + var newLineIndex = 0; + var lastSpaceIndex = 0; + let len = str.length; + + if (len < lineBreak){ + return str; + } + + for (var i = 0; i < len; i++){ + if (str[i] === ' '){ + lastSpaceIndex = i; + } + if (count == lineBreak){ + //Non-Alphanumeric character found + if (!(/[a-z]|[0-9]/i.test(str[i]))){ + retVal = retVal.concat("
", str.substr(newLineIndex, lineBreak)); + newLineIndex = i; + } + //Line break landed in the middle of a word. Need to back up. + else{ + retVal = retVal.concat("
", str.substr(newLineIndex, (lastSpaceIndex - newLineIndex))); + newLineIndex = lastSpaceIndex + 1; //Start on next character + i = lastSpaceIndex + 1; + } + //Append rest of str if there remains less characters than specified line break. + if ((len - i) < lineBreak){ + retVal = retVal.concat("
", str.substr(newLineIndex, len)); + break; + } + count = 0; + } + count++; + } + return retVal; +} // override 3.4.0 to be able to restore column state Ext.override(Ext.grid.ColumnModel, { @@ -2470,18 +2515,18 @@ Ext.override(Ext.ToolTip, { // override 3.4.0 to ensure that the grid stops editing if the view is refreshed // actual bug: removing grid lines with active lookup editor didn't hide editor Ext.grid.GridView.prototype.processRows = - Ext.grid.GridView.prototype.processRows.createInterceptor(function () { - if (this.grid) { - this.grid.stopEditing(true); - } - }); + Ext.grid.GridView.prototype.processRows.createInterceptor(function () { + if (this.grid) { + this.grid.stopEditing(true); + } + }); // override 3.4.0 to fix issue with chart labels losing their labelRenderer after hide/show Ext.override(Ext.chart.CartesianChart, { createAxis: function (axis, value) { var o = Ext.apply({}, value), - ref, - old; + ref, + old; if (this[axis]) { old = this[axis].labelFunction; @@ -2530,7 +2575,7 @@ Ext.override(Ext.menu.Item, { '', '{text}', '' - ); + ); } var a = this.getTemplateArgs(); this.el = position ? this.itemTpl.insertBefore(position, a, true) : this.itemTpl.append(container, a, true); diff --git a/html/gui/js/libraries/PlotlyUtilities.js b/html/gui/js/libraries/PlotlyUtilities.js new file mode 100644 index 0000000000..cb7d76e68d --- /dev/null +++ b/html/gui/js/libraries/PlotlyUtilities.js @@ -0,0 +1,156 @@ +/* generateChartOptions - Generates data array and layout dict for Plotly Chart + * ** Currently assumes that data is in format of a record returned in the JobViewer ** + * + * @param{dict} Record containing chart data + * + */ +function generateChartOptions(record){ + let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', + '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + var data = []; + var isEnvelope = false; + var hasSingleDataPoint = false; + var tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); + var ymin, ymax; + ymin = record.data.series[0].data[0].y; + ymax = ymin; + if (record.data.series[0].name === 'Range') { + isEnvelope = true; + ymin = record.data.series[1].data[0].y; + ymax = ymin; + tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); + } + for (let sid = 0; sid < record.data.series.length; sid++) { + var x = []; + var y = []; + var qtip = []; + let color = colors[sid % 10]; + if (record.data.series[sid].data.length == 1){ + hasSingleDataPoint = true; + } + + for(var i=0; i < record.data.series[sid].data.length; i++) { + x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS')); + y.push(record.data.series[sid].data[i].y); + if (isEnvelope){ + qtip.push(record.data.series[sid].data[i].qtip); + } + } + + var trace = { + x: x, + y: y, + marker: { + size: 0.1, + color: color + }, + line: { + width: 2, + color: color + }, + text: qtip, + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ":%{y:.f}" + + "", + name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines' + }; + + if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ + trace['fill'] = 'tonexty'; + trace['fillcolor'] = '#5EA0E2'; + } + + if (isEnvelope){ + trace.hovertemplate = "[%{text}] " + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ":%{y:.f}" + + ""; + } + + if (hasSingleDataPoint){ + trace.marker.size = 20; + trace.mode = 'markers'; + delete trace.line; + } + + data.push(trace); + tempMin = Math.min(...y); + tempMax = Math.max(...y); + if (tempMin < ymin) ymin = tempMin; + if (tempMax > ymax) ymax = tempMax; + } + + let layout = { + hoverlabel: { + bgcolor: '#ffffff' + }, + xaxis: { + title: '' + 'Time (' + record.data.schema.timezone + ')' + '', + titlefont: { + family: 'Open-Sans, verdana, arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + ticks: 'outside', + tickcolor: '#c0cfe0', + linecolor: '#c0cfe0', + automargin: true, + showgrid: false, + }, + yaxis: { + title: '' + record.data.schema.units + '', + titlefont: { + family: 'Open-Sans, verdana, arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + range: [0, ymax + (ymax * 0.2)], + showline: false, + zeroline: false, + gridcolor: '#d8d8d8', + automargin: true, + ticks: 'outside', + tickcolor: '#ffffff', + seperatethousands: true + }, + title: { + text: record.data.schema.description, + font: { + family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif', + color: '#444b6e', + size: 16 + }, + }, + annotations: [{ + text: record.data.schema.source + '. Powered by XDMoD/Plotly', + font:{ + color: '#909090', + size: 10, + family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' + }, + xref: 'paper', + yref: 'paper', + xanchor: 'right', + yanchor: 'bottom', + x: 1, + y: 0, + yshift: -80, + showarrow: false + }], + hovermode: 'closest', + showlegend: false, + margin: { + t: 50 + } + }; + + let ret = { + chartData: data, + chartLayout: layout + }; + + return ret; +} + diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 87f0eb4151..b762bd3ceb 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -20,33 +20,28 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { { value: .25, color: '#FF0000', - bg_color: 'rgb(255,102,102)' + bg_color: 'rgb(255,102,102)' }, { value: .50, color: '#FFB336', - bg_color: 'rgb(255,255,156)' - + bg_color: 'rgb(255,255,156)' + }, { value: .75, color: '#DDDF00', - bg_color: 'rgb(255,255,102)' + bg_color: 'rgb(255,255,102)' }, { value: 1, color: '#50B432', - bg_color: 'rgb(182,255,152)' + bg_color: 'rgb(182,255,152)' } ], - - + + layout: { - hoverlabel: { - bgcolor: 'white' - }, - paper_bgcolor: 'white', - plot_bgcolor: 'white', height: 65, xaxis: { showticklabels: false, @@ -64,7 +59,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { zerolinecolor: 'black', showline: false, zerolinewidth: 0, - fixedrange: true + fixedrange: true }, yaxis: { showticklabels: false, @@ -77,25 +72,26 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { showline: false, rangemode: 'tozero', zerolinewidth: 0, - fixedrange: true + fixedrange: true }, hovermode: false, - shapes: [], + shapes: [], + images: [], + annotations: [], showlegend: false, margin: { - t: 12.5, - l: 7.5, - r: 7.5, - b: 12.5, + t: 10, + l: 9, + r: 13, + b: 10, pad: 0 } }, - traces: [], - config: { - displayModeBar: false, - }, - bgColors: [] + config: { + displayModeBar: false, + staticPlot: true }, + }, // The instance of Highcharts used as this components primary display. chart: null, @@ -109,9 +105,10 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ initComponent: function() { + this.colorSteps = Ext.apply( - this.colorSteps || [], - this._DEFAULT_CONFIG.colorSteps + this.colorSteps || [], + this._DEFAULT_CONFIG.colorSteps ); XDMoD.Module.JobViewer.AnalyticChartPanel.superclass.initComponent.call( @@ -128,8 +125,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * a visible element to hook into are handled here. */ render: function() { - this.chart = true; - Plotly.newPlot(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + this.chart = Plotly.newPlot(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // render /** @@ -141,7 +137,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ update_data: function(data) { this._updateData(data); - //Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // update_data /** @@ -150,14 +145,14 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ reset: function() { if (this.chart) { - this._DEFAULT_CONFIG.traces = []; - Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + Plotly.react(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); } }, // reset beforedestroy: function () { if (this.chart) { - Plotly.purge(this.id); + Plotly.purge(this.id); + this.chart = null; } }, @@ -174,19 +169,13 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (this.chart) { - Plotly.relayout(this.id, {width: adjWidth}); - var elements = document.querySelectorAll('.bglayer'); - if (elements){ - for (var i=0; i < elements.length; i++){ - if (elements[i].firstChild){ - elements[i].firstChild.style.fill = this._DEFAULT_CONFIG.bgColors[i]; - } - } - } - if (this.errorMsg) { - this.updateErrorMessage(this.errorMsg.text.textStr); + let container = document.querySelector('#'+this.id); + let bgcolor = container._fullLayout.plot_bgcolor; + if (bgcolor != 'white'){ + Plotly.relayout(this.id, {width: adjWidth}); + Plotly.relayout(this.id, {plot_bgcolor: bgcolor}); } - } + } } // resize }, // listeners @@ -199,43 +188,45 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ updateErrorMessage: function (errorStr) { if (this.errorMsg) { - this.errorMsg.text.destroy(); - this.errorMsg.image.destroy(); this.errorMsg = null; } if (errorStr) { - this._DEFAULT_CONFIG.layout['images'] = [ + errorStr = CCR.wordWrap(errorStr, 60); + this.errorMsg = errorStr; + let errorImage = [ { - "source": '/gui/images/about_16.png', - "align": "left", - "xref": "paper", - "yref": "paper", - "sizex": 0.4, - "sizey": 0.4, - "x": 0, - "y": 1.2 + source: '/gui/images/about_16.png', + align: "left", + xref: "paper", + yref: "paper", + sizex: 0.4, + sizey: 0.4, + x: 0, + y: 1.2 } - ] - this._DEFAULT_CONFIG.layout['annotations'] = [ + ]; + this._DEFAULT_CONFIG.layout.images = errorImage; + let errorText = [ { - "text": '' + errorStr + '', - "align": "left", - "xref": "paper", - "yref": "paper", - "font":{ - "size": 11, - }, - "x" : 0.05, - "y" : 1.2, - "showarrow": false + text: '' + errorStr + '', + align: "left", + xref: "paper", + yref: "paper", + font:{ + size: 11, + }, + x : 0.05, + y : 1.2, + showarrow: false } - ] - this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = false; + ]; + this._DEFAULT_CONFIG.layout.annotations = errorText; + this._DEFAULT_CONFIG.layout.xaxis.showgrid = false; } else { - this._DEFAULT_CONFIG.layout['images'] = []; - this._DEFAULT_CONFIG.layout['annotations'] = []; - this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = true; + this._DEFAULT_CONFIG.layout.images = []; + this._DEFAULT_CONFIG.layout.annotations = []; + this._DEFAULT_CONFIG.layout.xaxis.showgrid = true; } }, @@ -247,32 +238,29 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _updateData: function(data) { - this._DEFAULT_CONFIG.traces = []; - - this._DEFAULT_CONFIG.layout['plot_bgcolor'] = 'white'; - var trace = {}; + var trace = {}; if (data.error == '') { - var chartColor = this._getDataColor(data.value); - this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; - this._DEFAULT_CONFIG.bgColors.push(chartColor.bg_color); - + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; + trace = { - x: [data.value], - name: data.name ? data.name : '', - width: [0.5], - marker:{ - color: chartColor.color, - line:{ - color: 'white', - width: 1 - } - }, - type: 'bar', - orientation: 'h', - }; + x: [data.value], + name: data.name ? data.name : '', + width: [0.5], + marker:{ + color: chartColor.color, + line:{ + color: 'white', + width: 1 + } + }, + type: 'bar', + orientation: 'h', + }; + } + else { + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = 'white'; } - this._DEFAULT_CONFIG.traces.push(trace); this.updateErrorMessage(data.error); this._updateTitle(data); this.ownerCt.doLayout(false, true); @@ -299,7 +287,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _getDataColor: function(data) { - var ret = {}; + var ret = {}; var color = null; if (typeof data === 'number') { var steps = this.colorSteps; @@ -307,10 +295,10 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { for ( var i = 0; i < count; i++) { var step = steps[i]; if (data <= step.value) { - ret = { - color: step.color, - bg_color: step.bg_color - }; + ret = { + color: step.color, + bg_color: step.bg_color + }; return ret; } } diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 7b127135ad..3f946bda3b 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -9,7 +9,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { // The default chart config options. _DEFAULT_CONFIG: { - chartPrefix: 'CreateChartPanel', + chartPrefix: 'CreateChartPanel', }, // The chart instance. @@ -95,15 +95,15 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { */ resize: function(panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (panel.chart) { - this.chartWidth = adjWidth; - this.chartWidth = adjHeight; + this.chartWidth = adjWidth; + this.chartWidth = adjHeight; Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); } }, // resize beforedestroy: function () { if (this.chart) { - Plotly.purge(this.id); + Plotly.purge(this.id); this.chart = false; } }, @@ -114,7 +114,31 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { print_clicked: function () { if (this.chart) { - this.chart.print(); + var chartDiv = document.querySelector('div[id^=tsid_]'); + chartDiv = chartDiv.firstChild.firstChild; // parent div of the plotly SVGs + + // Make deep copy + var tmpWidth = structuredClone(chartDiv.clientWidth); + var tmpHeight = structuredClone(chartDiv.clientHeight); + + // Resize to 'medium' export width and height -- Currently placeholder width and height + Plotly.relayout(this.id, {width: 916, height: 484}); + + // Combine Plotly svg elements similar to export + var svg; + var plotlyChart = chartDiv.children[0].outerHTML; + var plotlyLabels = chartDiv.children[2].innerHTML; + + plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); + svg = plotlyChart + plotlyLabels + '' + + var printWindow = window.open(); + printWindow.document.write(' Printing '); + printWindow.document.write(svg); + printWindow.print(); + printWindow.close(); + + Plotly.relayout(this.id, {width: tmpWidth, height: tmpHeight}); } }, @@ -164,150 +188,17 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } if (record) { - let colorChoices = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', - '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - let data = []; - var isEnvelope = false; - var hasSingleDataPoint = false; - var tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); - var ymin, ymax; - ymin = record.data.series[0].data[0].y; - ymax = ymin; - for (let sid = 0; sid < record.data.series.length; sid++) { - if (record.data.series[sid].name === "Range") { - isEnvelope = true; - ymin = record.data.series[1].data[0].y; - ymax = ymin; - tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); - continue; - } - let x = []; - let y = []; - let qtip = []; - let colors = colorChoices[sid % 10]; - for(let i=0; i < record.data.series[sid].data.length; i++) { - if (record.data.series[sid].data.length == 1){ - hasSingleDataPoint = true; - } - x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); - y.push(record.data.series[sid].data[i].y); - qtip.push(record.data.series[sid].data[i].qtip); - } - - if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ - data.push({ - x: x, - y: y, - fill: 'tonexty', - fillcolor: '#5EA0E2', - marker: { - size: 0.1, - color: colors - }, - line: { - width: 2, - color: colors - }, - text: qtip, - hovertemplate: "[%{text}] " + - "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ": %{y: .f}" + - "", - name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}); - } - else{ - var trace = { - x: x, - y: y, - marker: { - size: 0.1, - color: colors - }, - line: { - width: 2, - color: colors - }, - text: qtip, - hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ": %{y: .f}" + - "", - name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}; - - if (isEnvelope){ - trace.hovertemplate = "[%{text}] " + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ": %{y: .f}" + - ""; - } - if (hasSingleDataPoint){ - trace.marker.size = 20; - trace.mode = 'markers'; - delete trace.line; - } - data.push(trace); - } - var tempyMin = Math.min(...y); - var tempyMax = Math.max(...y); - if (tempyMin < ymin) ymin = tempyMin; - if (tempyMax > ymax) ymax = tempyMax; - } - + let chartOptions = generateChartOptions(record); panel.getEl().unmask(); - let layout = { - hoverlabel: { - bgcolor: 'white' - }, - xaxis: { - title: 'Time (' + record.data.schema.timezone + ')', - titlefont: { - family: 'Arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - ticks: 'outside', - ticklen: 10, - tickcolor: '#c0cfe0', - linecolor: '#c0cfe0', - automargin: true, - showgrid: false - }, - yaxis: { - title: '' + record.data.schema.units + '', - titlefont: { - family: 'Arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - range: [0, ymax + (ymax * 0.2)], - rangemode: 'nonnegative', - gridcolor: 'lightgray', - automargin: true, - linecolor: '#c0cfe0' - }, - title: { - text: record.data.schema.description, - font: { - color: '#444b6e', - size: 16 - } - }, - hovermode: 'closest', - showlegend: false, - margin: { - t: 50 - } - }; + if (panel.chart) { - Plotly.react(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'} ); + Plotly.react(this.id, chartOptions.chartData, chartOptions.chartLayout, {displayModeBar: false, doubleClick: 'reset'} ); this.chart = true; } else { - Plotly.newPlot(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'} ); + Plotly.newPlot(this.id, chartOptions.chartData, chartOptions.chartLayout, {displayModeBar: false, doubleClick: 'reset'} ); this.chart = true; } - - if (this.chart) { + if (panel.chart) { panel.chart = document.getElementById(this.id); panel.chart.on('plotly_click', function(data, event){ var userOptions = data.points[0].data.chartSeries @@ -320,13 +211,14 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { * plots and for the series for simple plots. */ if (userOptions.dtype == 'index') { - var nodeidIndex = data.points[0].data.chartSeries.data.findIndex(({y}) => y === data.points[0].y); + var nodeidIndex = data.points[0].pointIndex; if (nodeidIndex === -1) return; drilldown = { dtype: userOptions.index, value: userOptions.data[nodeidIndex].nodeid }; - } else { + } + else { drilldown = { dtype: userOptions.dtype, value: userOptions[userOptions.dtype] @@ -335,9 +227,9 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { var path = self.path.concat([drilldown]); var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); Ext.History.add(token); - }); - } - } + }); + } + } if (!record) { panel.getEl().mask('Loading...'); diff --git a/html/gui/js/modules/job_viewer/ChartTab.js b/html/gui/js/modules/job_viewer/ChartTab.js index 46d898d13d..c6798bb866 100644 --- a/html/gui/js/modules/job_viewer/ChartTab.js +++ b/html/gui/js/modules/job_viewer/ChartTab.js @@ -148,10 +148,8 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { xtype: 'container', id: this.id + '_hc', listeners: { - resize: function () { - if (self.chart) { - self.chart.reflow(); - } + resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { + self.fireEvent('resize', panel, adjWidth, adjHeight, rawWidth, rawHeight); }, render: createChart } @@ -181,6 +179,6 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { if (this.chart) { Plotly.purge(this.id); } - } + }, } }); diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index a098ba20f0..a4ba6b630a 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -18,6 +18,8 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, } }; + this.chart = null; + this.panelSettings = { pageSize: 11, url: this.url, @@ -26,140 +28,163 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, fields: ['series', 'schema', 'categories'] } }; - + XDMoD.Module.JobViewer.GanttChart.superclass.initComponent.call(this, arguments); - + + this.addListener('resize', function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { + if (this.chart) { + this.chartWidth = adjWidth; + this.chartWidth = adjHeight; + Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); + } + }); + this.addListener('updateChart', function (store) { - if (store.getCount() < 1) { + if (store.getCount() < 1) { return; - } - - var record = store.getAt(0); - var i; - var data = []; - var categories = []; - var count = 0; - var rect = []; - var yvals = [0]; - let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - for (i = 0; i < record.data.series.length; i++) { - var j = 0; - for (j = 0; j < record.data.series[i].data.length; j++){ - low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - categories.push(record.data.categories[count]); - var runtime = []; - let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; - var tooltip = [template]; - var ticks = [count]; - var start_time = record.data.series[i].data[j].low; - // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. - // Points on the scatter plot are created every min to create better coverage for tooltip information - while (start_time < record.data.series[i].data[j].high){ - ticks.push(count); - tooltip.push(template); - runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); - start_time += (60 * 1000); - } - runtime.push(high_data); - - rect.push({ - x0: low_data, - x1: high_data, - y0: count-0.25, - y1: count+0.25, - type: 'rect', - xref: 'x', - yref: 'y', - fillcolor: colors[i % 10], - //color: colors[i % 10] - }); - - - var info = {} - - if (i > 0){ - info = { - realm: record.data.series[i].data[j].ref.realm, - recordid: store.baseParams.recordid, - jobref: record.data.series[i].data[j].ref.jobid, - infoid: 3 - }; - } - - peer = { - x: runtime, - y: ticks, + } + + var record = store.getAt(0); + var i; + var data = []; + var categories = []; + var count = 0; + var rect = []; + var yvals = []; + let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + for (i = 0; i < record.data.series.length; i++) { + var j = 0; + for (j = 0; j < record.data.series[i].data.length; j++){ + low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + categories.push(record.data.categories[count]); + var runtime = []; + let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; + var tooltip = [template]; + var ticks = [count]; + var start_time = record.data.series[i].data[j].low; + // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. + // Points on the scatter plot are created every min to create better coverage for tooltip information + while (start_time < record.data.series[i].data[j].high){ + ticks.push(count); + tooltip.push(template); + runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); + start_time += (60 * 1000); + } + runtime.push(high_data); + + rect.push({ + x0: low_data, + x1: high_data, + y0: count-0.175, + y1: count+0.175, + type: 'rect', + xref: 'x', + yref: 'y', + fillcolor: colors[i % 10], + }); + + var info = {} + if (i > 0){ + info = { + realm: record.data.series[i].data[j].ref.realm, + recordid: store.baseParams.recordid, + jobref: record.data.series[i].data[j].ref.jobid, + infoid: 3 + }; + } + peer = { + x: runtime, + y: ticks, type: 'scatter', - - marker:{ - color: 'rgb(255,255,255)' - }, - orientation: 'h', - hovertemplate: record.data.categories[count] + '
'+ - "" + '%{text} ', - text: tooltip, - chartSeries: info + marker:{ + color: 'rgb(255,255,255)', + size: 20 + }, + orientation: 'h', + hovertemplate: record.data.categories[count] + '
'+ + "" + '%{text} ', + text: tooltip, + chartSeries: info }; - data.push(peer); - count++; - yvals.push(count); + data.push(peer); + yvals.push(count); + count++; + } + } - - } - let layout = { - hoverlabel: { - bgcolor: 'white' - }, - xaxis: { - title: 'Time (' + record.data.schema.timezone + ')', - titlefont: { - family: 'Arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - ticks: 'outside', - tickcolor: '#c0cfe0', - linecolor: '#c0cfe0', - type: 'date', - range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] + let layout = { + hoverlabel: { + bgcolor: 'white' + }, + xaxis: { + title: 'Time (' + record.data.schema.timezone + ')', + titlefont: { + family: 'Arial, sans-serif', + size: 12, + color: '#5078a0' }, - yaxis: { - autorange: 'reversed', - ticktext: categories, - zeroline: false, - tickvals: yvals - }, - title: { - text: record.data.schema.description, - font: { - color: '#444b6e', - size: 16 - } + color: '#606060', + zeroline: false, + gridcolor: '#d8d8d8', + //showgrid: false, + type: 'date', + range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] + }, + yaxis: { + autorange: 'reversed', + ticktext: categories, + zeroline: false, + showgrid: false, + showline: true, + linecolor: '#c0cfe0', + ticks: 'outside', + tickcolor: '#c0cfe0', + tickvals: yvals + }, + title: { + text: record.data.schema.description, + font: { + color: '#444b6e', + size: 16 + } + }, + hovermode: 'closest', + annotations: [{ + text: 'Powered by XDMoD/Plotly', + font:{ + color: '#909090', + size: 10, + family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' }, - hovermode: 'closest', - showlegend: false, - margin: { - l: 175, - b: 150 + xref: 'paper', + yref: 'paper', + xanchor: 'right', + yanchor: 'bottom', + x: 1, + y: 0, + yshift: -80, + showarrow: false - } - }; + }], + showlegend: false, + margin: { + t: 50, + l: 180, + } + }; - layout['shapes'] = rect; - Plotly.newPlot(this.id, data, layout, {displayModeBar: false}); + layout['shapes'] = rect; + this.chart = Plotly.newPlot(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'}); - - var panel = document.getElementById(this.id); - panel.on('plotly_click', function(data){ - var userOptions = data.points[0].data.chartSeries; - userOptions['action'] = 'show'; - Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); + var panel = document.getElementById(this.id); + panel.on('plotly_click', function(data){ + var userOptions = data.points[0].data.chartSeries; + userOptions['action'] = 'show'; + Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); + }); - }); - }); } }); diff --git a/html/gui/js/modules/job_viewer/JobViewer.js b/html/gui/js/modules/job_viewer/JobViewer.js index dcad5be084..af34cd9b54 100644 --- a/html/gui/js/modules/job_viewer/JobViewer.js +++ b/html/gui/js/modules/job_viewer/JobViewer.js @@ -129,12 +129,12 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { this.on('duration_change', this.noOpt); this.addEvents( - 'record_loaded', - 'data_account_loaded', - 'data_acct_loaded', - 'data_mdata_loaded', - 'data_jobrecord_loaded', - 'data_store_loaded' + 'record_loaded', + 'data_account_loaded', + 'data_acct_loaded', + 'data_mdata_loaded', + 'data_jobrecord_loaded', + 'data_store_loaded' ); // SETUP: the components for this tab and add them to this tabs 'items' @@ -182,7 +182,7 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { for (property in rhs) { if (rhs.hasOwnProperty(property)) { var rhsExists = rhs[property] !== undefined - && rhs[property] !== null; + && rhs[property] !== null; if (rhsExists) { results[property] = rhs[property]; } @@ -270,19 +270,19 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { }); self.treeSorter = new Ext.tree.TreeSorter(self.searchHistoryPanel, { - folderSort: false, - dir: "asc", - sortType: function(value, node) { - if (node.attributes.dtype == 'recordid') { - if (self.sortMode == 'age') { - return 9007199254740991 - parseInt(node.attributes.recordid, 10); - } else { - return node.attributes.text; - } + folderSort: false, + dir: "asc", + sortType: function(value, node) { + if (node.attributes.dtype == 'recordid') { + if (self.sortMode == 'age') { + return 9007199254740991 - parseInt(node.attributes.recordid, 10); } else { - return node.attributes[node.attributes.dtype]; + return node.attributes.text; } + } else { + return node.attributes[node.attributes.dtype]; } + } }); // NAVIGATION ( PARENT WESTERN PANEL ) ================================= @@ -376,8 +376,8 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { // RETURN: an array of the top level components. To be used as the // 'items' for another component with a border layout. return new Array( - searchHistory,// WEST - viewPanel // CENTER + searchHistory,// WEST + viewPanel // CENTER ); }, // setupComponents @@ -456,10 +456,10 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { getParameterByName: function (name, source) { name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), - results = regex.exec(source); + results = regex.exec(source); return results === null - ? "" - : decodeURIComponent(results[1].replace(/\+/g, " ")); + ? "" + : decodeURIComponent(results[1].replace(/\+/g, " ")); }, // getParameterByName /** @@ -821,21 +821,21 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { break; case 'vmstate': tab = new XDMoD.Module.JobViewer.VMStateChartPanel({ - id: chartId, - title: title, - url: base, - baseParams: this._getParams(path), - historyToken: '#' + this.module_id + '?' + this._createHistoryTokenFromArray(path), - path: path, - dtypes: [], - dtype: dtype, - dtypeValue: id, - store: new Ext.data.JsonStore({ - proxy: new Ext.data.HttpProxy({ url: url }), - autoLoad: true, - root: 'data', - fields: ['series', 'schema'] - }) + id: chartId, + title: title, + url: base, + baseParams: this._getParams(path), + historyToken: '#' + this.module_id + '?' + this._createHistoryTokenFromArray(path), + path: path, + dtypes: [], + dtype: dtype, + dtypeValue: id, + store: new Ext.data.JsonStore({ + proxy: new Ext.data.HttpProxy({ url: url }), + autoLoad: true, + root: 'data', + fields: ['series', 'schema'] + }) }); break; case 'analytics': @@ -973,6 +973,7 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { var chartPanel = this.getActiveJobSubPanel(); if (chartPanel) { chartPanel.fireEvent('print_clicked'); + chartPanel.resize(); } }, @@ -1138,8 +1139,8 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { if (exists(jobTab)) { var currentlyActive = tabs.activeTab && tabs.activeTab.jobId - ? tabs.activeTab.jobId === jobTab.jobId - : false; + ? tabs.activeTab.jobId === jobTab.jobId + : false; if (!currentlyActive) { jobTab.revert = false; @@ -1214,36 +1215,27 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { var exists = CCR.exists; if (isType(realm, CCR.Types.String)) { Ext.MessageBox.confirm('Delete All Saved Searches?', 'Do you want to delete all saved searches for the realm: ' + realm + ' ?', - function (btn) { - if (btn === 'ok' || btn === 'yes') { - Ext.Ajax.request({ - /*'/rest/datawarehouse/search/history?realm=' + realm + '&token=' + XDMoD.REST.token,*/ - url: XDMoD.REST.url + '/' + self.rest.warehouse + '/search/history?realm=' + realm + '&token=' + XDMoD.REST.token, - method: 'DELETE', - success: function (response) { - var data = JSON.parse(response.responseText); - var success = exists(data) && exists(data.success) && data.success; - if (success) { - self.fireEvent('clear_display'); - var current = Ext.History.getToken(); - if (isType(current, CCR.Types.String)) { - var currentToken = CCR.tokenize(current); - var token = currentToken.tab + '?realm=' + realm; - Ext.History.add(token); - } else if (isType(current, CCR.Types.Object)) { - var token = current.tab + '?realm=' + realm; - Ext.History.add(token); - } - } else { - Ext.MessageBox.show({ - title: 'Deletion Error', - msg: 'There was an error removing all searches for the realm: [' + realm + '].', - icon: Ext.MessageBox.ERROR, - buttons: Ext.MessageBox.OK - }); + function (btn) { + if (btn === 'ok' || btn === 'yes') { + Ext.Ajax.request({ + /*'/rest/datawarehouse/search/history?realm=' + realm + '&token=' + XDMoD.REST.token,*/ + url: XDMoD.REST.url + '/' + self.rest.warehouse + '/search/history?realm=' + realm + '&token=' + XDMoD.REST.token, + method: 'DELETE', + success: function (response) { + var data = JSON.parse(response.responseText); + var success = exists(data) && exists(data.success) && data.success; + if (success) { + self.fireEvent('clear_display'); + var current = Ext.History.getToken(); + if (isType(current, CCR.Types.String)) { + var currentToken = CCR.tokenize(current); + var token = currentToken.tab + '?realm=' + realm; + Ext.History.add(token); + } else if (isType(current, CCR.Types.Object)) { + var token = current.tab + '?realm=' + realm; + Ext.History.add(token); } - }, - failure: function (response) { + } else { Ext.MessageBox.show({ title: 'Deletion Error', msg: 'There was an error removing all searches for the realm: [' + realm + '].', @@ -1251,11 +1243,20 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { buttons: Ext.MessageBox.OK }); } - }); - } else { - Ext.MessageBox.alert('Ok', 'All your searches are belong to you.'); - } - }); + }, + failure: function (response) { + Ext.MessageBox.show({ + title: 'Deletion Error', + msg: 'There was an error removing all searches for the realm: [' + realm + '].', + icon: Ext.MessageBox.ERROR, + buttons: Ext.MessageBox.OK + }); + } + }); + } else { + Ext.MessageBox.alert('Ok', 'All your searches are belong to you.'); + } + }); } }, // search_delete_by_realm @@ -1272,44 +1273,35 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { if (isType(node, CCR.Types.Object)) { var title = node.text || node.attributes ? node.attributes.text : undefined; Ext.MessageBox.confirm('Delete All Saved Searches?', 'Do you want to delete the search: ' + title + ' ?', - function (text) { - if (text === 'ok' || text === 'yes') { - var recordId = node.attributes.recordid !== undefined ? node.attributes.recordid : null; - var fragment = recordId !== null ? '/search/history/' + recordId : '/search/history'; - var path = self._getPath(node); - /*'/rest/datawarehouse/search/history'*/ - var url = self._generateURL(XDMoD.REST.url + '/' + self.rest.warehouse + fragment, path); - Ext.Ajax.request({ - url: url, - method: 'DELETE', - success: function (response) { - var data = JSON.parse(response.responseText); - var success = exists(data) && exists(data.success) && data.success; - if (success) { - self.fireEvent('clear_display'); - var current = Ext.History.getToken(); - var path = self._getPath(node); - if (path && path.length && path.length > 0) delete path[path.length - 1]; - var params = self._getParams(path); - var encoded = encode(params); - if (isType(current, CCR.Types.String)) { - var currentToken = CCR.tokenize(current); - var token = currentToken.tab + '?' + encoded; - Ext.History.add(token); - } else if (isType(current, CCR.Types.Object)) { - var token = current.tab + '?' + encoded; - Ext.History.add(token); - } - } else { - Ext.MessageBox.show({ - title: 'Deletion Error', - msg: 'There was an error removing search: [' + title + '].', - icon: Ext.MessageBox.ERROR, - buttons: Ext.MessageBox.OK - }); + function (text) { + if (text === 'ok' || text === 'yes') { + var recordId = node.attributes.recordid !== undefined ? node.attributes.recordid : null; + var fragment = recordId !== null ? '/search/history/' + recordId : '/search/history'; + var path = self._getPath(node); + /*'/rest/datawarehouse/search/history'*/ + var url = self._generateURL(XDMoD.REST.url + '/' + self.rest.warehouse + fragment, path); + Ext.Ajax.request({ + url: url, + method: 'DELETE', + success: function (response) { + var data = JSON.parse(response.responseText); + var success = exists(data) && exists(data.success) && data.success; + if (success) { + self.fireEvent('clear_display'); + var current = Ext.History.getToken(); + var path = self._getPath(node); + if (path && path.length && path.length > 0) delete path[path.length - 1]; + var params = self._getParams(path); + var encoded = encode(params); + if (isType(current, CCR.Types.String)) { + var currentToken = CCR.tokenize(current); + var token = currentToken.tab + '?' + encoded; + Ext.History.add(token); + } else if (isType(current, CCR.Types.Object)) { + var token = current.tab + '?' + encoded; + Ext.History.add(token); } - }, - failure: function (response) { + } else { Ext.MessageBox.show({ title: 'Deletion Error', msg: 'There was an error removing search: [' + title + '].', @@ -1317,9 +1309,18 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { buttons: Ext.MessageBox.OK }); } - }) - } + }, + failure: function (response) { + Ext.MessageBox.show({ + title: 'Deletion Error', + msg: 'There was an error removing search: [' + title + '].', + icon: Ext.MessageBox.ERROR, + buttons: Ext.MessageBox.OK + }); + } + }) } + } ); } @@ -1742,8 +1743,8 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { if (exists(jobTab)) { var currentlyActive = tabs.activeTab && tabs.activeTab.jobId - ? tabs.activeTab.jobId === jobTab.jobId - : false; + ? tabs.activeTab.jobId === jobTab.jobId + : false; if (!currentlyActive) { tabs.setActiveTab(jobTab); @@ -1861,8 +1862,8 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { var data = results.data; var recordId = data.recordid; var jobs = data.results || [ - jobData - ]; + jobData + ]; var jobFound = false; for (var i = 0; i < jobs.length; i++) { var job = jobs[i]; @@ -1939,11 +1940,11 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { searchTerms = searchTerms || {}; var params = { 'data': JSON.stringify( - { - "text": title, - "searchterms": searchTerms, - "results": jobs - }) + { + "text": title, + "searchterms": searchTerms, + "results": jobs + }) }; if (CCR.exists(id)) params['recordid'] = id; diff --git a/html/plotly_template.html b/html/plotly_template.html index d7fc7bff09..487d129d15 100644 --- a/html/plotly_template.html +++ b/html/plotly_template.html @@ -1,163 +1,45 @@ - + - - Plotly Template + + Plotly Template - - - + + + + + + - + - - - + - -
+ +
+ - let data = []; - var tz = moment.tz.zone(globalChartOptions.timezone).abbr(inputChartOptions.series[0].data[0].x); - var ymin, ymax; - if (inputChartOptions.series[0].name === "Range"){ - ymin = inputChartOptions.series[1].data[0].y; - ymax = ymin; - } - else{ - ymin = inputChartOptions.series[0].data[0].y; - ymax = ymin; - } - for (let sid = 0; sid < inputChartOptions.series.length; sid++) { - if (inputChartOptions.series[sid].name === "Range") { - tz = moment.tz.zone(globalChartOptions.timezone).abbr(inputChartOptions.series[1].data[0].x); - continue; - } - let x = []; - let y = []; - let colors = inputChartOptions.colors[sid % 10]; - for(let i=0; i < inputChartOptions.series[sid].data.length; i++) { - x.push(moment.tz(inputChartOptions.series[sid].data[i].x, globalChartOptions.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); - y.push(inputChartOptions.series[sid].data[i].y); - } - - if (inputChartOptions.series[sid].name === "Median" || inputChartOptions.series[sid].name === "Minimum"){ - data.push({ - x: x, - y: y, - fill: 'tonexty', - fillcolor: '#2f7ed8', - marker: { - size: 0, - color: colors - }, - line: { - width: 2, - color: colors - }, - hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - "" + inputChartOptions.series[sid].name + ": %{y}" + - "", - name: inputChartOptions.series[sid].name, chartSeries: inputChartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}); - } - else{ - data.push({ - x: x, - y: y, - marker: { - size: 0, - color: colors - }, - line: { - width: 2, - color: colors - }, - hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - "" + inputChartOptions.series[sid].name + ":%{y}" + - "", - name: inputChartOptions.series[sid].name, chartSeries: inputChartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}); - } - var tempyMin = Math.min(...y); - var tempyMax = Math.max(...y); - if (tempyMin < ymin) ymin = tempyMin; - if (tempyMax > ymax) ymax = tempyMax; - } - inputChartOptions.yaxis.range = [ymin, ymax]; - inputChartOptions.width = _width_; - inputChartOptions.height = _heigh_; - - - - /*var chartOptions = { - - chart: { - renderTo: 'container', - animation: false - }, - - plotOptions: { - series: { - animation: false, - turboThreshold: 0 - } - }, - - exporting: { - width: _width_, - height: _height_, - sourceWidth: _width_, - sourceHeight: _height_ - } - - };//chartOptions - - jQuery.extend(true,inputChartOptions,chartOptions); - - // Needed to provide comma as thousands separator in export - Highcharts.setOptions ({ - lang: { - thousandsSep: '\u002c' // the humble comma - } - }); - - var globalChartOptions = _globalChartOptions_; - if (globalChartOptions) { - Highcharts.setOptions({ - global: globalChartOptions - }); - }*/ - - if (inputChartOptions.series.length == 0) { - inputChartOptions.title.text = 'No data available for the critera specified'; - inputChartOptions.title.yref = 'paper'; - inputChartOptions.title.y = 0.5; - inputChartOptions.title.yanchor = 'bottom'; - inputChartOptions.title.font = { "color": "#333333", "size": "40px" } - } - Plotly.newPlot('container', data, inputChartOptions, {staticPlot: true} ); - - });//$(document).ready(... - - - + diff --git a/libraries/charting.php b/libraries/charting.php index 5a3366fe1a..3e4acd93cf 100644 --- a/libraries/charting.php +++ b/libraries/charting.php @@ -50,10 +50,10 @@ function exportHighchart( $html_dir = __DIR__ . "/../html"; $template; if ($isPlotly){ - $template = file_get_contents($html_dir . "/plotly_template.html"); + $template = file_get_contents($html_dir . "/plotly_template.html"); } else{ - $template = file_get_contents($html_dir . "/highchart_template.html"); + $template = file_get_contents($html_dir . "/highchart_template.html"); } $template = str_replace('_html_dir_', $html_dir, $template); @@ -66,16 +66,17 @@ function exportHighchart( $template = str_replace('_globalChartOptions_', json_encode($globalChartOptions), $template); $template = str_replace('_chartOptions_', json_encode($chartConfig), $template); - $svg = getSvgViaChromiumHelper($template, $effectiveWidth, $effectiveHeight); + + $svg = getSvgViaChromiumHelper($template, $effectiveWidth, $effectiveHeight, $isPlotly); switch($format){ - case 'png': - return convertSvg($svg, 'png', $effectiveWidth, $effectiveHeight, $fileMetadata); - break; - case 'pdf': - return convertSvg($svg, 'pdf', round($width / 90.0 * 72.0), round($height / 90.0 * 72.0), $fileMetadata); - break; - default: - return $svg; + case 'png': + return convertSvg($svg, 'png', $effectiveWidth, $effectiveHeight, $fileMetadata); + break; + case 'pdf': + return convertSvg($svg, 'pdf', round($width / 90.0 * 72.0), round($height / 90.0 * 72.0), $fileMetadata); + break; + default: + return $svg; } } @@ -90,7 +91,7 @@ function exportHighchart( * * @throws \Exception on invalid format, command execution failure, or non zero exit status */ -function getSvgViaChromiumHelper($html, $width, $height){ +function getSvgViaChromiumHelper($html, $width, $height, $isPlotly){ // Chromium requires the file to have a .html extension // cant use datauri as it will not execute embdeeded javascript @@ -107,7 +108,8 @@ function getSvgViaChromiumHelper($html, $width, $height){ $command = LIB_DIR . '/chrome-helper/chrome-helper.js' . ' --window-size=' . $width . ',' . $height . ' --path-to-chrome=' . $chromiumPath . - ' --input-file=' . $tmpHtmlFile; + ' --input-file=' . $tmpHtmlFile . + ' --plotly=' . $isPlotly; $pipes = array(); $descriptor_spec = array( @@ -126,7 +128,7 @@ function getSvgViaChromiumHelper($html, $width, $height){ fclose($pipes[2]); $return_value = proc_close($process); - //@unlink($tmpHtmlFile); + @unlink($tmpHtmlFile); $chartSvg = json_decode($out); @@ -156,17 +158,20 @@ function convertSvg($svgData, $format, $width, $height, $docmeta){ $creator = addcslashes('XDMoD ' . OPEN_XDMOD_VERSION, "()\n\\"); switch($format){ - case 'png': - $exifArgs = "-Title='$title' -Author='$author' -Description='$subject' -Source='$creator'"; - break; - case 'pdf': - $exifArgs = "-Title='$title' -Author='$author' -Subject='$subject' -Creator='$creator'"; - break; - default: - return $svgData; + case 'png': + $exifArgs = "-Title='$title' -Author='$author' -Description='$subject' -Source='$creator'"; + break; + case 'pdf': + $exifArgs = "-Title='$title' -Author='$author' -Subject='$subject' -Creator='$creator'"; + break; + default: + return $svgData; } $rsvgCommand = 'rsvg-convert -w ' .$width. ' -h '.$height.' -f ' . $format; + if ($format == 'png'){ + $rsvgCommand = 'rsvg-convert -b "#FFFFFF" -w ' .$width. ' -h '.$height.' -f ' . $format; + } $exifCommand = 'exiftool ' . $exifArgs . ' -o - -'; $command = $rsvgCommand . ' | ' . $exifCommand; From 17380e6a94903d430e3f6de7a8f3462b9331d94f Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Wed, 14 Jun 2023 18:37:00 -0400 Subject: [PATCH 23/47] Revert "Updates to export, printing, code organization for chart logic, and chart layout consistency" This reverts commit cc27014ed0b3e8504fa773379e0f3b6c6bb36d1a. --- .../chrome-helper/chrome-helper.js | 21 +- .../WarehouseControllerProvider.php | 499 +++++++++--------- html/gui/js/CCR.js | 263 ++++----- html/gui/js/libraries/PlotlyUtilities.js | 156 ------ .../modules/job_viewer/AnalyticChartPanel.js | 186 ++++--- html/gui/js/modules/job_viewer/ChartPanel.js | 188 +++++-- html/gui/js/modules/job_viewer/ChartTab.js | 8 +- html/gui/js/modules/job_viewer/GanttChart.js | 267 +++++----- html/gui/js/modules/job_viewer/JobViewer.js | 243 +++++---- html/plotly_template.html | 180 +++++-- libraries/charting.php | 49 +- 11 files changed, 1022 insertions(+), 1038 deletions(-) delete mode 100644 html/gui/js/libraries/PlotlyUtilities.js diff --git a/background_scripts/chrome-helper/chrome-helper.js b/background_scripts/chrome-helper/chrome-helper.js index 1c5899e990..ff304435cf 100755 --- a/background_scripts/chrome-helper/chrome-helper.js +++ b/background_scripts/chrome-helper/chrome-helper.js @@ -20,24 +20,17 @@ const args = require('yargs').argv; await page.goto('file://' + args['input-file']); - var svgInnerHtml; + const highchartInnerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); - if (args['plotly']){ - // Chart traces and axis values svg - var plotlyChart = await page.evaluate(() => document.querySelector('.user-select-none.svg-container').children[0].outerHTML); - // Chart title and axis titles svg - var plotlyLabels = await page.evaluate(() => document.querySelector('.user-select-none.svg-container').children[2].innerHTML); - - plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); - let plotlyImage = plotlyChart + "" + plotlyLabels + ""; - - svgInnerHtml = plotlyImage.replace(/
||<\/b>/gm,""); //HTML tags in titles throw xml error + if (highchartInnerHtml !== null){ + console.log(JSON.stringify(highchartInnerHtml)); } - else{ - svgInnerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); + else{ + const plotlyInnerHtml = await page.evaluate(() => document.querySelector('#container').innerHTML); + console.log(JSON.stringify(plotlyInnerHtml)); } - console.log(JSON.stringify(svgInnerHtml)); + console.log(JSON.stringify(innerHtml)); await browser.close(); })(); diff --git a/classes/Rest/Controllers/WarehouseControllerProvider.php b/classes/Rest/Controllers/WarehouseControllerProvider.php index f919d4786a..2ab9191d7c 100644 --- a/classes/Rest/Controllers/WarehouseControllerProvider.php +++ b/classes/Rest/Controllers/WarehouseControllerProvider.php @@ -97,115 +97,115 @@ class WarehouseControllerProvider extends BaseControllerProvider */ private $_supported_types = array( \DataWarehouse\Query\RawQueryTypes::ACCOUNTING => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::ACCOUNTING, - "dtype" => "infoid", - "text" => "Accounting data", - "url" => "/rest/v1.0/warehouse/search/jobs/accounting", - "documentation" => "Shows information about the job that was obtained from the resource manager. - This includes timing information such as the start and end time of the job as - well as administrative information such as the user that submitted the job and - the account that was charged.", - "type" => "keyvaluedata", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::ACCOUNTING, + "dtype" => "infoid", + "text" => "Accounting data", + "url" => "/rest/v1.0/warehouse/search/jobs/accounting", + "documentation" => "Shows information about the job that was obtained from the resource manager. + This includes timing information such as the start and end time of the job as + well as administrative information such as the user that submitted the job and + the account that was charged.", + "type" => "keyvaluedata", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::BATCH_SCRIPT => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::BATCH_SCRIPT, - "dtype" => "infoid", - "text" => "Job script", - "url" => "/rest/v1.0/warehouse/search/jobs/jobscript", - "documentation" => "Shows the job batch script that was passed to the resource manager when the - job was submitted. The script is displayed verbatim.", - "type" => "utf8-text", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::BATCH_SCRIPT, + "dtype" => "infoid", + "text" => "Job script", + "url" => "/rest/v1.0/warehouse/search/jobs/jobscript", + "documentation" => "Shows the job batch script that was passed to the resource manager when the + job was submitted. The script is displayed verbatim.", + "type" => "utf8-text", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::EXECUTABLE => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::EXECUTABLE, - "dtype" => "infoid", - "text" => "Executable information", - "url" => "/rest/v1.0/warehouse/search/jobs/executable", - "documentation" => "Shows information about the processes that were run on the compute nodes during - the job. This information includes the names of the various processes and may - contain information about the linked libraries, loaded modules and process - environment.", - "type" => "nested", - "leaf" => true), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::EXECUTABLE, + "dtype" => "infoid", + "text" => "Executable information", + "url" => "/rest/v1.0/warehouse/search/jobs/executable", + "documentation" => "Shows information about the processes that were run on the compute nodes during + the job. This information includes the names of the various processes and may + contain information about the linked libraries, loaded modules and process + environment.", + "type" => "nested", + "leaf" => true), \DataWarehouse\Query\RawQueryTypes::PEERS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::PEERS, - "dtype" => "infoid", - "text" => "Peers", - 'url' => '/rest/v1.0/warehouse/search/jobs/peers', - 'documentation' => 'Shows the list of other HPC jobs that ran concurrently using the same shared hardware resources.', - 'type' => 'ganttchart', - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::PEERS, + "dtype" => "infoid", + "text" => "Peers", + 'url' => '/rest/v1.0/warehouse/search/jobs/peers', + 'documentation' => 'Shows the list of other HPC jobs that ran concurrently using the same shared hardware resources.', + 'type' => 'ganttchart', + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::NORMALIZED_METRICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::NORMALIZED_METRICS, - "dtype" => "infoid", - "text" => "Summary metrics", - "url" => "/rest/v1.0/warehouse/search/jobs/metrics", - "documentation" => "shows a table with the performance metrics collected during - the job. These are typically average values over the job. The - label for each row has a tooltip that describes the metric. The - data are grouped into the following categories: -
    -
  • CPU Statistics: information about the cores on which the job was - assigned, such as CPU usage, FLOPs, CPI
  • -
  • File I/O Statistics: information about the data read from and - written to block devices and file system mount points.
  • -
  • Memory Statistics: information about the memory usage on the nodes - on which the job ran.
  • -
  • Network I/O Statistics: information about the data transmitted and - received over the network devices.
  • -
-", -"type" => "metrics", -"leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::NORMALIZED_METRICS, + "dtype" => "infoid", + "text" => "Summary metrics", + "url" => "/rest/v1.0/warehouse/search/jobs/metrics", + "documentation" => "shows a table with the performance metrics collected during + the job. These are typically average values over the job. The + label for each row has a tooltip that describes the metric. The + data are grouped into the following categories: +
    +
  • CPU Statistics: information about the cores on which the job was + assigned, such as CPU usage, FLOPs, CPI
  • +
  • File I/O Statistics: information about the data read from and + written to block devices and file system mount points.
  • +
  • Memory Statistics: information about the memory usage on the nodes + on which the job ran.
  • +
  • Network I/O Statistics: information about the data transmitted and + received over the network devices.
  • +
+ ", + "type" => "metrics", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::DETAILED_METRICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::DETAILED_METRICS, - "dtype" => "infoid", - "text" => "Detailed metrics", - "url" => "/rest/v1.0/warehouse/search/jobs/detailedmetrics", - "documentation" => "shows the data generated by the job summarization software. Please - consult the relevant job summarization software documentation for details - about these metrics.", - "type" => "detailedmetrics", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::DETAILED_METRICS, + "dtype" => "infoid", + "text" => "Detailed metrics", + "url" => "/rest/v1.0/warehouse/search/jobs/detailedmetrics", + "documentation" => "shows the data generated by the job summarization software. Please + consult the relevant job summarization software documentation for details + about these metrics.", + "type" => "detailedmetrics", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::ANALYTICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::ANALYTICS, - "dtype" => "infoid", - "text" => "Job analytics", - "url" => "/rest/v1.0/warehouse/search/jobs/analytics", - "documentation" => "Click the help icon on each plot to show the description of the analytic", - "type" => "analytics", - "hidden" => true, - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::ANALYTICS, + "dtype" => "infoid", + "text" => "Job analytics", + "url" => "/rest/v1.0/warehouse/search/jobs/analytics", + "documentation" => "Click the help icon on each plot to show the description of the analytic", + "type" => "analytics", + "hidden" => true, + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS, - "dtype" => "infoid", - "text" => "Timeseries", - "leaf" => false - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS, + "dtype" => "infoid", + "text" => "Timeseries", + "leaf" => false + ), \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE, - "dtype" => "infoid", - "text" => "VM State/Events", - "documentation" => "Show the lifecycle of a VM. Green signifies when a VM is active and red signifies when a VM is stopped.", - "url" => "/rest/v1.0/warehouse/search/cloud/vmstate", - "type" => "vmstate", - "leaf" => true - ) + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE, + "dtype" => "infoid", + "text" => "VM State/Events", + "documentation" => "Show the lifecycle of a VM. Green signifies when a VM is active and red signifies when a VM is stopped.", + "url" => "/rest/v1.0/warehouse/search/cloud/vmstate", + "type" => "vmstate", + "leaf" => true + ) ); /** @@ -1025,7 +1025,7 @@ public function getQuickFilters(Request $request, Application $app) )); } - /** + /** * Attempt to retrieve the the name for the provided dimensionId. * * @param Request $request @@ -1042,15 +1042,15 @@ public function getDimensionName(Request $request, Application $app, $dimensionI $status = $success ? 200 : 404; $payload = $success - ? array( - 'success' => $success, - 'results' => array( - 'name' => $dimensionName - )) - : array( - 'success' => false, - 'message' => "Unable to find a name for dimension: $dimensionId" - ); + ? array( + 'success' => $success, + 'results' => array( + 'name' => $dimensionName + )) + : array( + 'success' => false, + 'message' => "Unable to find a name for dimension: $dimensionId" + ); return $app->json( $payload, @@ -1077,16 +1077,16 @@ public function getDimensionValueName(Request $request, Application $app, $dimen $status = $success ? 200 : 404; $payload = $success - ? array( - 'success' => $success, - 'results' => array( - 'name' => $valueName - ) - ) - : array( - 'success' => $success, - 'message' => "Unable to find a name for dimesion: $dimensionId | value: $valueId" - ); + ? array( + 'success' => $success, + 'results' => array( + 'name' => $valueName + ) + ) + : array( + 'success' => $success, + 'message' => "Unable to find a name for dimesion: $dimensionId | value: $valueId" + ); return $app->json( $payload, @@ -1381,44 +1381,44 @@ public function processJobSearch(Request $request, Application $app, XDUser $use public function processJobSearchByAction(Request $request, Application $app, XDUser $user, $action, $realm, $jobId, $actionName) { switch ($action) { - case 'accounting': - case 'jobscript': - case 'analysis': - case 'metrics': - case 'analytics': - $results = $this->getJobData($app, $user, $realm, $jobId, $action, $actionName); - break; - case 'peers': - $start = $this->getIntParam($request, 'start', true); - $limit = $this->getIntParam($request, 'limit', true); - $results = $this->getJobPeers($app, $user, $realm, $jobId, $start, $limit); - break; - case 'executable': - $results = $this->getJobExecutable($app, $user, $realm, $jobId, $action, $actionName); - break; - case 'detailedmetrics': - $results = $this->getJobSummary($app, $user, $realm, $jobId, $action, $actionName); - break; - case 'timeseries': - $tsId = $this->getStringParam($request, 'tsid', true); - $nodeId = $this->getIntParam($request, 'nodeid', false); - $cpuId = $this->getIntParam($request, 'cpuid', false); - - $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, $tsId, $nodeId, $cpuId); - break; - case 'vmstate': - $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, null, null, null); - break; - default: - $results = $app->json( - array( - 'success' => false, - 'action' => $actionName, - 'message' => "Unable to process the requested operation. Unsupported action $action." - ), - 400 - ); - break; + case 'accounting': + case 'jobscript': + case 'analysis': + case 'metrics': + case 'analytics': + $results = $this->getJobData($app, $user, $realm, $jobId, $action, $actionName); + break; + case 'peers': + $start = $this->getIntParam($request, 'start', true); + $limit = $this->getIntParam($request, 'limit', true); + $results = $this->getJobPeers($app, $user, $realm, $jobId, $start, $limit); + break; + case 'executable': + $results = $this->getJobExecutable($app, $user, $realm, $jobId, $action, $actionName); + break; + case 'detailedmetrics': + $results = $this->getJobSummary($app, $user, $realm, $jobId, $action, $actionName); + break; + case 'timeseries': + $tsId = $this->getStringParam($request, 'tsid', true); + $nodeId = $this->getIntParam($request, 'nodeid', false); + $cpuId = $this->getIntParam($request, 'cpuid', false); + + $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, $tsId, $nodeId, $cpuId); + break; + case 'vmstate': + $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, null, null, null); + break; + default: + $results = $app->json( + array( + 'success' => false, + 'action' => $actionName, + 'message' => "Unable to process the requested operation. Unsupported action $action." + ), + 400 + ); + break; } return $results; @@ -1591,7 +1591,7 @@ private function getJobExecutable(Application $app, \XDUser $user, $realm, $jobI private function arraytostore(array $values) { - return array(array("key" => ".", "value" => "", "expanded" => true, "children" => $this->atosrecurse($values, false) )); + return array(array("key" => ".", "value" => "", "expanded" => true, "children" => $this->atosrecurse($values, false) )); } private function atosrecurse(array $values) @@ -1713,34 +1713,34 @@ private function processJobRequest( switch ($infoId) { - case "" . \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE: - $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; - $info = new $infoclass(); - - $result = array(); - foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { - $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/vmstate"; - $tsid['type'] = "timeseries"; - $tsid['dtype'] = "tsid"; - $result[] = $tsid; - } - return $app->json(array('success' => true, "results" => $result)); - break; - case "" . \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS: - $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; - $info = new $infoclass(); - - $result = array(); - foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { - $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/timeseries"; - $tsid['type'] = "timeseries"; - $tsid['dtype'] = "tsid"; - $result[] = $tsid; - } - return $app->json(array('success' => true, "results" => $result)); - break; - default: - throw new BadRequestException("Node is a leaf"); + case "" . \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE: + $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; + $info = new $infoclass(); + + $result = array(); + foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { + $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/vmstate"; + $tsid['type'] = "timeseries"; + $tsid['dtype'] = "tsid"; + $result[] = $tsid; + } + return $app->json(array('success' => true, "results" => $result)); + break; + case "" . \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS: + $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; + $info = new $infoclass(); + + $result = array(); + foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { + $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/timeseries"; + $tsid['type'] = "timeseries"; + $tsid['dtype'] = "tsid"; + $result[] = $tsid; + } + return $app->json(array('success' => true, "results" => $result)); + break; + default: + throw new BadRequestException("Node is a leaf"); } } @@ -1981,49 +1981,34 @@ private function chartImageResponse($data, $type, $settings) $chartConfig = array( 'colors' => array( '#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', - '#f28f43', '#77a1e5', '#c42525', '#a6c96a' + '#f28f43', '#77a1e5', '#c42525', '#a6c96a' ), - 'width' => $settings['width'], - 'height' => $settings['height'], - 'data' => $data, - 'paper_bgcolor' => 'white', - 'plot_bgcolor' => 'white', + 'series' => $data['series'], 'xaxis' => array( 'tickfont' => array( - 'size' => $axisLabelFontSize + 'size' => $axisLabelFontSize ), 'zerolinewidth' => $lineWidth, - 'title' => 'Time (' . $data['schema']['timezone'] . ')', - 'titlefont' => array( - 'size' => $axisTitleFontSize, - 'color' => '#5078a0' - ), - 'color' => '#606060', - 'ticks' => 'outside', - 'ticklen' => 10, - 'tickcolor' => '#c0cfe0', - 'linecolor' => '#c0cfe0', - 'automargin' => true, - 'showgrid' => false + 'title' => '' + 'Time (' . $data['schema']['timezone'] . ')' + '', + 'titlefont' => array( + 'size' => $axisTitleFontSize, + 'color' => '#5078a0' + ) ), 'yaxis' => array( - 'title' => 'Time (' . $data['schema']['units'] . ')', + 'title' => '' + 'Time (' . $data['schema']['units'] . ')' + '', 'titlefont' => array( - 'size' => $axisTitleFontSize, - 'color' => '#5078a0' + 'size' => $axisTitleFontSize, + 'color' => '#5078a0' ), 'zerolinewidth' => $lineWidth, - 'tickfont' => array( - 'size' => $axisLabelFontSize + 'ticfont' => array( + 'size' => $axisLabelFontSize ), - 'rangemode' => 'nonnegative', - 'color' => '#606060', - 'gridcolor' => 'lightgray', - 'automargin' => true, - 'linecolor' => '#c0cfe0' + 'rangemode' => 'nonnegative' ), 'showlegend' => false, - 'plotOptions' => array( + 'plotOptions' => array( 'line' => array( 'lineWidth' => $lineWidth, 'marker' => array( @@ -2031,22 +2016,16 @@ private function chartImageResponse($data, $type, $settings) ) ) ), - 'annotations' => array(array( + 'annotations' => array( 'text' => $data['schema']['source'] . '. Powered by XDMoD/Plotly', - 'font' => array( - 'color' => '#909090', - 'size' => 10, - 'family' => 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' - ), - 'xref' => 'paper', - 'yref' => 'paper', - 'x' => 1, - 'xanchor' => 'right', - 'yanchor' => 'bottom', - 'y' => 0, - 'yshift' => -80, - 'showarrow' => false - )), + 'xref' => 'paper', + 'yref' => 'paper', + 'x' => 1, + 'xanchor' => 'left', + 'y' => 0, + 'yanchor' => 'top', + 'showarrow' => false + ), 'title' => array( 'font' => array( 'color' => '#444b6e', @@ -2057,11 +2036,15 @@ private function chartImageResponse($data, $type, $settings) ) ); + /*if (strpos($data['schema']['units'], '%') !== false) { + $chartConfig['yAxis']['max'] = 100.0; + }*/ + $globalConfig = array( 'timezone' => $data['schema']['timezone'] ); - $chartImage = \xd_charting\exportHighchart($chartConfig, $settings['width'], $settings['height'], $settings['scale'], $type, $globalConfig, $settings['fileMetadata'], true); + $chartImage = \xd_charting\exportHighchart($chartConfig, $settings['width'], $settings['height'], $settings['scale'], $type, $globalConfig, $settings['fileMetadata']); $chartFilename = $settings['fileMetadata']['title'] . '.' . $type; $mimeOverride = $type == 'svg' ? 'image/svg+xml' : null; @@ -2085,30 +2068,30 @@ private function getJobTimeSeriesData(Application $app, Request $request, \XDUse } switch ($format) { - case 'png': - case 'pdf': - case 'svg': - $exportConfig = array( - 'width' => $this->getIntParam($request, 'width', false, 916), - 'height' => $this->getIntParam($request, 'height', false, 484), - 'scale' => floatval($this->getStringParam($request, 'scale', false, '1')), - 'font_size' => $this->getIntParam($request, 'font_size', false, 3), - 'show_title' => $this->getStringParam($request, 'show_title', false, 'y') === 'y' ? true : false, - 'fileMetadata' => array( - 'author' => $user->getFormalName(), - 'subject' => 'Timeseries data for ' . $results['schema']['source'], - 'title' => $results['schema']['description'] - ) - ); - $response = $this->chartImageResponse($results, $format, $exportConfig); - break; - case 'csv': - $response = $this->chartDataResponse($results); - break; - case 'json': - default: - $response = $app->json(array("success" => true, "data" => array($results))); - break; + case 'png': + case 'pdf': + case 'svg': + $exportConfig = array( + 'width' => $this->getIntParam($request, 'width', false, 916), + 'height' => $this->getIntParam($request, 'height', false, 484), + 'scale' => floatval($this->getStringParam($request, 'scale', false, '1')), + 'font_size' => $this->getIntParam($request, 'font_size', false, 3), + 'show_title' => $this->getStringParam($request, 'show_title', false, 'y') === 'y' ? true : false, + 'fileMetadata' => array( + 'author' => $user->getFormalName(), + 'subject' => 'Timeseries data for ' . $results['schema']['source'], + 'title' => $results['schema']['description'] + ) + ); + $response = $this->chartImageResponse($results, $format, $exportConfig); + break; + case 'csv': + $response = $this->chartDataResponse($results); + break; + case 'json': + default: + $response = $app->json(array("success" => true, "data" => array($results))); + break; } return $response; diff --git a/html/gui/js/CCR.js b/html/gui/js/CCR.js index 531da5456f..d8888a1eef 100644 --- a/html/gui/js/CCR.js +++ b/html/gui/js/CCR.js @@ -223,18 +223,18 @@ XDMoD.GlobalToolbar.Contact = function () { id: 'global-toolbar-contact-us-send-message', handler: contactHandler }, - { - text: 'Request Feature', - iconCls: 'bulb_16', - id: 'global-toolbar-contact-us-request-feature', - handler: contactHandler - }, - { - text: 'Submit Support Request', - iconCls: 'help_16', - id: 'global-toolbar-contact-us-submit-support-request', - handler: contactHandler - }] + { + text: 'Request Feature', + iconCls: 'bulb_16', + id: 'global-toolbar-contact-us-request-feature', + handler: contactHandler + }, + { + text: 'Submit Support Request', + iconCls: 'help_16', + id: 'global-toolbar-contact-us-submit-support-request', + handler: contactHandler + }] }) //menu }; }; //XDMoD.GlobalToolbar.Contact @@ -254,31 +254,31 @@ XDMoD.createTour = function () { var tourItems = [ { html: 'Welcome to XDMoD! This tour will guide you through some of the features of XDMoD.' + - ((CCR.xdmod.publicUser) ? ' This tour has additional information after you sign in.' : ''), + ((CCR.xdmod.publicUser) ? ' This tour has additional information after you sign in.' : ''), target: '#tg_summary', position: 't-t' }, { html: 'XDMoD provides a wealth of information. Different functionality is provided by individual tabs listed below.' + - ((CCR.xdmod.publicUser) ? ' Some tabs are only visible after you sign in.' : '') + - '
    ' + - '
  • ' + - dashboardDescription + - '
  • ' + - '
  • ' + - 'Usage - A convenient way to browse all available statistics.' + - '
  • ' + - '
  • ' + - 'Metric Explorer - Allows you to create complex charts containing multiple statistics and optionally apply filters.' + - '
  • ' + - '
  • ' + - 'Report Generator - Create reports that may contain multiple charts. Reports may be downloaded directly or scheduled to be emailed periodically.' + - '
  • ' + - '
  • ' + - 'Job Viewer - A detailed view of individual jobs that provides an overall summary of job accounting data, job performance, and a temporal view of a job\'s CPU, network, and disk I/O utilization.' + - '
  • ' + - '
' + - '* Additional modules might provide additional tabs not mentioned here.', + ((CCR.xdmod.publicUser) ? ' Some tabs are only visible after you sign in.' : '') + + '
    ' + + '
  • ' + + dashboardDescription + + '
  • ' + + '
  • ' + + 'Usage - A convenient way to browse all available statistics.' + + '
  • ' + + '
  • ' + + 'Metric Explorer - Allows you to create complex charts containing multiple statistics and optionally apply filters.' + + '
  • ' + + '
  • ' + + 'Report Generator - Create reports that may contain multiple charts. Reports may be downloaded directly or scheduled to be emailed periodically.' + + '
  • ' + + '
  • ' + + 'Job Viewer - A detailed view of individual jobs that provides an overall summary of job accounting data, job performance, and a temporal view of a job\'s CPU, network, and disk I/O utilization.' + + '
  • ' + + '
' + + '* Additional modules might provide additional tabs not mentioned here.', target: '#main_tab_panel .x-tab-panel-header', position: 'tl-bl', maxWidth: 400, @@ -289,19 +289,19 @@ XDMoD.createTour = function () { tourItems.push( { html: 'The Dashboard tab presents individual component and summary charts that provide an overview of information that is available throughout XDMoD as well as the ability to access more detailed information. ' + - 'In order to provide relevant information, XDMoD accounts have an assigned role (set by the XDMoD system administrator), the content of the dashboard is tailored to your role.' + - '
    ' + - '
  • ' + - 'User - Information such as a list of all jobs that you have run as well as other useful information such as queue wait times. ' + - '
  • ' + - '
  • ' + - 'Principal Investigator - Information about all the jobs running under your projects. ' + - '
  • ' + - '
  • ' + - 'Center Staff - Information on all user jobs run at the center as well as information you can use to gauge how well the center is running. ' + - '
  • ' + - '
' + - 'For more information on XDMoD roles, please refer to the XDMoD User Manual available from the Help menu.', + 'In order to provide relevant information, XDMoD accounts have an assigned role (set by the XDMoD system administrator), the content of the dashboard is tailored to your role.' + + '
    ' + + '
  • ' + + 'User - Information such as a list of all jobs that you have run as well as other useful information such as queue wait times. ' + + '
  • ' + + '
  • ' + + 'Principal Investigator - Information about all the jobs running under your projects. ' + + '
  • ' + + '
  • ' + + 'Center Staff - Information on all user jobs run at the center as well as information you can use to gauge how well the center is running. ' + + '
  • ' + + '
' + + 'For more information on XDMoD roles, please refer to the XDMoD User Manual available from the Help menu.', target: '#tg_summary', position: 't-t' } @@ -310,28 +310,28 @@ XDMoD.createTour = function () { tourItems.push( { html: 'Each component provides a toolbar that is customized to provide controls relevant to that component. ' + - 'Hovering over each control with your mouse will display a tool-tip describing what that control does.' + - '
    ' + - '
  • ' + - '"?" - Display additional information about a component' + - '
  • ' + - '
  • ' + - '"*" - Open a chart in the Metric Explorer.' + - '
  • ' + - '
', + 'Hovering over each control with your mouse will display a tool-tip describing what that control does.' + + '
    ' + + '
  • ' + + '"?" - Display additional information about a component' + + '
  • ' + + '
  • ' + + '"*" - Open a chart in the Metric Explorer.' + + '
  • ' + + '
', target: '.x-panel-header:first', position: 'tl-br', offset: [-10, 0] }, { html: 'The Help button provides you with the following options:' + - '
    ' + - '
  • User Manual - A detailed help document for XDMoD. If help is available for the section of XDMoD you currently are visiting, this' + - 'will automatically navigate to the respective section.' + - '
  • ' + - '
  • XDMoD Tour - start this tour again' + - '
  • ' + - '
', + '
    ' + + '
  • User Manual - A detailed help document for XDMoD. If help is available for the section of XDMoD you currently are visiting, this' + + 'will automatically navigate to the respective section.' + + '
  • ' + + '
  • XDMoD Tour - start this tour again' + + '
  • ' + + '
', target: '#help_button', position: 'tr-bl' } @@ -339,7 +339,7 @@ XDMoD.createTour = function () { if (CCR.xdmod.publicUser !== true) { tourItems.push({ html: 'The My Profile button allows you to view and update your account settings. Your role will be displayed in the title bar of the My Profile window.' + - '

>Information you can update includes your Name, Email Address and Password.', + '

>Information you can update includes your Name, Email Address and Password.', target: '#global-toolbar-profile', position: 'tl-bl' }); @@ -751,8 +751,8 @@ CCR.xdmod.ui.login_prompt = null; CCR.xdmod.ui.createUserManualLink = function (tags) { return '
' + - 'For more information, please refer to the User Manual' + - '
'; + 'For more information, please refer to the User Manual' + + ''; }; //CCR.xdmod.ui.createUserManualLink @@ -1319,24 +1319,24 @@ CCR.xdmod.ui.actionLogin = function (config, animateTarget) { id: 'btn_sign_in', handler: signInWithLocalAccount }, + { + xtype: 'container', + autoEl: 'div', + autoWidth: true, + flex: 2, + height: 38, + id: 'assistancePrompt', + items: [{ + xtype: 'tbtext', + html: 'Forgot your password?', + id: 'forgot_password_link' + }, { - xtype: 'container', - autoEl: 'div', - autoWidth: true, - flex: 2, - height: 38, - id: 'assistancePrompt', - items: [{ - xtype: 'tbtext', - html: 'Forgot your password?', - id: 'forgot_password_link' - }, - { - xtype: 'tbtext', - html: 'Don\'t have an account?', - id: 'sign_up_link' - }] + xtype: 'tbtext', + html: 'Don\'t have an account?', + id: 'sign_up_link' }] + }] } ]; @@ -1861,7 +1861,7 @@ CCR.isType = function (value, type) { return Object.prototype.toString.call(value) === type; } else { return Object.prototype.toString.call(value) === - Object.prototype.toString.call(type); + Object.prototype.toString.call(type); } }; @@ -1892,10 +1892,10 @@ CCR.merge = function (obj1, obj2) { CCR.getParameter = function (name, source) { name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), - results = regex.exec(source); + results = regex.exec(source); return results === null - ? "" - : decodeURIComponent(results[1].replace(/\+/g, " ")); + ? "" + : decodeURIComponent(results[1].replace(/\+/g, " ")); }; /* * Process the location hash string. The string should have the form: @@ -1909,8 +1909,8 @@ CCR.getParameter = function (name, source) { */ CCR.tokenize = function (hash) { var raw = (typeof hash !== 'string') - ? String(hash) - : hash; + ? String(hash) + : hash; var matches = raw.match(/^#?(([^:\\?]*):?([^:\\?]*):?([^:\\?]*)\??(.*))/); @@ -2124,13 +2124,13 @@ CCR._encodeObject = function(value, options) { var propertyValue = value[property]; if (CCR.isType(propertyValue, CCR.Types.Array)) { results.push(property + '=' + encodeURIComponent(CCR._encodeArray(propertyValue, { - wrap: true, - seperator: ':' - }))); + wrap: true, + seperator: ':' + }))); } else if (CCR.isType(propertyValue, CCR.Types.Object)) { results.push(property + '=' + encodeURIComponent(CCR._encodeObject(propertyValue, { - wrap: true - }))); + wrap: true + }))); } else { var key = wrap ? '"' + property + '"' : property; results.push(key + separator + propertyValue); @@ -2153,8 +2153,8 @@ CCR.encode = function (values) { var isArray = CCR.isType(values[property], CCR.Types.Array); var isObject = CCR.isType(values[property], CCR.Types.Object); var value = isArray || isObject - ? encodeURIComponent(CCR.deepEncode(values[property])) - : values[property]; + ? encodeURIComponent(CCR.deepEncode(values[property])) + : values[property]; parameters.push(property + '=' + value); } } @@ -2184,7 +2184,7 @@ CCR.apply = function (lhs, rhs) { for (property in rhs) { if (rhs.hasOwnProperty(property)) { var rhsExists = rhs[property] !== undefined - && rhs[property] !== null; + && rhs[property] !== null; if (rhsExists) { results[property] = rhs[property]; } @@ -2210,7 +2210,7 @@ CCR.toInt = function (value) { /** * Displays a MessageBox to the user with the error styling. - * + * * @param {String} title of the Error Dialog Box. * @param {String} message that will be displayed to the user. * @param {Function} success function that will be executed if the user does not @@ -2232,7 +2232,7 @@ CCR.error = function (title, message, success, failure, buttons) { fn: function(buttonId, text, options) { var compare = CCR.compare; if (compare.strings(buttonId, Ext.MessageBox.buttonText['no']) - || compare.strings(buttonId, Ext.MessageBox.buttonText['cancel'])) { + || compare.strings(buttonId, Ext.MessageBox.buttonText['cancel'])) { failure(buttonId, text, options); } else { success(buttonId, text, options); @@ -2306,11 +2306,11 @@ CCR.getInstance = function(instancePath, classPath, config) { * @return {*} the result of walking the provided 'path'. **/ var getReference = function(path, callback) { - callback = callback !== undefined - ? callback - : function(previous, current) { - return previous[current]; - }; + callback = callback !== undefined + ? callback + : function(previous, current) { + return previous[current]; + }; return path.split('.').reduce( callback, window ); }; @@ -2371,51 +2371,6 @@ CCR.intersect = function (left, right) { return found; }; -/** - * Basic word wrap that will insert
tags at specified line break points. - * - * @param {String} str - * @param {Int} lineBreak - * @return {String} - */ -CCR.wordWrap = function (str, lineBreak) { - var retVal = ""; - var count = 0; - var newLineIndex = 0; - var lastSpaceIndex = 0; - let len = str.length; - - if (len < lineBreak){ - return str; - } - - for (var i = 0; i < len; i++){ - if (str[i] === ' '){ - lastSpaceIndex = i; - } - if (count == lineBreak){ - //Non-Alphanumeric character found - if (!(/[a-z]|[0-9]/i.test(str[i]))){ - retVal = retVal.concat("
", str.substr(newLineIndex, lineBreak)); - newLineIndex = i; - } - //Line break landed in the middle of a word. Need to back up. - else{ - retVal = retVal.concat("
", str.substr(newLineIndex, (lastSpaceIndex - newLineIndex))); - newLineIndex = lastSpaceIndex + 1; //Start on next character - i = lastSpaceIndex + 1; - } - //Append rest of str if there remains less characters than specified line break. - if ((len - i) < lineBreak){ - retVal = retVal.concat("
", str.substr(newLineIndex, len)); - break; - } - count = 0; - } - count++; - } - return retVal; -} // override 3.4.0 to be able to restore column state Ext.override(Ext.grid.ColumnModel, { @@ -2515,18 +2470,18 @@ Ext.override(Ext.ToolTip, { // override 3.4.0 to ensure that the grid stops editing if the view is refreshed // actual bug: removing grid lines with active lookup editor didn't hide editor Ext.grid.GridView.prototype.processRows = - Ext.grid.GridView.prototype.processRows.createInterceptor(function () { - if (this.grid) { - this.grid.stopEditing(true); - } - }); + Ext.grid.GridView.prototype.processRows.createInterceptor(function () { + if (this.grid) { + this.grid.stopEditing(true); + } + }); // override 3.4.0 to fix issue with chart labels losing their labelRenderer after hide/show Ext.override(Ext.chart.CartesianChart, { createAxis: function (axis, value) { var o = Ext.apply({}, value), - ref, - old; + ref, + old; if (this[axis]) { old = this[axis].labelFunction; @@ -2575,7 +2530,7 @@ Ext.override(Ext.menu.Item, { '', '{text}', '' - ); + ); } var a = this.getTemplateArgs(); this.el = position ? this.itemTpl.insertBefore(position, a, true) : this.itemTpl.append(container, a, true); diff --git a/html/gui/js/libraries/PlotlyUtilities.js b/html/gui/js/libraries/PlotlyUtilities.js deleted file mode 100644 index cb7d76e68d..0000000000 --- a/html/gui/js/libraries/PlotlyUtilities.js +++ /dev/null @@ -1,156 +0,0 @@ -/* generateChartOptions - Generates data array and layout dict for Plotly Chart - * ** Currently assumes that data is in format of a record returned in the JobViewer ** - * - * @param{dict} Record containing chart data - * - */ -function generateChartOptions(record){ - let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', - '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - var data = []; - var isEnvelope = false; - var hasSingleDataPoint = false; - var tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); - var ymin, ymax; - ymin = record.data.series[0].data[0].y; - ymax = ymin; - if (record.data.series[0].name === 'Range') { - isEnvelope = true; - ymin = record.data.series[1].data[0].y; - ymax = ymin; - tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); - } - for (let sid = 0; sid < record.data.series.length; sid++) { - var x = []; - var y = []; - var qtip = []; - let color = colors[sid % 10]; - if (record.data.series[sid].data.length == 1){ - hasSingleDataPoint = true; - } - - for(var i=0; i < record.data.series[sid].data.length; i++) { - x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS')); - y.push(record.data.series[sid].data[i].y); - if (isEnvelope){ - qtip.push(record.data.series[sid].data[i].qtip); - } - } - - var trace = { - x: x, - y: y, - marker: { - size: 0.1, - color: color - }, - line: { - width: 2, - color: color - }, - text: qtip, - hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ":%{y:.f}" + - "", - name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines' - }; - - if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ - trace['fill'] = 'tonexty'; - trace['fillcolor'] = '#5EA0E2'; - } - - if (isEnvelope){ - trace.hovertemplate = "[%{text}] " + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ":%{y:.f}" + - ""; - } - - if (hasSingleDataPoint){ - trace.marker.size = 20; - trace.mode = 'markers'; - delete trace.line; - } - - data.push(trace); - tempMin = Math.min(...y); - tempMax = Math.max(...y); - if (tempMin < ymin) ymin = tempMin; - if (tempMax > ymax) ymax = tempMax; - } - - let layout = { - hoverlabel: { - bgcolor: '#ffffff' - }, - xaxis: { - title: '' + 'Time (' + record.data.schema.timezone + ')' + '', - titlefont: { - family: 'Open-Sans, verdana, arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - ticks: 'outside', - tickcolor: '#c0cfe0', - linecolor: '#c0cfe0', - automargin: true, - showgrid: false, - }, - yaxis: { - title: '' + record.data.schema.units + '', - titlefont: { - family: 'Open-Sans, verdana, arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - range: [0, ymax + (ymax * 0.2)], - showline: false, - zeroline: false, - gridcolor: '#d8d8d8', - automargin: true, - ticks: 'outside', - tickcolor: '#ffffff', - seperatethousands: true - }, - title: { - text: record.data.schema.description, - font: { - family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif', - color: '#444b6e', - size: 16 - }, - }, - annotations: [{ - text: record.data.schema.source + '. Powered by XDMoD/Plotly', - font:{ - color: '#909090', - size: 10, - family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' - }, - xref: 'paper', - yref: 'paper', - xanchor: 'right', - yanchor: 'bottom', - x: 1, - y: 0, - yshift: -80, - showarrow: false - }], - hovermode: 'closest', - showlegend: false, - margin: { - t: 50 - } - }; - - let ret = { - chartData: data, - chartLayout: layout - }; - - return ret; -} - diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index b762bd3ceb..87f0eb4151 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -20,28 +20,33 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { { value: .25, color: '#FF0000', - bg_color: 'rgb(255,102,102)' + bg_color: 'rgb(255,102,102)' }, { value: .50, color: '#FFB336', - bg_color: 'rgb(255,255,156)' - + bg_color: 'rgb(255,255,156)' + }, { value: .75, color: '#DDDF00', - bg_color: 'rgb(255,255,102)' + bg_color: 'rgb(255,255,102)' }, { value: 1, color: '#50B432', - bg_color: 'rgb(182,255,152)' + bg_color: 'rgb(182,255,152)' } ], - - + + layout: { + hoverlabel: { + bgcolor: 'white' + }, + paper_bgcolor: 'white', + plot_bgcolor: 'white', height: 65, xaxis: { showticklabels: false, @@ -59,7 +64,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { zerolinecolor: 'black', showline: false, zerolinewidth: 0, - fixedrange: true + fixedrange: true }, yaxis: { showticklabels: false, @@ -72,26 +77,25 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { showline: false, rangemode: 'tozero', zerolinewidth: 0, - fixedrange: true + fixedrange: true }, hovermode: false, - shapes: [], - images: [], - annotations: [], + shapes: [], showlegend: false, margin: { - t: 10, - l: 9, - r: 13, - b: 10, + t: 12.5, + l: 7.5, + r: 7.5, + b: 12.5, pad: 0 } }, - config: { - displayModeBar: false, - staticPlot: true + traces: [], + config: { + displayModeBar: false, + }, + bgColors: [] }, - }, // The instance of Highcharts used as this components primary display. chart: null, @@ -105,10 +109,9 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ initComponent: function() { - this.colorSteps = Ext.apply( - this.colorSteps || [], - this._DEFAULT_CONFIG.colorSteps + this.colorSteps || [], + this._DEFAULT_CONFIG.colorSteps ); XDMoD.Module.JobViewer.AnalyticChartPanel.superclass.initComponent.call( @@ -125,7 +128,8 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * a visible element to hook into are handled here. */ render: function() { - this.chart = Plotly.newPlot(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + this.chart = true; + Plotly.newPlot(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // render /** @@ -137,6 +141,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ update_data: function(data) { this._updateData(data); + //Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // update_data /** @@ -145,14 +150,14 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ reset: function() { if (this.chart) { - Plotly.react(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + this._DEFAULT_CONFIG.traces = []; + Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); } }, // reset beforedestroy: function () { if (this.chart) { - Plotly.purge(this.id); - this.chart = null; + Plotly.purge(this.id); } }, @@ -169,13 +174,19 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (this.chart) { - let container = document.querySelector('#'+this.id); - let bgcolor = container._fullLayout.plot_bgcolor; - if (bgcolor != 'white'){ - Plotly.relayout(this.id, {width: adjWidth}); - Plotly.relayout(this.id, {plot_bgcolor: bgcolor}); + Plotly.relayout(this.id, {width: adjWidth}); + var elements = document.querySelectorAll('.bglayer'); + if (elements){ + for (var i=0; i < elements.length; i++){ + if (elements[i].firstChild){ + elements[i].firstChild.style.fill = this._DEFAULT_CONFIG.bgColors[i]; + } + } + } + if (this.errorMsg) { + this.updateErrorMessage(this.errorMsg.text.textStr); } - } + } } // resize }, // listeners @@ -188,45 +199,43 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ updateErrorMessage: function (errorStr) { if (this.errorMsg) { + this.errorMsg.text.destroy(); + this.errorMsg.image.destroy(); this.errorMsg = null; } if (errorStr) { - errorStr = CCR.wordWrap(errorStr, 60); - this.errorMsg = errorStr; - let errorImage = [ + this._DEFAULT_CONFIG.layout['images'] = [ { - source: '/gui/images/about_16.png', - align: "left", - xref: "paper", - yref: "paper", - sizex: 0.4, - sizey: 0.4, - x: 0, - y: 1.2 + "source": '/gui/images/about_16.png', + "align": "left", + "xref": "paper", + "yref": "paper", + "sizex": 0.4, + "sizey": 0.4, + "x": 0, + "y": 1.2 } - ]; - this._DEFAULT_CONFIG.layout.images = errorImage; - let errorText = [ + ] + this._DEFAULT_CONFIG.layout['annotations'] = [ { - text: '' + errorStr + '', - align: "left", - xref: "paper", - yref: "paper", - font:{ - size: 11, - }, - x : 0.05, - y : 1.2, - showarrow: false + "text": '' + errorStr + '', + "align": "left", + "xref": "paper", + "yref": "paper", + "font":{ + "size": 11, + }, + "x" : 0.05, + "y" : 1.2, + "showarrow": false } - ]; - this._DEFAULT_CONFIG.layout.annotations = errorText; - this._DEFAULT_CONFIG.layout.xaxis.showgrid = false; + ] + this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = false; } else { - this._DEFAULT_CONFIG.layout.images = []; - this._DEFAULT_CONFIG.layout.annotations = []; - this._DEFAULT_CONFIG.layout.xaxis.showgrid = true; + this._DEFAULT_CONFIG.layout['images'] = []; + this._DEFAULT_CONFIG.layout['annotations'] = []; + this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = true; } }, @@ -238,29 +247,32 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _updateData: function(data) { - var trace = {}; + this._DEFAULT_CONFIG.traces = []; + + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = 'white'; + var trace = {}; if (data.error == '') { - var chartColor = this._getDataColor(data.value); - this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; + var chartColor = this._getDataColor(data.value); + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; + this._DEFAULT_CONFIG.bgColors.push(chartColor.bg_color); + trace = { - x: [data.value], - name: data.name ? data.name : '', - width: [0.5], - marker:{ - color: chartColor.color, - line:{ - color: 'white', - width: 1 - } - }, - type: 'bar', - orientation: 'h', - }; - } - else { - this._DEFAULT_CONFIG.layout['plot_bgcolor'] = 'white'; + x: [data.value], + name: data.name ? data.name : '', + width: [0.5], + marker:{ + color: chartColor.color, + line:{ + color: 'white', + width: 1 + } + }, + type: 'bar', + orientation: 'h', + }; } + this._DEFAULT_CONFIG.traces.push(trace); this.updateErrorMessage(data.error); this._updateTitle(data); this.ownerCt.doLayout(false, true); @@ -287,7 +299,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _getDataColor: function(data) { - var ret = {}; + var ret = {}; var color = null; if (typeof data === 'number') { var steps = this.colorSteps; @@ -295,10 +307,10 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { for ( var i = 0; i < count; i++) { var step = steps[i]; if (data <= step.value) { - ret = { - color: step.color, - bg_color: step.bg_color - }; + ret = { + color: step.color, + bg_color: step.bg_color + }; return ret; } } diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 3f946bda3b..7b127135ad 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -9,7 +9,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { // The default chart config options. _DEFAULT_CONFIG: { - chartPrefix: 'CreateChartPanel', + chartPrefix: 'CreateChartPanel', }, // The chart instance. @@ -95,15 +95,15 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { */ resize: function(panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (panel.chart) { - this.chartWidth = adjWidth; - this.chartWidth = adjHeight; + this.chartWidth = adjWidth; + this.chartWidth = adjHeight; Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); } }, // resize beforedestroy: function () { if (this.chart) { - Plotly.purge(this.id); + Plotly.purge(this.id); this.chart = false; } }, @@ -114,31 +114,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { print_clicked: function () { if (this.chart) { - var chartDiv = document.querySelector('div[id^=tsid_]'); - chartDiv = chartDiv.firstChild.firstChild; // parent div of the plotly SVGs - - // Make deep copy - var tmpWidth = structuredClone(chartDiv.clientWidth); - var tmpHeight = structuredClone(chartDiv.clientHeight); - - // Resize to 'medium' export width and height -- Currently placeholder width and height - Plotly.relayout(this.id, {width: 916, height: 484}); - - // Combine Plotly svg elements similar to export - var svg; - var plotlyChart = chartDiv.children[0].outerHTML; - var plotlyLabels = chartDiv.children[2].innerHTML; - - plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); - svg = plotlyChart + plotlyLabels + '' - - var printWindow = window.open(); - printWindow.document.write(' Printing '); - printWindow.document.write(svg); - printWindow.print(); - printWindow.close(); - - Plotly.relayout(this.id, {width: tmpWidth, height: tmpHeight}); + this.chart.print(); } }, @@ -188,17 +164,150 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } if (record) { - let chartOptions = generateChartOptions(record); + let colorChoices = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', + '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + let data = []; + var isEnvelope = false; + var hasSingleDataPoint = false; + var tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); + var ymin, ymax; + ymin = record.data.series[0].data[0].y; + ymax = ymin; + for (let sid = 0; sid < record.data.series.length; sid++) { + if (record.data.series[sid].name === "Range") { + isEnvelope = true; + ymin = record.data.series[1].data[0].y; + ymax = ymin; + tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); + continue; + } + let x = []; + let y = []; + let qtip = []; + let colors = colorChoices[sid % 10]; + for(let i=0; i < record.data.series[sid].data.length; i++) { + if (record.data.series[sid].data.length == 1){ + hasSingleDataPoint = true; + } + x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); + y.push(record.data.series[sid].data[i].y); + qtip.push(record.data.series[sid].data[i].qtip); + } + + if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ + data.push({ + x: x, + y: y, + fill: 'tonexty', + fillcolor: '#5EA0E2', + marker: { + size: 0.1, + color: colors + }, + line: { + width: 2, + color: colors + }, + text: qtip, + hovertemplate: "[%{text}] " + + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y: .f}" + + "", + name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}); + } + else{ + var trace = { + x: x, + y: y, + marker: { + size: 0.1, + color: colors + }, + line: { + width: 2, + color: colors + }, + text: qtip, + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y: .f}" + + "", + name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}; + + if (isEnvelope){ + trace.hovertemplate = "[%{text}] " + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y: .f}" + + ""; + } + if (hasSingleDataPoint){ + trace.marker.size = 20; + trace.mode = 'markers'; + delete trace.line; + } + data.push(trace); + } + var tempyMin = Math.min(...y); + var tempyMax = Math.max(...y); + if (tempyMin < ymin) ymin = tempyMin; + if (tempyMax > ymax) ymax = tempyMax; + } + panel.getEl().unmask(); - + let layout = { + hoverlabel: { + bgcolor: 'white' + }, + xaxis: { + title: 'Time (' + record.data.schema.timezone + ')', + titlefont: { + family: 'Arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + ticks: 'outside', + ticklen: 10, + tickcolor: '#c0cfe0', + linecolor: '#c0cfe0', + automargin: true, + showgrid: false + }, + yaxis: { + title: '' + record.data.schema.units + '', + titlefont: { + family: 'Arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + range: [0, ymax + (ymax * 0.2)], + rangemode: 'nonnegative', + gridcolor: 'lightgray', + automargin: true, + linecolor: '#c0cfe0' + }, + title: { + text: record.data.schema.description, + font: { + color: '#444b6e', + size: 16 + } + }, + hovermode: 'closest', + showlegend: false, + margin: { + t: 50 + } + }; if (panel.chart) { - Plotly.react(this.id, chartOptions.chartData, chartOptions.chartLayout, {displayModeBar: false, doubleClick: 'reset'} ); + Plotly.react(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'} ); this.chart = true; } else { - Plotly.newPlot(this.id, chartOptions.chartData, chartOptions.chartLayout, {displayModeBar: false, doubleClick: 'reset'} ); + Plotly.newPlot(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'} ); this.chart = true; } - if (panel.chart) { + + if (this.chart) { panel.chart = document.getElementById(this.id); panel.chart.on('plotly_click', function(data, event){ var userOptions = data.points[0].data.chartSeries @@ -211,14 +320,13 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { * plots and for the series for simple plots. */ if (userOptions.dtype == 'index') { - var nodeidIndex = data.points[0].pointIndex; + var nodeidIndex = data.points[0].data.chartSeries.data.findIndex(({y}) => y === data.points[0].y); if (nodeidIndex === -1) return; drilldown = { dtype: userOptions.index, value: userOptions.data[nodeidIndex].nodeid }; - } - else { + } else { drilldown = { dtype: userOptions.dtype, value: userOptions[userOptions.dtype] @@ -227,9 +335,9 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { var path = self.path.concat([drilldown]); var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); Ext.History.add(token); - }); - } - } + }); + } + } if (!record) { panel.getEl().mask('Loading...'); diff --git a/html/gui/js/modules/job_viewer/ChartTab.js b/html/gui/js/modules/job_viewer/ChartTab.js index c6798bb866..46d898d13d 100644 --- a/html/gui/js/modules/job_viewer/ChartTab.js +++ b/html/gui/js/modules/job_viewer/ChartTab.js @@ -148,8 +148,10 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { xtype: 'container', id: this.id + '_hc', listeners: { - resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { - self.fireEvent('resize', panel, adjWidth, adjHeight, rawWidth, rawHeight); + resize: function () { + if (self.chart) { + self.chart.reflow(); + } }, render: createChart } @@ -179,6 +181,6 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { if (this.chart) { Plotly.purge(this.id); } - }, + } } }); diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index a4ba6b630a..a098ba20f0 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -18,8 +18,6 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, } }; - this.chart = null; - this.panelSettings = { pageSize: 11, url: this.url, @@ -28,163 +26,140 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, fields: ['series', 'schema', 'categories'] } }; - + XDMoD.Module.JobViewer.GanttChart.superclass.initComponent.call(this, arguments); - - this.addListener('resize', function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { - if (this.chart) { - this.chartWidth = adjWidth; - this.chartWidth = adjHeight; - Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); - } - }); - + this.addListener('updateChart', function (store) { - if (store.getCount() < 1) { + if (store.getCount() < 1) { return; - } - - var record = store.getAt(0); - var i; - var data = []; - var categories = []; - var count = 0; - var rect = []; - var yvals = []; - let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - for (i = 0; i < record.data.series.length; i++) { - var j = 0; - for (j = 0; j < record.data.series[i].data.length; j++){ - low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - categories.push(record.data.categories[count]); - var runtime = []; - let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; - var tooltip = [template]; - var ticks = [count]; - var start_time = record.data.series[i].data[j].low; - // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. - // Points on the scatter plot are created every min to create better coverage for tooltip information - while (start_time < record.data.series[i].data[j].high){ - ticks.push(count); - tooltip.push(template); - runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); - start_time += (60 * 1000); - } - runtime.push(high_data); - - rect.push({ - x0: low_data, - x1: high_data, - y0: count-0.175, - y1: count+0.175, - type: 'rect', - xref: 'x', - yref: 'y', - fillcolor: colors[i % 10], - }); - - var info = {} - if (i > 0){ - info = { - realm: record.data.series[i].data[j].ref.realm, - recordid: store.baseParams.recordid, - jobref: record.data.series[i].data[j].ref.jobid, - infoid: 3 - }; - } - peer = { - x: runtime, - y: ticks, + } + + var record = store.getAt(0); + var i; + var data = []; + var categories = []; + var count = 0; + var rect = []; + var yvals = [0]; + let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + for (i = 0; i < record.data.series.length; i++) { + var j = 0; + for (j = 0; j < record.data.series[i].data.length; j++){ + low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + categories.push(record.data.categories[count]); + var runtime = []; + let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; + var tooltip = [template]; + var ticks = [count]; + var start_time = record.data.series[i].data[j].low; + // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. + // Points on the scatter plot are created every min to create better coverage for tooltip information + while (start_time < record.data.series[i].data[j].high){ + ticks.push(count); + tooltip.push(template); + runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); + start_time += (60 * 1000); + } + runtime.push(high_data); + + rect.push({ + x0: low_data, + x1: high_data, + y0: count-0.25, + y1: count+0.25, + type: 'rect', + xref: 'x', + yref: 'y', + fillcolor: colors[i % 10], + //color: colors[i % 10] + }); + + + var info = {} + + if (i > 0){ + info = { + realm: record.data.series[i].data[j].ref.realm, + recordid: store.baseParams.recordid, + jobref: record.data.series[i].data[j].ref.jobid, + infoid: 3 + }; + } + + peer = { + x: runtime, + y: ticks, type: 'scatter', - marker:{ - color: 'rgb(255,255,255)', - size: 20 - }, - orientation: 'h', - hovertemplate: record.data.categories[count] + '
'+ - "" + '%{text} ', - text: tooltip, - chartSeries: info + + marker:{ + color: 'rgb(255,255,255)' + }, + orientation: 'h', + hovertemplate: record.data.categories[count] + '
'+ + "" + '%{text} ', + text: tooltip, + chartSeries: info }; - data.push(peer); - yvals.push(count); - count++; - } - + data.push(peer); + count++; + yvals.push(count); } - let layout = { - hoverlabel: { - bgcolor: 'white' - }, - xaxis: { - title: 'Time (' + record.data.schema.timezone + ')', - titlefont: { - family: 'Arial, sans-serif', - size: 12, - color: '#5078a0' + + } + let layout = { + hoverlabel: { + bgcolor: 'white' }, - color: '#606060', - zeroline: false, - gridcolor: '#d8d8d8', - //showgrid: false, - type: 'date', - range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] - }, - yaxis: { - autorange: 'reversed', - ticktext: categories, - zeroline: false, - showgrid: false, - showline: true, - linecolor: '#c0cfe0', - ticks: 'outside', - tickcolor: '#c0cfe0', - tickvals: yvals - }, - title: { - text: record.data.schema.description, - font: { - color: '#444b6e', - size: 16 - } - }, - hovermode: 'closest', - annotations: [{ - text: 'Powered by XDMoD/Plotly', - font:{ - color: '#909090', - size: 10, - family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' + xaxis: { + title: 'Time (' + record.data.schema.timezone + ')', + titlefont: { + family: 'Arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + ticks: 'outside', + tickcolor: '#c0cfe0', + linecolor: '#c0cfe0', + type: 'date', + range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] + }, + yaxis: { + autorange: 'reversed', + ticktext: categories, + zeroline: false, + tickvals: yvals + }, + title: { + text: record.data.schema.description, + font: { + color: '#444b6e', + size: 16 + } }, - xref: 'paper', - yref: 'paper', - xanchor: 'right', - yanchor: 'bottom', - x: 1, - y: 0, - yshift: -80, - showarrow: false + hovermode: 'closest', + showlegend: false, + margin: { + l: 175, + b: 150 - }], - showlegend: false, - margin: { - t: 50, - l: 180, - } - }; + } + }; - layout['shapes'] = rect; - this.chart = Plotly.newPlot(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'}); + layout['shapes'] = rect; + Plotly.newPlot(this.id, data, layout, {displayModeBar: false}); - var panel = document.getElementById(this.id); - panel.on('plotly_click', function(data){ - var userOptions = data.points[0].data.chartSeries; - userOptions['action'] = 'show'; - Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); - }); + + var panel = document.getElementById(this.id); + panel.on('plotly_click', function(data){ + var userOptions = data.points[0].data.chartSeries; + userOptions['action'] = 'show'; + Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); + }); + }); } }); diff --git a/html/gui/js/modules/job_viewer/JobViewer.js b/html/gui/js/modules/job_viewer/JobViewer.js index af34cd9b54..dcad5be084 100644 --- a/html/gui/js/modules/job_viewer/JobViewer.js +++ b/html/gui/js/modules/job_viewer/JobViewer.js @@ -129,12 +129,12 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { this.on('duration_change', this.noOpt); this.addEvents( - 'record_loaded', - 'data_account_loaded', - 'data_acct_loaded', - 'data_mdata_loaded', - 'data_jobrecord_loaded', - 'data_store_loaded' + 'record_loaded', + 'data_account_loaded', + 'data_acct_loaded', + 'data_mdata_loaded', + 'data_jobrecord_loaded', + 'data_store_loaded' ); // SETUP: the components for this tab and add them to this tabs 'items' @@ -182,7 +182,7 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { for (property in rhs) { if (rhs.hasOwnProperty(property)) { var rhsExists = rhs[property] !== undefined - && rhs[property] !== null; + && rhs[property] !== null; if (rhsExists) { results[property] = rhs[property]; } @@ -270,19 +270,19 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { }); self.treeSorter = new Ext.tree.TreeSorter(self.searchHistoryPanel, { - folderSort: false, - dir: "asc", - sortType: function(value, node) { - if (node.attributes.dtype == 'recordid') { - if (self.sortMode == 'age') { - return 9007199254740991 - parseInt(node.attributes.recordid, 10); + folderSort: false, + dir: "asc", + sortType: function(value, node) { + if (node.attributes.dtype == 'recordid') { + if (self.sortMode == 'age') { + return 9007199254740991 - parseInt(node.attributes.recordid, 10); + } else { + return node.attributes.text; + } } else { - return node.attributes.text; + return node.attributes[node.attributes.dtype]; } - } else { - return node.attributes[node.attributes.dtype]; } - } }); // NAVIGATION ( PARENT WESTERN PANEL ) ================================= @@ -376,8 +376,8 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { // RETURN: an array of the top level components. To be used as the // 'items' for another component with a border layout. return new Array( - searchHistory,// WEST - viewPanel // CENTER + searchHistory,// WEST + viewPanel // CENTER ); }, // setupComponents @@ -456,10 +456,10 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { getParameterByName: function (name, source) { name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), - results = regex.exec(source); + results = regex.exec(source); return results === null - ? "" - : decodeURIComponent(results[1].replace(/\+/g, " ")); + ? "" + : decodeURIComponent(results[1].replace(/\+/g, " ")); }, // getParameterByName /** @@ -821,21 +821,21 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { break; case 'vmstate': tab = new XDMoD.Module.JobViewer.VMStateChartPanel({ - id: chartId, - title: title, - url: base, - baseParams: this._getParams(path), - historyToken: '#' + this.module_id + '?' + this._createHistoryTokenFromArray(path), - path: path, - dtypes: [], - dtype: dtype, - dtypeValue: id, - store: new Ext.data.JsonStore({ - proxy: new Ext.data.HttpProxy({ url: url }), - autoLoad: true, - root: 'data', - fields: ['series', 'schema'] - }) + id: chartId, + title: title, + url: base, + baseParams: this._getParams(path), + historyToken: '#' + this.module_id + '?' + this._createHistoryTokenFromArray(path), + path: path, + dtypes: [], + dtype: dtype, + dtypeValue: id, + store: new Ext.data.JsonStore({ + proxy: new Ext.data.HttpProxy({ url: url }), + autoLoad: true, + root: 'data', + fields: ['series', 'schema'] + }) }); break; case 'analytics': @@ -973,7 +973,6 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { var chartPanel = this.getActiveJobSubPanel(); if (chartPanel) { chartPanel.fireEvent('print_clicked'); - chartPanel.resize(); } }, @@ -1139,8 +1138,8 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { if (exists(jobTab)) { var currentlyActive = tabs.activeTab && tabs.activeTab.jobId - ? tabs.activeTab.jobId === jobTab.jobId - : false; + ? tabs.activeTab.jobId === jobTab.jobId + : false; if (!currentlyActive) { jobTab.revert = false; @@ -1215,27 +1214,36 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { var exists = CCR.exists; if (isType(realm, CCR.Types.String)) { Ext.MessageBox.confirm('Delete All Saved Searches?', 'Do you want to delete all saved searches for the realm: ' + realm + ' ?', - function (btn) { - if (btn === 'ok' || btn === 'yes') { - Ext.Ajax.request({ - /*'/rest/datawarehouse/search/history?realm=' + realm + '&token=' + XDMoD.REST.token,*/ - url: XDMoD.REST.url + '/' + self.rest.warehouse + '/search/history?realm=' + realm + '&token=' + XDMoD.REST.token, - method: 'DELETE', - success: function (response) { - var data = JSON.parse(response.responseText); - var success = exists(data) && exists(data.success) && data.success; - if (success) { - self.fireEvent('clear_display'); - var current = Ext.History.getToken(); - if (isType(current, CCR.Types.String)) { - var currentToken = CCR.tokenize(current); - var token = currentToken.tab + '?realm=' + realm; - Ext.History.add(token); - } else if (isType(current, CCR.Types.Object)) { - var token = current.tab + '?realm=' + realm; - Ext.History.add(token); + function (btn) { + if (btn === 'ok' || btn === 'yes') { + Ext.Ajax.request({ + /*'/rest/datawarehouse/search/history?realm=' + realm + '&token=' + XDMoD.REST.token,*/ + url: XDMoD.REST.url + '/' + self.rest.warehouse + '/search/history?realm=' + realm + '&token=' + XDMoD.REST.token, + method: 'DELETE', + success: function (response) { + var data = JSON.parse(response.responseText); + var success = exists(data) && exists(data.success) && data.success; + if (success) { + self.fireEvent('clear_display'); + var current = Ext.History.getToken(); + if (isType(current, CCR.Types.String)) { + var currentToken = CCR.tokenize(current); + var token = currentToken.tab + '?realm=' + realm; + Ext.History.add(token); + } else if (isType(current, CCR.Types.Object)) { + var token = current.tab + '?realm=' + realm; + Ext.History.add(token); + } + } else { + Ext.MessageBox.show({ + title: 'Deletion Error', + msg: 'There was an error removing all searches for the realm: [' + realm + '].', + icon: Ext.MessageBox.ERROR, + buttons: Ext.MessageBox.OK + }); } - } else { + }, + failure: function (response) { Ext.MessageBox.show({ title: 'Deletion Error', msg: 'There was an error removing all searches for the realm: [' + realm + '].', @@ -1243,20 +1251,11 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { buttons: Ext.MessageBox.OK }); } - }, - failure: function (response) { - Ext.MessageBox.show({ - title: 'Deletion Error', - msg: 'There was an error removing all searches for the realm: [' + realm + '].', - icon: Ext.MessageBox.ERROR, - buttons: Ext.MessageBox.OK - }); - } - }); - } else { - Ext.MessageBox.alert('Ok', 'All your searches are belong to you.'); - } - }); + }); + } else { + Ext.MessageBox.alert('Ok', 'All your searches are belong to you.'); + } + }); } }, // search_delete_by_realm @@ -1273,35 +1272,44 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { if (isType(node, CCR.Types.Object)) { var title = node.text || node.attributes ? node.attributes.text : undefined; Ext.MessageBox.confirm('Delete All Saved Searches?', 'Do you want to delete the search: ' + title + ' ?', - function (text) { - if (text === 'ok' || text === 'yes') { - var recordId = node.attributes.recordid !== undefined ? node.attributes.recordid : null; - var fragment = recordId !== null ? '/search/history/' + recordId : '/search/history'; - var path = self._getPath(node); - /*'/rest/datawarehouse/search/history'*/ - var url = self._generateURL(XDMoD.REST.url + '/' + self.rest.warehouse + fragment, path); - Ext.Ajax.request({ - url: url, - method: 'DELETE', - success: function (response) { - var data = JSON.parse(response.responseText); - var success = exists(data) && exists(data.success) && data.success; - if (success) { - self.fireEvent('clear_display'); - var current = Ext.History.getToken(); - var path = self._getPath(node); - if (path && path.length && path.length > 0) delete path[path.length - 1]; - var params = self._getParams(path); - var encoded = encode(params); - if (isType(current, CCR.Types.String)) { - var currentToken = CCR.tokenize(current); - var token = currentToken.tab + '?' + encoded; - Ext.History.add(token); - } else if (isType(current, CCR.Types.Object)) { - var token = current.tab + '?' + encoded; - Ext.History.add(token); + function (text) { + if (text === 'ok' || text === 'yes') { + var recordId = node.attributes.recordid !== undefined ? node.attributes.recordid : null; + var fragment = recordId !== null ? '/search/history/' + recordId : '/search/history'; + var path = self._getPath(node); + /*'/rest/datawarehouse/search/history'*/ + var url = self._generateURL(XDMoD.REST.url + '/' + self.rest.warehouse + fragment, path); + Ext.Ajax.request({ + url: url, + method: 'DELETE', + success: function (response) { + var data = JSON.parse(response.responseText); + var success = exists(data) && exists(data.success) && data.success; + if (success) { + self.fireEvent('clear_display'); + var current = Ext.History.getToken(); + var path = self._getPath(node); + if (path && path.length && path.length > 0) delete path[path.length - 1]; + var params = self._getParams(path); + var encoded = encode(params); + if (isType(current, CCR.Types.String)) { + var currentToken = CCR.tokenize(current); + var token = currentToken.tab + '?' + encoded; + Ext.History.add(token); + } else if (isType(current, CCR.Types.Object)) { + var token = current.tab + '?' + encoded; + Ext.History.add(token); + } + } else { + Ext.MessageBox.show({ + title: 'Deletion Error', + msg: 'There was an error removing search: [' + title + '].', + icon: Ext.MessageBox.ERROR, + buttons: Ext.MessageBox.OK + }); } - } else { + }, + failure: function (response) { Ext.MessageBox.show({ title: 'Deletion Error', msg: 'There was an error removing search: [' + title + '].', @@ -1309,18 +1317,9 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { buttons: Ext.MessageBox.OK }); } - }, - failure: function (response) { - Ext.MessageBox.show({ - title: 'Deletion Error', - msg: 'There was an error removing search: [' + title + '].', - icon: Ext.MessageBox.ERROR, - buttons: Ext.MessageBox.OK - }); - } - }) + }) + } } - } ); } @@ -1743,8 +1742,8 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { if (exists(jobTab)) { var currentlyActive = tabs.activeTab && tabs.activeTab.jobId - ? tabs.activeTab.jobId === jobTab.jobId - : false; + ? tabs.activeTab.jobId === jobTab.jobId + : false; if (!currentlyActive) { tabs.setActiveTab(jobTab); @@ -1862,8 +1861,8 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { var data = results.data; var recordId = data.recordid; var jobs = data.results || [ - jobData - ]; + jobData + ]; var jobFound = false; for (var i = 0; i < jobs.length; i++) { var job = jobs[i]; @@ -1940,11 +1939,11 @@ XDMoD.Module.JobViewer = Ext.extend(XDMoD.PortalModule, { searchTerms = searchTerms || {}; var params = { 'data': JSON.stringify( - { - "text": title, - "searchterms": searchTerms, - "results": jobs - }) + { + "text": title, + "searchterms": searchTerms, + "results": jobs + }) }; if (CCR.exists(id)) params['recordid'] = id; diff --git a/html/plotly_template.html b/html/plotly_template.html index 487d129d15..d7fc7bff09 100644 --- a/html/plotly_template.html +++ b/html/plotly_template.html @@ -1,45 +1,163 @@ - + - - Plotly Template + + Plotly Template - - - + + + - - - - + - + + + - -
- +
- chartOptions.chartLayout['width'] = _width_; - chartOptions.chartLayout['height'] = _height_; + + // _ chartOptions _ is a macro that is substituted by \xd_charting\exportHighchart() + var inputChartOptions = _chartOptions_; + var globalChartOptions = _globalChartOptions_; + $(document).ready(function() { - + let data = []; + var tz = moment.tz.zone(globalChartOptions.timezone).abbr(inputChartOptions.series[0].data[0].x); + var ymin, ymax; + if (inputChartOptions.series[0].name === "Range"){ + ymin = inputChartOptions.series[1].data[0].y; + ymax = ymin; + } + else{ + ymin = inputChartOptions.series[0].data[0].y; + ymax = ymin; + } + for (let sid = 0; sid < inputChartOptions.series.length; sid++) { + if (inputChartOptions.series[sid].name === "Range") { + tz = moment.tz.zone(globalChartOptions.timezone).abbr(inputChartOptions.series[1].data[0].x); + continue; + } + let x = []; + let y = []; + let colors = inputChartOptions.colors[sid % 10]; + for(let i=0; i < inputChartOptions.series[sid].data.length; i++) { + x.push(moment.tz(inputChartOptions.series[sid].data[i].x, globalChartOptions.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); + y.push(inputChartOptions.series[sid].data[i].y); + } + + if (inputChartOptions.series[sid].name === "Median" || inputChartOptions.series[sid].name === "Minimum"){ + data.push({ + x: x, + y: y, + fill: 'tonexty', + fillcolor: '#2f7ed8', + marker: { + size: 0, + color: colors + }, + line: { + width: 2, + color: colors + }, + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + "" + inputChartOptions.series[sid].name + ": %{y}" + + "", + name: inputChartOptions.series[sid].name, chartSeries: inputChartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}); + } + else{ + data.push({ + x: x, + y: y, + marker: { + size: 0, + color: colors + }, + line: { + width: 2, + color: colors + }, + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + "" + inputChartOptions.series[sid].name + ":%{y}" + + "", + name: inputChartOptions.series[sid].name, chartSeries: inputChartOptions.series[sid], type: 'scatter', mode: 'markers+lines'}); + } + var tempyMin = Math.min(...y); + var tempyMax = Math.max(...y); + if (tempyMin < ymin) ymin = tempyMin; + if (tempyMax > ymax) ymax = tempyMax; + } + inputChartOptions.yaxis.range = [ymin, ymax]; + inputChartOptions.width = _width_; + inputChartOptions.height = _heigh_; + + + + /*var chartOptions = { + + chart: { + renderTo: 'container', + animation: false + }, + + plotOptions: { + series: { + animation: false, + turboThreshold: 0 + } + }, + + exporting: { + width: _width_, + height: _height_, + sourceWidth: _width_, + sourceHeight: _height_ + } + + };//chartOptions + + jQuery.extend(true,inputChartOptions,chartOptions); + + // Needed to provide comma as thousands separator in export + Highcharts.setOptions ({ + lang: { + thousandsSep: '\u002c' // the humble comma + } + }); + + var globalChartOptions = _globalChartOptions_; + if (globalChartOptions) { + Highcharts.setOptions({ + global: globalChartOptions + }); + }*/ + + if (inputChartOptions.series.length == 0) { + inputChartOptions.title.text = 'No data available for the critera specified'; + inputChartOptions.title.yref = 'paper'; + inputChartOptions.title.y = 0.5; + inputChartOptions.title.yanchor = 'bottom'; + inputChartOptions.title.font = { "color": "#333333", "size": "40px" } + } + Plotly.newPlot('container', data, inputChartOptions, {staticPlot: true} ); + + });//$(document).ready(... + + + diff --git a/libraries/charting.php b/libraries/charting.php index 3e4acd93cf..5a3366fe1a 100644 --- a/libraries/charting.php +++ b/libraries/charting.php @@ -50,10 +50,10 @@ function exportHighchart( $html_dir = __DIR__ . "/../html"; $template; if ($isPlotly){ - $template = file_get_contents($html_dir . "/plotly_template.html"); + $template = file_get_contents($html_dir . "/plotly_template.html"); } else{ - $template = file_get_contents($html_dir . "/highchart_template.html"); + $template = file_get_contents($html_dir . "/highchart_template.html"); } $template = str_replace('_html_dir_', $html_dir, $template); @@ -66,17 +66,16 @@ function exportHighchart( $template = str_replace('_globalChartOptions_', json_encode($globalChartOptions), $template); $template = str_replace('_chartOptions_', json_encode($chartConfig), $template); - - $svg = getSvgViaChromiumHelper($template, $effectiveWidth, $effectiveHeight, $isPlotly); + $svg = getSvgViaChromiumHelper($template, $effectiveWidth, $effectiveHeight); switch($format){ - case 'png': - return convertSvg($svg, 'png', $effectiveWidth, $effectiveHeight, $fileMetadata); - break; - case 'pdf': - return convertSvg($svg, 'pdf', round($width / 90.0 * 72.0), round($height / 90.0 * 72.0), $fileMetadata); - break; - default: - return $svg; + case 'png': + return convertSvg($svg, 'png', $effectiveWidth, $effectiveHeight, $fileMetadata); + break; + case 'pdf': + return convertSvg($svg, 'pdf', round($width / 90.0 * 72.0), round($height / 90.0 * 72.0), $fileMetadata); + break; + default: + return $svg; } } @@ -91,7 +90,7 @@ function exportHighchart( * * @throws \Exception on invalid format, command execution failure, or non zero exit status */ -function getSvgViaChromiumHelper($html, $width, $height, $isPlotly){ +function getSvgViaChromiumHelper($html, $width, $height){ // Chromium requires the file to have a .html extension // cant use datauri as it will not execute embdeeded javascript @@ -108,8 +107,7 @@ function getSvgViaChromiumHelper($html, $width, $height, $isPlotly){ $command = LIB_DIR . '/chrome-helper/chrome-helper.js' . ' --window-size=' . $width . ',' . $height . ' --path-to-chrome=' . $chromiumPath . - ' --input-file=' . $tmpHtmlFile . - ' --plotly=' . $isPlotly; + ' --input-file=' . $tmpHtmlFile; $pipes = array(); $descriptor_spec = array( @@ -128,7 +126,7 @@ function getSvgViaChromiumHelper($html, $width, $height, $isPlotly){ fclose($pipes[2]); $return_value = proc_close($process); - @unlink($tmpHtmlFile); + //@unlink($tmpHtmlFile); $chartSvg = json_decode($out); @@ -158,20 +156,17 @@ function convertSvg($svgData, $format, $width, $height, $docmeta){ $creator = addcslashes('XDMoD ' . OPEN_XDMOD_VERSION, "()\n\\"); switch($format){ - case 'png': - $exifArgs = "-Title='$title' -Author='$author' -Description='$subject' -Source='$creator'"; - break; - case 'pdf': - $exifArgs = "-Title='$title' -Author='$author' -Subject='$subject' -Creator='$creator'"; - break; - default: - return $svgData; + case 'png': + $exifArgs = "-Title='$title' -Author='$author' -Description='$subject' -Source='$creator'"; + break; + case 'pdf': + $exifArgs = "-Title='$title' -Author='$author' -Subject='$subject' -Creator='$creator'"; + break; + default: + return $svgData; } $rsvgCommand = 'rsvg-convert -w ' .$width. ' -h '.$height.' -f ' . $format; - if ($format == 'png'){ - $rsvgCommand = 'rsvg-convert -b "#FFFFFF" -w ' .$width. ' -h '.$height.' -f ' . $format; - } $exifCommand = 'exiftool ' . $exifArgs . ' -o - -'; $command = $rsvgCommand . ' | ' . $exifCommand; From a1f4d5555734bb262038597fa641cfe0d5f99967 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Thu, 15 Jun 2023 20:01:02 -0400 Subject: [PATCH 24/47] Recommit of previous commit due to some styling adjustments. Also includes some Gantt Chart and minor export updates. --- .../chrome-helper/chrome-helper.js | 21 +- .../WarehouseControllerProvider.php | 492 ++++++++---------- composer-el7.json | 8 +- composer-el7.lock | 53 +- composer-el8.json | 8 +- html/gui/js/libraries/PlotlyUtilities.js | 152 ++++++ .../modules/job_viewer/AnalyticChartPanel.js | 294 ++++++----- html/gui/js/modules/job_viewer/ChartPanel.js | 291 +++-------- html/gui/js/modules/job_viewer/ChartTab.js | 72 +-- html/gui/js/modules/job_viewer/GanttChart.js | 263 +++++----- html/index.php | 3 +- html/plotly_template.html | 177 ++----- libraries/charting.php | 23 +- 13 files changed, 827 insertions(+), 1030 deletions(-) create mode 100644 html/gui/js/libraries/PlotlyUtilities.js diff --git a/background_scripts/chrome-helper/chrome-helper.js b/background_scripts/chrome-helper/chrome-helper.js index ff304435cf..db1b8fa811 100755 --- a/background_scripts/chrome-helper/chrome-helper.js +++ b/background_scripts/chrome-helper/chrome-helper.js @@ -20,17 +20,24 @@ const args = require('yargs').argv; await page.goto('file://' + args['input-file']); - const highchartInnerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); + let svgInnerHtml; - if (highchartInnerHtml !== null){ - console.log(JSON.stringify(highchartInnerHtml)); + if (args.plotly) { + // Chart traces and axis values svg + let plotlyChart = await page.evaluate(() => document.querySelector('.user-select-none.svg-container').children[0].outerHTML); + // Chart title and axis titles svg + const plotlyLabels = await page.evaluate(() => document.querySelector('.user-select-none.svg-container').children[2].innerHTML); + + plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); + const plotlyImage = plotlyChart + "" + plotlyLabels + ""; + + svgInnerHtml = plotlyImage.replace(/
||<\/b>/gm,""); //HTML tags in titles throw xml error } - else{ - const plotlyInnerHtml = await page.evaluate(() => document.querySelector('#container').innerHTML); - console.log(JSON.stringify(plotlyInnerHtml)); + else { + svgInnerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); } - console.log(JSON.stringify(innerHtml)); + console.log(JSON.stringify(svgInnerHtml)); await browser.close(); })(); diff --git a/classes/Rest/Controllers/WarehouseControllerProvider.php b/classes/Rest/Controllers/WarehouseControllerProvider.php index 2ab9191d7c..af8d9879f0 100644 --- a/classes/Rest/Controllers/WarehouseControllerProvider.php +++ b/classes/Rest/Controllers/WarehouseControllerProvider.php @@ -97,115 +97,115 @@ class WarehouseControllerProvider extends BaseControllerProvider */ private $_supported_types = array( \DataWarehouse\Query\RawQueryTypes::ACCOUNTING => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::ACCOUNTING, - "dtype" => "infoid", - "text" => "Accounting data", - "url" => "/rest/v1.0/warehouse/search/jobs/accounting", - "documentation" => "Shows information about the job that was obtained from the resource manager. - This includes timing information such as the start and end time of the job as - well as administrative information such as the user that submitted the job and - the account that was charged.", - "type" => "keyvaluedata", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::ACCOUNTING, + "dtype" => "infoid", + "text" => "Accounting data", + "url" => "/rest/v1.0/warehouse/search/jobs/accounting", + "documentation" => "Shows information about the job that was obtained from the resource manager. + This includes timing information such as the start and end time of the job as + well as administrative information such as the user that submitted the job and + the account that was charged.", + "type" => "keyvaluedata", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::BATCH_SCRIPT => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::BATCH_SCRIPT, - "dtype" => "infoid", - "text" => "Job script", - "url" => "/rest/v1.0/warehouse/search/jobs/jobscript", - "documentation" => "Shows the job batch script that was passed to the resource manager when the - job was submitted. The script is displayed verbatim.", - "type" => "utf8-text", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::BATCH_SCRIPT, + "dtype" => "infoid", + "text" => "Job script", + "url" => "/rest/v1.0/warehouse/search/jobs/jobscript", + "documentation" => "Shows the job batch script that was passed to the resource manager when the + job was submitted. The script is displayed verbatim.", + "type" => "utf8-text", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::EXECUTABLE => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::EXECUTABLE, - "dtype" => "infoid", - "text" => "Executable information", - "url" => "/rest/v1.0/warehouse/search/jobs/executable", - "documentation" => "Shows information about the processes that were run on the compute nodes during - the job. This information includes the names of the various processes and may - contain information about the linked libraries, loaded modules and process - environment.", - "type" => "nested", - "leaf" => true), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::EXECUTABLE, + "dtype" => "infoid", + "text" => "Executable information", + "url" => "/rest/v1.0/warehouse/search/jobs/executable", + "documentation" => "Shows information about the processes that were run on the compute nodes during + the job. This information includes the names of the various processes and may + contain information about the linked libraries, loaded modules and process + environment.", + "type" => "nested", + "leaf" => true), \DataWarehouse\Query\RawQueryTypes::PEERS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::PEERS, - "dtype" => "infoid", - "text" => "Peers", - 'url' => '/rest/v1.0/warehouse/search/jobs/peers', - 'documentation' => 'Shows the list of other HPC jobs that ran concurrently using the same shared hardware resources.', - 'type' => 'ganttchart', - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::PEERS, + "dtype" => "infoid", + "text" => "Peers", + 'url' => '/rest/v1.0/warehouse/search/jobs/peers', + 'documentation' => 'Shows the list of other HPC jobs that ran concurrently using the same shared hardware resources.', + 'type' => 'ganttchart', + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::NORMALIZED_METRICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::NORMALIZED_METRICS, - "dtype" => "infoid", - "text" => "Summary metrics", - "url" => "/rest/v1.0/warehouse/search/jobs/metrics", - "documentation" => "shows a table with the performance metrics collected during - the job. These are typically average values over the job. The - label for each row has a tooltip that describes the metric. The - data are grouped into the following categories: -
    -
  • CPU Statistics: information about the cores on which the job was - assigned, such as CPU usage, FLOPs, CPI
  • -
  • File I/O Statistics: information about the data read from and - written to block devices and file system mount points.
  • -
  • Memory Statistics: information about the memory usage on the nodes - on which the job ran.
  • -
  • Network I/O Statistics: information about the data transmitted and - received over the network devices.
  • -
- ", - "type" => "metrics", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::NORMALIZED_METRICS, + "dtype" => "infoid", + "text" => "Summary metrics", + "url" => "/rest/v1.0/warehouse/search/jobs/metrics", + "documentation" => "shows a table with the performance metrics collected during + the job. These are typically average values over the job. The + label for each row has a tooltip that describes the metric. The + data are grouped into the following categories: +
    +
  • CPU Statistics: information about the cores on which the job was + assigned, such as CPU usage, FLOPs, CPI
  • +
  • File I/O Statistics: information about the data read from and + written to block devices and file system mount points.
  • +
  • Memory Statistics: information about the memory usage on the nodes + on which the job ran.
  • +
  • Network I/O Statistics: information about the data transmitted and + received over the network devices.
  • +
+ ", + "type" => "metrics", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::DETAILED_METRICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::DETAILED_METRICS, - "dtype" => "infoid", - "text" => "Detailed metrics", - "url" => "/rest/v1.0/warehouse/search/jobs/detailedmetrics", - "documentation" => "shows the data generated by the job summarization software. Please - consult the relevant job summarization software documentation for details - about these metrics.", - "type" => "detailedmetrics", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::DETAILED_METRICS, + "dtype" => "infoid", + "text" => "Detailed metrics", + "url" => "/rest/v1.0/warehouse/search/jobs/detailedmetrics", + "documentation" => "shows the data generated by the job summarization software. Please + consult the relevant job summarization software documentation for details + about these metrics.", + "type" => "detailedmetrics", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::ANALYTICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::ANALYTICS, - "dtype" => "infoid", - "text" => "Job analytics", - "url" => "/rest/v1.0/warehouse/search/jobs/analytics", - "documentation" => "Click the help icon on each plot to show the description of the analytic", - "type" => "analytics", - "hidden" => true, - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::ANALYTICS, + "dtype" => "infoid", + "text" => "Job analytics", + "url" => "/rest/v1.0/warehouse/search/jobs/analytics", + "documentation" => "Click the help icon on each plot to show the description of the analytic", + "type" => "analytics", + "hidden" => true, + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS, - "dtype" => "infoid", - "text" => "Timeseries", - "leaf" => false - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS, + "dtype" => "infoid", + "text" => "Timeseries", + "leaf" => false + ), \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE, - "dtype" => "infoid", - "text" => "VM State/Events", - "documentation" => "Show the lifecycle of a VM. Green signifies when a VM is active and red signifies when a VM is stopped.", - "url" => "/rest/v1.0/warehouse/search/cloud/vmstate", - "type" => "vmstate", - "leaf" => true - ) + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE, + "dtype" => "infoid", + "text" => "VM State/Events", + "documentation" => "Show the lifecycle of a VM. Green signifies when a VM is active and red signifies when a VM is stopped.", + "url" => "/rest/v1.0/warehouse/search/cloud/vmstate", + "type" => "vmstate", + "leaf" => true + ) ); /** @@ -1025,7 +1025,7 @@ public function getQuickFilters(Request $request, Application $app) )); } - /** + /** * Attempt to retrieve the the name for the provided dimensionId. * * @param Request $request @@ -1042,15 +1042,15 @@ public function getDimensionName(Request $request, Application $app, $dimensionI $status = $success ? 200 : 404; $payload = $success - ? array( - 'success' => $success, - 'results' => array( - 'name' => $dimensionName - )) - : array( - 'success' => false, - 'message' => "Unable to find a name for dimension: $dimensionId" - ); + ? array( + 'success' => $success, + 'results' => array( + 'name' => $dimensionName + )) + : array( + 'success' => false, + 'message' => "Unable to find a name for dimension: $dimensionId" + ); return $app->json( $payload, @@ -1077,16 +1077,16 @@ public function getDimensionValueName(Request $request, Application $app, $dimen $status = $success ? 200 : 404; $payload = $success - ? array( - 'success' => $success, - 'results' => array( - 'name' => $valueName - ) - ) - : array( - 'success' => $success, - 'message' => "Unable to find a name for dimesion: $dimensionId | value: $valueId" - ); + ? array( + 'success' => $success, + 'results' => array( + 'name' => $valueName + ) + ) + : array( + 'success' => $success, + 'message' => "Unable to find a name for dimesion: $dimensionId | value: $valueId" + ); return $app->json( $payload, @@ -1381,44 +1381,44 @@ public function processJobSearch(Request $request, Application $app, XDUser $use public function processJobSearchByAction(Request $request, Application $app, XDUser $user, $action, $realm, $jobId, $actionName) { switch ($action) { - case 'accounting': - case 'jobscript': - case 'analysis': - case 'metrics': - case 'analytics': - $results = $this->getJobData($app, $user, $realm, $jobId, $action, $actionName); - break; - case 'peers': - $start = $this->getIntParam($request, 'start', true); - $limit = $this->getIntParam($request, 'limit', true); - $results = $this->getJobPeers($app, $user, $realm, $jobId, $start, $limit); - break; - case 'executable': - $results = $this->getJobExecutable($app, $user, $realm, $jobId, $action, $actionName); - break; - case 'detailedmetrics': - $results = $this->getJobSummary($app, $user, $realm, $jobId, $action, $actionName); - break; - case 'timeseries': - $tsId = $this->getStringParam($request, 'tsid', true); - $nodeId = $this->getIntParam($request, 'nodeid', false); - $cpuId = $this->getIntParam($request, 'cpuid', false); - - $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, $tsId, $nodeId, $cpuId); - break; - case 'vmstate': - $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, null, null, null); - break; - default: - $results = $app->json( - array( - 'success' => false, - 'action' => $actionName, - 'message' => "Unable to process the requested operation. Unsupported action $action." - ), - 400 - ); - break; + case 'accounting': + case 'jobscript': + case 'analysis': + case 'metrics': + case 'analytics': + $results = $this->getJobData($app, $user, $realm, $jobId, $action, $actionName); + break; + case 'peers': + $start = $this->getIntParam($request, 'start', true); + $limit = $this->getIntParam($request, 'limit', true); + $results = $this->getJobPeers($app, $user, $realm, $jobId, $start, $limit); + break; + case 'executable': + $results = $this->getJobExecutable($app, $user, $realm, $jobId, $action, $actionName); + break; + case 'detailedmetrics': + $results = $this->getJobSummary($app, $user, $realm, $jobId, $action, $actionName); + break; + case 'timeseries': + $tsId = $this->getStringParam($request, 'tsid', true); + $nodeId = $this->getIntParam($request, 'nodeid', false); + $cpuId = $this->getIntParam($request, 'cpuid', false); + + $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, $tsId, $nodeId, $cpuId); + break; + case 'vmstate': + $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, null, null, null); + break; + default: + $results = $app->json( + array( + 'success' => false, + 'action' => $actionName, + 'message' => "Unable to process the requested operation. Unsupported action $action." + ), + 400 + ); + break; } return $results; @@ -1591,7 +1591,7 @@ private function getJobExecutable(Application $app, \XDUser $user, $realm, $jobI private function arraytostore(array $values) { - return array(array("key" => ".", "value" => "", "expanded" => true, "children" => $this->atosrecurse($values, false) )); + return array(array("key" => ".", "value" => "", "expanded" => true, "children" => $this->atosrecurse($values, false) )); } private function atosrecurse(array $values) @@ -1713,34 +1713,34 @@ private function processJobRequest( switch ($infoId) { - case "" . \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE: - $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; - $info = new $infoclass(); - - $result = array(); - foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { - $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/vmstate"; - $tsid['type'] = "timeseries"; - $tsid['dtype'] = "tsid"; - $result[] = $tsid; - } - return $app->json(array('success' => true, "results" => $result)); - break; - case "" . \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS: - $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; - $info = new $infoclass(); - - $result = array(); - foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { - $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/timeseries"; - $tsid['type'] = "timeseries"; - $tsid['dtype'] = "tsid"; - $result[] = $tsid; - } - return $app->json(array('success' => true, "results" => $result)); - break; - default: - throw new BadRequestException("Node is a leaf"); + case "" . \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE: + $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; + $info = new $infoclass(); + + $result = array(); + foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { + $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/vmstate"; + $tsid['type'] = "timeseries"; + $tsid['dtype'] = "tsid"; + $result[] = $tsid; + } + return $app->json(array('success' => true, "results" => $result)); + break; + case "" . \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS: + $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; + $info = new $infoclass(); + + $result = array(); + foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { + $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/timeseries"; + $tsid['type'] = "timeseries"; + $tsid['dtype'] = "tsid"; + $result[] = $tsid; + } + return $app->json(array('success' => true, "results" => $result)); + break; + default: + throw new BadRequestException("Node is a leaf"); } } @@ -1960,7 +1960,6 @@ private function chartImageResponse($data, $type, $settings) { // Enable plot marker only if a single point is present in the data series' plot data. // Otherwise plot the data with a line. - $markerEnabled = false; // check the series array passed in from the overall data array: @@ -1980,71 +1979,20 @@ private function chartImageResponse($data, $type, $settings) $lineWidth = 1 + $settings['scale']; $chartConfig = array( - 'colors' => array( '#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', - '#f28f43', '#77a1e5', '#c42525', '#a6c96a' - ), - 'series' => $data['series'], - 'xaxis' => array( - 'tickfont' => array( - 'size' => $axisLabelFontSize - ), - 'zerolinewidth' => $lineWidth, - 'title' => '' + 'Time (' . $data['schema']['timezone'] . ')' + '', - 'titlefont' => array( - 'size' => $axisTitleFontSize, - 'color' => '#5078a0' - ) - ), - 'yaxis' => array( - 'title' => '' + 'Time (' . $data['schema']['units'] . ')' + '', - 'titlefont' => array( - 'size' => $axisTitleFontSize, - 'color' => '#5078a0' - ), - 'zerolinewidth' => $lineWidth, - 'ticfont' => array( - 'size' => $axisLabelFontSize - ), - 'rangemode' => 'nonnegative' - ), - 'showlegend' => false, - 'plotOptions' => array( - 'line' => array( - 'lineWidth' => $lineWidth, - 'marker' => array( - 'enabled' => $markerEnabled - ) - ) - ), - 'annotations' => array( - 'text' => $data['schema']['source'] . '. Powered by XDMoD/Plotly', - 'xref' => 'paper', - 'yref' => 'paper', - 'x' => 1, - 'xanchor' => 'left', - 'y' => 0, - 'yanchor' => 'top', - 'showarrow' => false - ), - 'title' => array( - 'font' => array( - 'color' => '#444b6e', - 'size' => $mainTitleFontSize - ), - - 'text' => $settings['show_title'] ? $data['schema']['description'] : null - ) + 'width' => $settings['width'], + 'height' => $settings['height'], + 'data' => $data, + 'axisTickSize' => $axisLabelFontSize, + 'axisTitleSize' => $axisTitleFontSize, + 'lineWidth' => $lineWidth, + 'chartTitleSize' => $mainTitleFontSize ); - /*if (strpos($data['schema']['units'], '%') !== false) { - $chartConfig['yAxis']['max'] = 100.0; - }*/ - $globalConfig = array( 'timezone' => $data['schema']['timezone'] ); - $chartImage = \xd_charting\exportHighchart($chartConfig, $settings['width'], $settings['height'], $settings['scale'], $type, $globalConfig, $settings['fileMetadata']); + $chartImage = \xd_charting\exportHighchart($chartConfig, $settings['width'], $settings['height'], $settings['scale'], $type, $globalConfig, $settings['fileMetadata'], true); $chartFilename = $settings['fileMetadata']['title'] . '.' . $type; $mimeOverride = $type == 'svg' ? 'image/svg+xml' : null; @@ -2068,30 +2016,30 @@ private function getJobTimeSeriesData(Application $app, Request $request, \XDUse } switch ($format) { - case 'png': - case 'pdf': - case 'svg': - $exportConfig = array( - 'width' => $this->getIntParam($request, 'width', false, 916), - 'height' => $this->getIntParam($request, 'height', false, 484), - 'scale' => floatval($this->getStringParam($request, 'scale', false, '1')), - 'font_size' => $this->getIntParam($request, 'font_size', false, 3), - 'show_title' => $this->getStringParam($request, 'show_title', false, 'y') === 'y' ? true : false, - 'fileMetadata' => array( - 'author' => $user->getFormalName(), - 'subject' => 'Timeseries data for ' . $results['schema']['source'], - 'title' => $results['schema']['description'] - ) - ); - $response = $this->chartImageResponse($results, $format, $exportConfig); - break; - case 'csv': - $response = $this->chartDataResponse($results); - break; - case 'json': - default: - $response = $app->json(array("success" => true, "data" => array($results))); - break; + case 'png': + case 'pdf': + case 'svg': + $exportConfig = array( + 'width' => $this->getIntParam($request, 'width', false, 916), + 'height' => $this->getIntParam($request, 'height', false, 484), + 'scale' => floatval($this->getStringParam($request, 'scale', false, '1')), + 'font_size' => $this->getIntParam($request, 'font_size', false, 3), + 'show_title' => $this->getStringParam($request, 'show_title', false, 'y') === 'y' ? true : false, + 'fileMetadata' => array( + 'author' => $user->getFormalName(), + 'subject' => 'Timeseries data for ' . $results['schema']['source'], + 'title' => $results['schema']['description'] + ) + ); + $response = $this->chartImageResponse($results, $format, $exportConfig); + break; + case 'csv': + $response = $this->chartDataResponse($results); + break; + case 'json': + default: + $response = $app->json(array("success" => true, "data" => array($results))); + break; } return $response; diff --git a/composer-el7.json b/composer-el7.json index a76f8af785..ddb1a3f226 100644 --- a/composer-el7.json +++ b/composer-el7.json @@ -24,7 +24,7 @@ "ubccr/simplesamlphp-module-authoidcoauth2": "^1.1", "phpoffice/phpword": "^0.17.0", "monolog/monolog": "^1.25", - "plotly/plotly": "^1.57.1", + "plotly/plotly": "^2.24.2", "kassner/log-parser": "~1.5", "geoip2/geoip2": "~2.0", "ua-parser/uap-php": "^3.9" @@ -211,13 +211,13 @@ "package": { "name": "plotly/plotly", "type": "vanilla-plugin", - "version": "1.57.1", + "version": "2.24.2", "license": "MIT", "homepage": "https://github.com/plotly/plotly.js", "dist": { - "url": "https://cdn.plot.ly/plotly-1.57.1.min.js", + "url": "https://cdn.plot.ly/plotly-2.24.2.min.js", "type": "file", - "shasum": "ccf99902ea104599c3b5b4811e83b8ee8118aad3" + "shasum": "18d8802e7767617cfc23b6008a56581a567c1015" }, "require": { "composer/installers": "~1.0" diff --git a/composer-el7.lock b/composer-el7.lock index 1d7dba26e2..b720348849 100644 --- a/composer-el7.lock +++ b/composer-el7.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "17fedb677e9a0759ac6e7dd79ad5ce89", + "content-hash": "d302793db10b0b49c5088813f41343e6", "packages": [ { "name": "carlo/jquery-base64-file", @@ -28,16 +28,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.3.2", + "version": "1.3.6", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "fd5dd441932a7e10ca6e5b490e272d34c8430640" + "reference": "90d087e988ff194065333d16bc5cf649872d9cdb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/fd5dd441932a7e10ca6e5b490e272d34c8430640", - "reference": "fd5dd441932a7e10ca6e5b490e272d34c8430640", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/90d087e988ff194065333d16bc5cf649872d9cdb", + "reference": "90d087e988ff194065333d16bc5cf649872d9cdb", "shasum": "" }, "require": { @@ -81,11 +81,6 @@ "ssl", "tls" ], - "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.3.2" - }, "funding": [ { "url": "https://packagist.com", @@ -100,7 +95,7 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:56:16+00:00" + "time": "2023-06-06T12:02:59+00:00" }, { "name": "composer/installers", @@ -492,16 +487,16 @@ }, { "name": "gettext/languages", - "version": "2.9.0", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/php-gettext/Languages.git", - "reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa" + "reference": "4d61d67fe83a2ad85959fe6133d6d9ba7dddd1ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-gettext/Languages/zipball/ed56dd2c7f4024cc953ed180d25f02f2640e3ffa", - "reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa", + "url": "https://api.github.com/repos/php-gettext/Languages/zipball/4d61d67fe83a2ad85959fe6133d6d9ba7dddd1ab", + "reference": "4d61d67fe83a2ad85959fe6133d6d9ba7dddd1ab", "shasum": "" }, "require": { @@ -548,10 +543,6 @@ "translations", "unicode" ], - "support": { - "issues": "https://github.com/php-gettext/Languages/issues", - "source": "https://github.com/php-gettext/Languages/tree/2.9.0" - }, "funding": [ { "url": "https://paypal.me/mlocati", @@ -562,7 +553,7 @@ "type": "github" } ], - "time": "2021-11-11T17:30:39+00:00" + "time": "2022-10-18T15:00:10+00:00" }, { "name": "google/recaptcha", @@ -616,16 +607,16 @@ }, { "name": "greenlion/php-sql-parser", - "version": "v4.5.0", + "version": "v4.6.0", "source": { "type": "git", "url": "https://github.com/greenlion/PHP-SQL-Parser.git", - "reference": "a5d5c292d97271c95140192e6f0e962916e39b50" + "reference": "f0e4645eb1612f0a295e3d35bda4c7740ae8c366" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/greenlion/PHP-SQL-Parser/zipball/a5d5c292d97271c95140192e6f0e962916e39b50", - "reference": "a5d5c292d97271c95140192e6f0e962916e39b50", + "url": "https://api.github.com/repos/greenlion/PHP-SQL-Parser/zipball/f0e4645eb1612f0a295e3d35bda4c7740ae8c366", + "reference": "f0e4645eb1612f0a295e3d35bda4c7740ae8c366", "shasum": "" }, "require": { @@ -668,11 +659,7 @@ "parser", "sql" ], - "support": { - "issues": "https://github.com/greenlion/PHP-SQL-Parser/issues", - "source": "https://github.com/greenlion/PHP-SQL-Parser" - }, - "time": "2022-02-01T09:26:56+00:00" + "time": "2023-03-09T20:54:23+00:00" }, { "name": "highsoft/highcharts", @@ -1587,11 +1574,11 @@ }, { "name": "plotly/plotly", - "version": "1.57.1", + "version": "2.24.2", "dist": { "type": "file", - "url": "https://cdn.plot.ly/plotly-1.57.1.min.js", - "shasum": "ccf99902ea104599c3b5b4811e83b8ee8118aad3" + "url": "https://cdn.plot.ly/plotly-2.24.2.min.js", + "shasum": "18d8802e7767617cfc23b6008a56581a567c1015" }, "require": { "composer/installers": "~1.0" @@ -4531,5 +4518,5 @@ "platform-overrides": { "php": "5.4" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "1.1.0" } diff --git a/composer-el8.json b/composer-el8.json index ec7c3debd6..07813c0cca 100644 --- a/composer-el8.json +++ b/composer-el8.json @@ -25,7 +25,7 @@ "ubccr/simplesamlphp-module-authoidcoauth2": "^1.1", "phpoffice/phpword": "^0.17.0", "monolog/monolog": "^1.25", - "plotly/plotly": "^1.57.1", + "plotly/plotly": "^2.24.2", "kassner/log-parser": "~1.5", "geoip2/geoip2": "~2.0", "ua-parser/uap-php": "^3.9", @@ -213,13 +213,13 @@ "package": { "name": "plotly/plotly", "type": "vanilla-plugin", - "version": "1.57.1", + "version": "2.24.2", "license": "MIT", "homepage": "https://github.com/plotly/plotly.js", "dist": { - "url": "https://cdn.plot.ly/plotly-1.57.1.min.js", + "url": "https://cdn.plot.ly/plotly-2.24.2.min.js", "type": "file", - "shasum": "ccf99902ea104599c3b5b4811e83b8ee8118aad3" + "shasum": "18d8802e7767617cfc23b6008a56581a567c1015" }, "require": { "composer/installers": "~1.0" diff --git a/html/gui/js/libraries/PlotlyUtilities.js b/html/gui/js/libraries/PlotlyUtilities.js new file mode 100644 index 0000000000..07199bd32f --- /dev/null +++ b/html/gui/js/libraries/PlotlyUtilities.js @@ -0,0 +1,152 @@ +/* generateChartOptions - Generates data array and layout dict for Plotly Chart + * ** Currently assumes that data is in format of a record returned in the JobViewer ** + * + * @param{dict} Record containing chart data + * + */ +function generateChartOptions(record){ + let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', + '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + let data = []; + let isEnvelope = false; + let tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); + let ymin, ymax; + ymin = record.data.series[0].data[0].y; + ymax = ymin; + + for (let sid = 0; sid < record.data.series.length; sid++) { + if (record.data.series[sid].name === 'Range') { + isEnvelope = true; + ymin = record.data.series[1].data[0].y; + ymax = ymin; + tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); + continue; + } + let x = []; + let y = []; + let qtip = []; + let color = colors[sid % 10]; + + for(let i = 0; i < record.data.series[sid].data.length; i++) { + x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS')); + y.push(record.data.series[sid].data[i].y); + qtip.push(record.data.series[sid].data[i].qtip); + } + + let trace = { + x: x, + y: y, + marker: { + size: 0.1, + color: color + }, + line: { + width: 2, + color: color + }, + text: qtip, + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y:}" + + "", + name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines' + }; + + if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ + trace['fill'] = 'tonexty'; + trace['fillcolor'] = '#5EA0E2'; + } + + if (isEnvelope){ + trace.hovertemplate = "[%{text}] " + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y:}" + + ""; + } + + if (record.data.series[sid].data.length === 1){ + trace.marker.size = 20; + trace.mode = 'markers'; + delete trace.line; + } + + data.push(trace); + const tempMin = Math.min(...y); + const tempMax = Math.max(...y); + if (tempMin < ymin) ymin = tempMin; + if (tempMax > ymax) ymax = tempMax; + } + + let layout = { + hoverlabel: { + bgcolor: '#ffffff' + }, + xaxis: { + title: '' + 'Time (' + record.data.schema.timezone + ')' + '', + titlefont: { + family: 'Open-Sans, verdana, arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + ticks: 'outside', + tickcolor: '#c0cfe0', + linecolor: '#c0cfe0', + automargin: true, + showgrid: false, + }, + yaxis: { + title: '' + record.data.schema.units + '', + titlefont: { + family: 'Open-Sans, verdana, arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + range: [0, ymax + (ymax * 0.2)], + showline: false, + zeroline: false, + gridcolor: '#d8d8d8', + automargin: true, + ticks: 'outside', + tickcolor: '#ffffff', + seperatethousands: true + }, + title: { + text: record.data.schema.description, + font: { + family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif', + color: '#444b6e', + size: 16 + }, + }, + annotations: [{ + text: record.data.schema.source + '. Powered by XDMoD/Plotly', + font: { + color: '#909090', + size: 10, + family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' + }, + xref: 'paper', + yref: 'paper', + xanchor: 'right', + yanchor: 'bottom', + x: 1, + y: 0, + yshift: -80, + showarrow: false + }], + hovermode: 'closest', + showlegend: false, + margin: { + t: 50 + } + }; + + let ret = { + chartData: data, + chartLayout: layout + }; + + return ret; +} + diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 87f0eb4151..f2d7bbe8d1 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -17,36 +17,30 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { _DEFAULT_CONFIG: { delimiter: ':', colorSteps: [ - { - value: .25, - color: '#FF0000', - bg_color: 'rgb(255,102,102)' - }, - { - value: .50, - color: '#FFB336', - bg_color: 'rgb(255,255,156)' - - }, - { - value: .75, - color: '#DDDF00', - bg_color: 'rgb(255,255,102)' - }, - { - value: 1, - color: '#50B432', - bg_color: 'rgb(182,255,152)' - } + { + value: .25, + color: '#ff0000', + bg_color: 'rgb(255,102,102)' + }, + { + value: .50, + color: '#ffb336', + bg_color: 'rgb(255,255,156)' + + }, + { + value: .75, + color: '#dddf00', + bg_color: 'rgb(255,255,102)' + }, + { + value: 1, + color: '#50b432', + bg_color: 'rgb(182,255,152)' + } ], - - + layout: { - hoverlabel: { - bgcolor: 'white' - }, - paper_bgcolor: 'white', - plot_bgcolor: 'white', height: 65, xaxis: { showticklabels: false, @@ -56,68 +50,60 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { tick0: 0.0, dtick: 0.2, ticklen: 2, - tickcolor: 'white', + tickcolor: '#ffffff', gridcolor: '#c0c0c0', - linecolor: 'white', + linecolor: '#ffffff', zeroline : false, showgrid: true, - zerolinecolor: 'black', + zerolinecolor: '#000000', showline: false, zerolinewidth: 0, - fixedrange: true + fixedrange: true }, yaxis: { showticklabels: false, color: '#606060', showgrid : false, gridcolor: '#c0c0c0', - linecolor: 'white', + linecolor: '#ffffff', zeroline: false, - zerolinecolor: 'white', + zerolinecolor: '#ffffff', showline: false, rangemode: 'tozero', zerolinewidth: 0, - fixedrange: true + fixedrange: true }, hovermode: false, - shapes: [], + shapes: [], + images: [], + annotations: [], showlegend: false, margin: { - t: 12.5, - l: 7.5, - r: 7.5, - b: 12.5, + t: 10, + l: 9, + r: 13, + b: 10, pad: 0 } }, - traces: [], - config: { - displayModeBar: false, - }, - bgColors: [] + config: { + displayModeBar: false, + staticPlot: true }, + }, // The instance of Highcharts used as this components primary display. chart: null, - // private member that stores the error message object errorMsg: null, - /** - * This components 'constructor'. - */ + /** + * This components 'constructor'. + */ initComponent: function() { - - this.colorSteps = Ext.apply( - this.colorSteps || [], - this._DEFAULT_CONFIG.colorSteps - ); - - XDMoD.Module.JobViewer.AnalyticChartPanel.superclass.initComponent.call( - this - ); - + this.colorSteps = Ext.apply(this.colorSteps || [], this._DEFAULT_CONFIG.colorSteps); + XDMoD.Module.JobViewer.AnalyticChartPanel.superclass.initComponent.call(this); }, // initComponent listeners: { @@ -128,8 +114,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * a visible element to hook into are handled here. */ render: function() { - this.chart = true; - Plotly.newPlot(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + this.chart = Plotly.newPlot(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // render /** @@ -141,7 +126,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ update_data: function(data) { this._updateData(data); - //Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // update_data /** @@ -150,18 +134,17 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ reset: function() { if (this.chart) { - this._DEFAULT_CONFIG.traces = []; - Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + Plotly.react(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); } }, // reset beforedestroy: function () { if (this.chart) { - Plotly.purge(this.id); + Plotly.purge(this.id); + this.chart = null; } }, - /** * Attempt to resize this components HighCharts instance such that it * falls with in the new adjWidth and adjHeight. @@ -174,19 +157,57 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (this.chart) { - Plotly.relayout(this.id, {width: adjWidth}); - var elements = document.querySelectorAll('.bglayer'); - if (elements){ - for (var i=0; i < elements.length; i++){ - if (elements[i].firstChild){ - elements[i].firstChild.style.fill = this._DEFAULT_CONFIG.bgColors[i]; - } - } + const container = document.querySelector('#'+this.id); + const bgcolor = container._fullLayout.plot_bgcolor; + const annotation = structuredClone(container._fullLayout.annotations); + const image = structuredClone(container._fullLayout.images); + Plotly.relayout(this.id, {width: adjWidth}); + if (annotation.length > 0){ + Plotly.relayout(this.id, {images: image}); + Plotly.relayout(this.id, {annotations: annotation}); + let update = { + xaxis: { + showticklabels: false, + tickcolor: '#ffffff', + gidcolor: '#ffffff', + linecolor: '#ffffff', + zeroline : false, + showgrid: false, + zerolinecolor: '#000000', + showline: false, + zerolinewidth: 0 + } + }; + Plotly.relayout(this.id, update); + Plotly.relayout(this.id, {plot_bgcolor: bgcolor}); } - if (this.errorMsg) { - this.updateErrorMessage(this.errorMsg.text.textStr); + else { + Plotly.relayout(this.id, {images: []}); + Plotly.relayout(this.id, {annotations: []}); + let update = { + xaxis: { + showticklabels: false, + range: [0,1], + color: '#606060', + ticks: 'inside', + tick0: 0.0, + dtick: 0.2, + ticklen: 2, + tickcolor: '#ffffff', + gridcolor: '#c0c0c0', + linecolor: '#ffffff', + zeroline : false, + showgrid: true, + zerolinecolor: '#000000', + showline: false, + zerolinewidth: 0, + fixedrange: true + } + }; + Plotly.relayout(this.id, update); + Plotly.relayout(this.id, {plot_bgcolor: bgcolor}); } - } + } } // resize }, // listeners @@ -199,43 +220,45 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ updateErrorMessage: function (errorStr) { if (this.errorMsg) { - this.errorMsg.text.destroy(); - this.errorMsg.image.destroy(); this.errorMsg = null; } if (errorStr) { - this._DEFAULT_CONFIG.layout['images'] = [ - { - "source": '/gui/images/about_16.png', - "align": "left", - "xref": "paper", - "yref": "paper", - "sizex": 0.4, - "sizey": 0.4, - "x": 0, - "y": 1.2 - } - ] - this._DEFAULT_CONFIG.layout['annotations'] = [ - { - "text": '' + errorStr + '', - "align": "left", - "xref": "paper", - "yref": "paper", - "font":{ - "size": 11, - }, - "x" : 0.05, - "y" : 1.2, - "showarrow": false - } - ] - this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = false; + errorStr = errorStr, 60; + this.errorMsg = errorStr; + let errorImage = [ + { + source: '/gui/images/about_16.png', + align: 'left', + xref: 'paper', + yref: 'paper', + sizex: 0.4, + sizey: 0.4, + x: 0, + y: 1.2 + } + ]; + this._DEFAULT_CONFIG.layout.images = errorImage; + let errorText = [ + { + text: '' + errorStr + '', + align: 'left', + xref: 'paper', + yref: 'paper', + font:{ + size: 11, + }, + x : 0.05, + y : 1.2, + showarrow: false + } + ]; + this._DEFAULT_CONFIG.layout.annotations = errorText; + this._DEFAULT_CONFIG.layout.xaxis.showgrid = false; } else { - this._DEFAULT_CONFIG.layout['images'] = []; - this._DEFAULT_CONFIG.layout['annotations'] = []; - this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = true; + this._DEFAULT_CONFIG.layout.images = []; + this._DEFAULT_CONFIG.layout.annotations = []; + this._DEFAULT_CONFIG.layout.xaxis.showgrid = true; } }, @@ -247,37 +270,33 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _updateData: function(data) { - this._DEFAULT_CONFIG.traces = []; - - this._DEFAULT_CONFIG.layout['plot_bgcolor'] = 'white'; - var trace = {}; - if (data.error == '') { + let trace = {}; + if (data.error == '') { + let chartColor = this._getDataColor(data.value); + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; - var chartColor = this._getDataColor(data.value); - this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; - this._DEFAULT_CONFIG.bgColors.push(chartColor.bg_color); - trace = { - x: [data.value], - name: data.name ? data.name : '', - width: [0.5], - marker:{ - color: chartColor.color, - line:{ - color: 'white', - width: 1 - } - }, - type: 'bar', - orientation: 'h', - }; + x: [data.value], + name: data.name ? data.name : '', + width: [0.5], + marker:{ + color: chartColor.color, + line:{ + color: '#ffffff', + width: 1 + } + }, + type: 'bar', + orientation: 'h', + }; + } + else { + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = '#ffffff'; } - this._DEFAULT_CONFIG.traces.push(trace); this.updateErrorMessage(data.error); this._updateTitle(data); this.ownerCt.doLayout(false, true); Plotly.react(this.id, [trace], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); - }, // _updateData /** @@ -299,7 +318,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _getDataColor: function(data) { - var ret = {}; var color = null; if (typeof data === 'number') { var steps = this.colorSteps; @@ -307,10 +325,10 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { for ( var i = 0; i < count; i++) { var step = steps[i]; if (data <= step.value) { - ret = { - color: step.color, - bg_color: step.bg_color - }; + let ret = { + color: step.color, + bg_color: step.bg_color + }; return ret; } } diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 7b127135ad..a570965bb9 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -7,18 +7,9 @@ Ext.namespace('XDMoD', 'XDMoD.Module', 'XDMoD.Module.JobViewer'); */ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { - // The default chart config options. - _DEFAULT_CONFIG: { - chartPrefix: 'CreateChartPanel', - }, - // The chart instance. chart: null, - chartWidth: null, - - chartHeight: null, - /** * The component 'constructor'. */ @@ -35,9 +26,9 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { // ADD: The custom events that we're listening for. this.addEvents( - 'load_record', - 'record_loaded' - ); + 'load_record', + 'record_loaded' + ); var self = this; @@ -85,25 +76,23 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } }, // render - /** - * - * @param panel - * @param adjWidth - * @param adjHeight - * @param rawWidth - * @param rawHeight - */ + /** + * + * @param panel + * @param adjWidth + * @param adjHeight + * @param rawWidth + * @param rawHeight + */ resize: function(panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (panel.chart) { - this.chartWidth = adjWidth; - this.chartWidth = adjHeight; Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); } }, // resize beforedestroy: function () { if (this.chart) { - Plotly.purge(this.id); + Plotly.purge(this.id); this.chart = false; } }, @@ -114,49 +103,45 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { print_clicked: function () { if (this.chart) { - this.chart.print(); + let chartDiv = document.querySelector('#' + this.id); + chartDiv = chartDiv.firstChild.firstChild; // parent div of the plotly SVGs + + // Make deep copy + const tmpWidth = structuredClone(chartDiv.clientWidth); + const tmpHeight = structuredClone(chartDiv.clientHeight); + + // Resize to 'medium' export width and height -- Currently placeholder width and height + Plotly.relayout(this.id, {width: 916, height: 484}); + + // Combine Plotly svg elements similar to export + let plotlyChart = chartDiv.children[0].outerHTML; + const plotlyLabels = chartDiv.children[2].innerHTML; + + plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); + let svg = plotlyChart + plotlyLabels + '' + + let printWindow = window.open(); + printWindow.document.write(' Printing '); + printWindow.document.write(svg); + printWindow.print(); + printWindow.close(); + + Plotly.relayout(this.id, {width: tmpWidth, height: tmpHeight}); } }, - /** - * - * @param panel - * @param record - */ + /** + * + * @param panel + * @param record + */ load_record: function(panel, record) { var self = this; - var chartClickHandler = function(event) { - var userOptions = this.series.userOptions; - if (!userOptions || !userOptions.dtype) { - return; - } - var drilldown; - /* - * The drilldown data are stored on each point for envelope - * plots and for the series for simple plots. - */ - if (userOptions.dtype == 'index') { - drilldown = { - dtype: userOptions.index, - value: event.point.options[userOptions.index] - }; - } else { - drilldown = { - dtype: userOptions.dtype, - value: userOptions[userOptions.dtype] - }; - } - var path = self.path.concat([drilldown]); - var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); - Ext.History.add(token); - }; - if (record !== null && record !== undefined) { this.dataurl = record.store.proxy.url; this.displayTimezone = record.data.schema.timezone; - if (record.data.schema.help) { panel.helptext.documentation = record.data.schema.help; this.jobTab.fireEvent("display_help", panel.helptext); @@ -164,180 +149,48 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } if (record) { - let colorChoices = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', - '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - let data = []; - var isEnvelope = false; - var hasSingleDataPoint = false; - var tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); - var ymin, ymax; - ymin = record.data.series[0].data[0].y; - ymax = ymin; - for (let sid = 0; sid < record.data.series.length; sid++) { - if (record.data.series[sid].name === "Range") { - isEnvelope = true; - ymin = record.data.series[1].data[0].y; - ymax = ymin; - tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); - continue; - } - let x = []; - let y = []; - let qtip = []; - let colors = colorChoices[sid % 10]; - for(let i=0; i < record.data.series[sid].data.length; i++) { - if (record.data.series[sid].data.length == 1){ - hasSingleDataPoint = true; - } - x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); - y.push(record.data.series[sid].data[i].y); - qtip.push(record.data.series[sid].data[i].qtip); - } - - if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ - data.push({ - x: x, - y: y, - fill: 'tonexty', - fillcolor: '#5EA0E2', - marker: { - size: 0.1, - color: colors - }, - line: { - width: 2, - color: colors - }, - text: qtip, - hovertemplate: "[%{text}] " + - "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ": %{y: .f}" + - "", - name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}); - } - else{ - var trace = { - x: x, - y: y, - marker: { - size: 0.1, - color: colors - }, - line: { - width: 2, - color: colors - }, - text: qtip, - hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ": %{y: .f}" + - "", - name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}; - - if (isEnvelope){ - trace.hovertemplate = "[%{text}] " + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ": %{y: .f}" + - ""; - } - if (hasSingleDataPoint){ - trace.marker.size = 20; - trace.mode = 'markers'; - delete trace.line; - } - data.push(trace); - } - var tempyMin = Math.min(...y); - var tempyMax = Math.max(...y); - if (tempyMin < ymin) ymin = tempyMin; - if (tempyMax > ymax) ymax = tempyMax; - } - + let chartOptions = generateChartOptions(record); panel.getEl().unmask(); - let layout = { - hoverlabel: { - bgcolor: 'white' - }, - xaxis: { - title: 'Time (' + record.data.schema.timezone + ')', - titlefont: { - family: 'Arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - ticks: 'outside', - ticklen: 10, - tickcolor: '#c0cfe0', - linecolor: '#c0cfe0', - automargin: true, - showgrid: false - }, - yaxis: { - title: '' + record.data.schema.units + '', - titlefont: { - family: 'Arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - range: [0, ymax + (ymax * 0.2)], - rangemode: 'nonnegative', - gridcolor: 'lightgray', - automargin: true, - linecolor: '#c0cfe0' - }, - title: { - text: record.data.schema.description, - font: { - color: '#444b6e', - size: 16 - } - }, - hovermode: 'closest', - showlegend: false, - margin: { - t: 50 - } - }; + if (panel.chart) { - Plotly.react(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'} ); + Plotly.react(this.id, chartOptions.chartData, chartOptions.chartLayout, {displayModeBar: false, doubleClick: 'reset'} ); this.chart = true; } else { - Plotly.newPlot(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'} ); + Plotly.newPlot(this.id, chartOptions.chartData, chartOptions.chartLayout, {displayModeBar: false, doubleClick: 'reset'} ); this.chart = true; } - - if (this.chart) { + if (panel.chart) { panel.chart = document.getElementById(this.id); panel.chart.on('plotly_click', function(data, event){ - var userOptions = data.points[0].data.chartSeries + const userOptions = data.points[0].data.chartSeries; if (!userOptions || !userOptions.dtype) { return; } - var drilldown; + let drilldown; /* * The drilldown data are stored on each point for envelope * plots and for the series for simple plots. */ if (userOptions.dtype == 'index') { - var nodeidIndex = data.points[0].data.chartSeries.data.findIndex(({y}) => y === data.points[0].y); + const nodeidIndex = data.points[0].pointIndex; if (nodeidIndex === -1) return; drilldown = { dtype: userOptions.index, value: userOptions.data[nodeidIndex].nodeid }; - } else { + } + else { drilldown = { dtype: userOptions.dtype, value: userOptions[userOptions.dtype] }; } - var path = self.path.concat([drilldown]); - var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); + const path = self.path.concat([drilldown]); + const token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); Ext.History.add(token); - }); - } - } + }); + } + } if (!record) { panel.getEl().mask('Loading...'); @@ -348,12 +201,12 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, // listeners - /** - * - * @param series - * @returns {*} - * @private - */ + /** + * + * @param series + * @returns {*} + * @private + */ _findDtype: function(series) { if (!CCR.isType(series, CCR.Types.Array)) return null; @@ -369,16 +222,16 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { return result; }, - /** - * Helper function that adds an explicit 'load' listener to the provided - * this.series.userOptions;data store. This listener will ensure that each time the store receives - * a load event, if there is at least one record, then this components - * 'load_record' event will be fired with a reference to the first record - * returned. - * - * @param store to be listened to. - * @private - */ + /** + * Helper function that adds an explicit 'load' listener to the provided + * this.series.userOptions;data store. This listener will ensure that each time the store receives + * a load event, if there is at least one record, then this components + * 'load_record' event will be fired with a reference to the first record + * returned. + * + * @param store to be listened to. + * @private + */ _addStoreListeners: function(store) { if ( typeof store === 'object') { var self = this; diff --git a/html/gui/js/modules/job_viewer/ChartTab.js b/html/gui/js/modules/job_viewer/ChartTab.js index 46d898d13d..48bc865e7b 100644 --- a/html/gui/js/modules/job_viewer/ChartTab.js +++ b/html/gui/js/modules/job_viewer/ChartTab.js @@ -37,68 +37,7 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { var self = this; var createChart = function () { - var defaultChartSettings = { - chart: { - renderTo: self.id + '_hc' - }, - colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a'], - title: { - style: { - color: '#444b6e', - fontSize: '16px' - }, - text: '' - }, - loading: { - style: { - opacity: 0.7 - } - }, - yAxis: { - title: { - style: { - fontWeight: 'bold', - color: '#5078a0' - } - } - }, - legend: { - enabled: false - }, - exporting: { - enabled: false - }, - tooltip: { - pointFormat: ' {series.name}: {point.low:%A, %b %e, %H:%M:%S} - {point.high:%A, %b %e, %H:%M:%S}
', - dateTimeLabelFormats: { - millisecond: '%A, %b %e, %H:%M:%S.%L %T', - second: '%A, %b %e, %H:%M:%S %T', - minute: '%A, %b %e, %H:%M:%S %T', - hour: '%A, %b %e, %H:%M:%S %T' - } - }, - plotOptions: { - line: { - marker: { - enabled: false - } - }, - columnrange: { - minPointLength: 3, - animation: false, - dataLabels: { - enabled: false - } - }, - series: { - allowPointSelect: false, - animation: false - } - } - }; - - var chartOptions = jQuery.extend(true, {}, defaultChartSettings, self.chartSettings); - + this.chart = Plotly.newPlot(this.id, [], [], {displayModeBar: false, doubleClick: 'reset'}); var storeParams; if (self.panelSettings.pageSize) { storeParams = { @@ -148,9 +87,9 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { xtype: 'container', id: this.id + '_hc', listeners: { - resize: function () { - if (self.chart) { - self.chart.reflow(); + resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { + if (this.chart) { + Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); } }, render: createChart @@ -180,7 +119,8 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { beforedestroy: function () { if (this.chart) { Plotly.purge(this.id); + this.chart = false; } - } + }, } }); diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index a098ba20f0..0e06e6d2b9 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -3,20 +3,6 @@ Ext.namespace('XDMoD', 'XDMoD.Module', 'XDMoD.Module.JobViewer'); XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, { initComponent: function () { - this.chartSettings = { - chart: { - type: 'columnrange', - grouping: false, - inverted: true - }, - yAxis: { - type: 'datetime', - minTickInterval: 1000, - title: { - text: 'Time (' + self.displayTimezone + ')' - } - } - }; this.panelSettings = { pageSize: 11, @@ -26,140 +12,151 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, fields: ['series', 'schema', 'categories'] } }; - + XDMoD.Module.JobViewer.GanttChart.superclass.initComponent.call(this, arguments); - + this.addListener('updateChart', function (store) { - if (store.getCount() < 1) { + if (store.getCount() < 1) { return; - } + } - var record = store.getAt(0); - var i; - var data = []; - var categories = []; - var count = 0; - var rect = []; - var yvals = [0]; - let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - for (i = 0; i < record.data.series.length; i++) { - var j = 0; - for (j = 0; j < record.data.series[i].data.length; j++){ - low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - categories.push(record.data.categories[count]); - var runtime = []; - let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; - var tooltip = [template]; - var ticks = [count]; - var start_time = record.data.series[i].data[j].low; - // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. - // Points on the scatter plot are created every min to create better coverage for tooltip information - while (start_time < record.data.series[i].data[j].high){ - ticks.push(count); - tooltip.push(template); - runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); - start_time += (60 * 1000); - } - runtime.push(high_data); - - rect.push({ - x0: low_data, - x1: high_data, - y0: count-0.25, - y1: count+0.25, - type: 'rect', - xref: 'x', - yref: 'y', - fillcolor: colors[i % 10], - //color: colors[i % 10] - }); + var record = store.getAt(0); + let data = []; + let categories = []; + let count = 0; + let rect = []; + let yvals = []; + let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + for (let i = 0; i < record.data.series.length; i++) { + for (let j = 0; j < record.data.series[i].data.length; j++){ + let low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + let high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + categories.push(record.data.categories[count]); + let runtime = []; + let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; + let tooltip = [template]; + let ticks = [count]; + let start_time = record.data.series[i].data[j].low; + // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. + // Points on the scatter plot are created every min to create better coverage for tooltip information + while (start_time < record.data.series[i].data[j].high){ + ticks.push(count); + tooltip.push(template); + runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); + start_time += (60 * 1000); + } + runtime.push(high_data); - - var info = {} + rect.push({ + x0: low_data, + x1: high_data, + y0: count-0.175, + y1: count+0.175, + type: 'rect', + xref: 'x', + yref: 'y', + fillcolor: colors[i % 10], + }); - if (i > 0){ - info = { - realm: record.data.series[i].data[j].ref.realm, - recordid: store.baseParams.recordid, - jobref: record.data.series[i].data[j].ref.jobid, - infoid: 3 - }; - } - - peer = { - x: runtime, - y: ticks, + let info = {}; + if (i > 0){ + info = { + realm: record.data.series[i].data[j].ref.realm, + recordid: store.baseParams.recordid, + jobref: record.data.series[i].data[j].ref.jobid, + infoid: 3 + }; + } + let peer = { + x: runtime, + y: ticks, type: 'scatter', - - marker:{ - color: 'rgb(255,255,255)' - }, - orientation: 'h', - hovertemplate: record.data.categories[count] + '
'+ - "" + '%{text} ', - text: tooltip, - chartSeries: info + marker:{ + color: 'rgb(255,255,255)', + size: 20 + }, + orientation: 'h', + hovertemplate: record.data.categories[count] + '
'+ + "" + '%{text} ', + text: tooltip, + chartSeries: info }; - data.push(peer); - count++; - yvals.push(count); + data.push(peer); + yvals.push(count); + count++; + } + } - - } - let layout = { - hoverlabel: { - bgcolor: 'white' - }, - xaxis: { - title: 'Time (' + record.data.schema.timezone + ')', - titlefont: { - family: 'Arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - ticks: 'outside', - tickcolor: '#c0cfe0', - linecolor: '#c0cfe0', - type: 'date', - range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] + let layout = { + hoverlabel: { + bgcolor: '#ffffff' + }, + xaxis: { + title: 'Time (' + record.data.schema.timezone + ')', + titlefont: { + family: 'Arial, sans-serif', + size: 12, + color: '#5078a0' }, - yaxis: { - autorange: 'reversed', - ticktext: categories, - zeroline: false, - tickvals: yvals - }, - title: { - text: record.data.schema.description, - font: { - color: '#444b6e', - size: 16 - } - }, - hovermode: 'closest', - showlegend: false, - margin: { - l: 175, - b: 150 - + color: '#606060', + zeroline: false, + gridcolor: '#d8d8d8', + type: 'date', + range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] + }, + yaxis: { + autorange: 'reversed', + ticktext: categories, + zeroline: false, + showgrid: false, + showline: true, + linecolor: '#c0cfe0', + ticks: 'outside', + tickcolor: '#c0cfe0', + tickvals: yvals + }, + title: { + text: record.data.schema.description, + font: { + color: '#444b6e', + size: 16 } - }; + }, + hovermode: 'closest', + annotations: [{ + text: 'Powered by XDMoD/Plotly', + font:{ + color: '#909090', + size: 10, + family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' + }, + xref: 'paper', + yref: 'paper', + xanchor: 'right', + yanchor: 'bottom', + x: 1, + y: 0, + yshift: -80, + showarrow: false + }], + showlegend: false, + margin: { + t: 50, + l: 180, + } + }; - layout['shapes'] = rect; - Plotly.newPlot(this.id, data, layout, {displayModeBar: false}); + layout['shapes'] = rect; + Plotly.react(this.id + '_hc', data, layout, {displayModeBar: false, doubleClick: 'reset'}); - - var panel = document.getElementById(this.id); - panel.on('plotly_click', function(data){ - var userOptions = data.points[0].data.chartSeries; - userOptions['action'] = 'show'; - Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); + const panel = document.getElementById(this.id + '_hc'); + panel.on('plotly_click', function(data){ + const userOptions = data.points[0].data.chartSeries; + userOptions['action'] = 'show'; + Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); + }); - }); - }); } }); diff --git a/html/index.php b/html/index.php index 689536eeac..48c2c03e33 100644 --- a/html/index.php +++ b/html/index.php @@ -162,6 +162,7 @@ function isReferrer($referrer) + @@ -421,7 +422,7 @@ function ($item) { - + diff --git a/html/plotly_template.html b/html/plotly_template.html index d7fc7bff09..61d48ba1f8 100644 --- a/html/plotly_template.html +++ b/html/plotly_template.html @@ -1,163 +1,56 @@ - - + + - - Plotly Template + + Plotly Template - - - + + + + + + - + - - - + - -
- - - + + diff --git a/libraries/charting.php b/libraries/charting.php index 5a3366fe1a..fbcc08f357 100644 --- a/libraries/charting.php +++ b/libraries/charting.php @@ -42,18 +42,15 @@ function exportHighchart( $format, $globalChartConfig = null, $fileMetadata = null, - $isPlotly = false + $isPlotly = false ) { $effectiveWidth = (int)($width*$scale); $effectiveHeight = (int)($height*$scale); $html_dir = __DIR__ . "/../html"; - $template; + $template = file_get_contents($html_dir . "/highchart_template.html"); if ($isPlotly){ - $template = file_get_contents($html_dir . "/plotly_template.html"); - } - else{ - $template = file_get_contents($html_dir . "/highchart_template.html"); + $template = file_get_contents($html_dir . "/plotly_template.html"); } $template = str_replace('_html_dir_', $html_dir, $template); @@ -64,9 +61,9 @@ function exportHighchart( $globalChartOptions = array_merge($globalChartOptions, $globalChartConfig); } $template = str_replace('_globalChartOptions_', json_encode($globalChartOptions), $template); - $template = str_replace('_chartOptions_', json_encode($chartConfig), $template); - $svg = getSvgViaChromiumHelper($template, $effectiveWidth, $effectiveHeight); + + $svg = getSvgViaChromiumHelper($template, $effectiveWidth, $effectiveHeight, $isPlotly); switch($format){ case 'png': return convertSvg($svg, 'png', $effectiveWidth, $effectiveHeight, $fileMetadata); @@ -90,7 +87,7 @@ function exportHighchart( * * @throws \Exception on invalid format, command execution failure, or non zero exit status */ -function getSvgViaChromiumHelper($html, $width, $height){ +function getSvgViaChromiumHelper($html, $width, $height, $isPlotly){ // Chromium requires the file to have a .html extension // cant use datauri as it will not execute embdeeded javascript @@ -107,7 +104,8 @@ function getSvgViaChromiumHelper($html, $width, $height){ $command = LIB_DIR . '/chrome-helper/chrome-helper.js' . ' --window-size=' . $width . ',' . $height . ' --path-to-chrome=' . $chromiumPath . - ' --input-file=' . $tmpHtmlFile; + ' --input-file=' . $tmpHtmlFile . + ' --plotly=' . $isPlotly; $pipes = array(); $descriptor_spec = array( @@ -126,7 +124,7 @@ function getSvgViaChromiumHelper($html, $width, $height){ fclose($pipes[2]); $return_value = proc_close($process); - //@unlink($tmpHtmlFile); + @unlink($tmpHtmlFile); $chartSvg = json_decode($out); @@ -167,6 +165,9 @@ function convertSvg($svgData, $format, $width, $height, $docmeta){ } $rsvgCommand = 'rsvg-convert -w ' .$width. ' -h '.$height.' -f ' . $format; + if ($format == 'png'){ + $rsvgCommand = 'rsvg-convert -b "#FFFFFF" -w ' .$width. ' -h '.$height.' -f ' . $format; + } $exifCommand = 'exiftool ' . $exifArgs . ' -o - -'; $command = $rsvgCommand . ' | ' . $exifCommand; From 918acc7db93c50487448c224b862e56b5378f4b7 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Thu, 15 Jun 2023 20:05:08 -0400 Subject: [PATCH 25/47] Fix bug with analytic chart error text --- html/gui/js/modules/job_viewer/AnalyticChartPanel.js | 1 - 1 file changed, 1 deletion(-) diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index f2d7bbe8d1..79220da628 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -223,7 +223,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { this.errorMsg = null; } if (errorStr) { - errorStr = errorStr, 60; this.errorMsg = errorStr; let errorImage = [ { From 38fbb8b9dc05293b9ff46119c95ee813a308d0e1 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Thu, 15 Jun 2023 22:18:38 -0400 Subject: [PATCH 26/47] Revert "Fix bug with analytic chart error text" This reverts commit 918acc7db93c50487448c224b862e56b5378f4b7. --- html/gui/js/modules/job_viewer/AnalyticChartPanel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 79220da628..f2d7bbe8d1 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -223,6 +223,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { this.errorMsg = null; } if (errorStr) { + errorStr = errorStr, 60; this.errorMsg = errorStr; let errorImage = [ { From c9ba3327bc1664759aecee5691b088db1a72d672 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Thu, 15 Jun 2023 22:18:40 -0400 Subject: [PATCH 27/47] Revert "Recommit of previous commit due to some styling adjustments. Also includes some Gantt Chart and minor export updates." This reverts commit a1f4d5555734bb262038597fa641cfe0d5f99967. --- .../chrome-helper/chrome-helper.js | 21 +- .../WarehouseControllerProvider.php | 492 ++++++++++-------- composer-el7.json | 8 +- composer-el7.lock | 53 +- composer-el8.json | 8 +- html/gui/js/libraries/PlotlyUtilities.js | 152 ------ .../modules/job_viewer/AnalyticChartPanel.js | 294 +++++------ html/gui/js/modules/job_viewer/ChartPanel.js | 291 ++++++++--- html/gui/js/modules/job_viewer/ChartTab.js | 72 ++- html/gui/js/modules/job_viewer/GanttChart.js | 263 +++++----- html/index.php | 3 +- html/plotly_template.html | 177 +++++-- libraries/charting.php | 23 +- 13 files changed, 1030 insertions(+), 827 deletions(-) delete mode 100644 html/gui/js/libraries/PlotlyUtilities.js diff --git a/background_scripts/chrome-helper/chrome-helper.js b/background_scripts/chrome-helper/chrome-helper.js index db1b8fa811..ff304435cf 100755 --- a/background_scripts/chrome-helper/chrome-helper.js +++ b/background_scripts/chrome-helper/chrome-helper.js @@ -20,24 +20,17 @@ const args = require('yargs').argv; await page.goto('file://' + args['input-file']); - let svgInnerHtml; + const highchartInnerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); - if (args.plotly) { - // Chart traces and axis values svg - let plotlyChart = await page.evaluate(() => document.querySelector('.user-select-none.svg-container').children[0].outerHTML); - // Chart title and axis titles svg - const plotlyLabels = await page.evaluate(() => document.querySelector('.user-select-none.svg-container').children[2].innerHTML); - - plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); - const plotlyImage = plotlyChart + "" + plotlyLabels + ""; - - svgInnerHtml = plotlyImage.replace(/
||<\/b>/gm,""); //HTML tags in titles throw xml error + if (highchartInnerHtml !== null){ + console.log(JSON.stringify(highchartInnerHtml)); } - else { - svgInnerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); + else{ + const plotlyInnerHtml = await page.evaluate(() => document.querySelector('#container').innerHTML); + console.log(JSON.stringify(plotlyInnerHtml)); } - console.log(JSON.stringify(svgInnerHtml)); + console.log(JSON.stringify(innerHtml)); await browser.close(); })(); diff --git a/classes/Rest/Controllers/WarehouseControllerProvider.php b/classes/Rest/Controllers/WarehouseControllerProvider.php index af8d9879f0..2ab9191d7c 100644 --- a/classes/Rest/Controllers/WarehouseControllerProvider.php +++ b/classes/Rest/Controllers/WarehouseControllerProvider.php @@ -97,115 +97,115 @@ class WarehouseControllerProvider extends BaseControllerProvider */ private $_supported_types = array( \DataWarehouse\Query\RawQueryTypes::ACCOUNTING => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::ACCOUNTING, - "dtype" => "infoid", - "text" => "Accounting data", - "url" => "/rest/v1.0/warehouse/search/jobs/accounting", - "documentation" => "Shows information about the job that was obtained from the resource manager. - This includes timing information such as the start and end time of the job as - well as administrative information such as the user that submitted the job and - the account that was charged.", - "type" => "keyvaluedata", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::ACCOUNTING, + "dtype" => "infoid", + "text" => "Accounting data", + "url" => "/rest/v1.0/warehouse/search/jobs/accounting", + "documentation" => "Shows information about the job that was obtained from the resource manager. + This includes timing information such as the start and end time of the job as + well as administrative information such as the user that submitted the job and + the account that was charged.", + "type" => "keyvaluedata", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::BATCH_SCRIPT => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::BATCH_SCRIPT, - "dtype" => "infoid", - "text" => "Job script", - "url" => "/rest/v1.0/warehouse/search/jobs/jobscript", - "documentation" => "Shows the job batch script that was passed to the resource manager when the - job was submitted. The script is displayed verbatim.", - "type" => "utf8-text", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::BATCH_SCRIPT, + "dtype" => "infoid", + "text" => "Job script", + "url" => "/rest/v1.0/warehouse/search/jobs/jobscript", + "documentation" => "Shows the job batch script that was passed to the resource manager when the + job was submitted. The script is displayed verbatim.", + "type" => "utf8-text", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::EXECUTABLE => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::EXECUTABLE, - "dtype" => "infoid", - "text" => "Executable information", - "url" => "/rest/v1.0/warehouse/search/jobs/executable", - "documentation" => "Shows information about the processes that were run on the compute nodes during - the job. This information includes the names of the various processes and may - contain information about the linked libraries, loaded modules and process - environment.", - "type" => "nested", - "leaf" => true), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::EXECUTABLE, + "dtype" => "infoid", + "text" => "Executable information", + "url" => "/rest/v1.0/warehouse/search/jobs/executable", + "documentation" => "Shows information about the processes that were run on the compute nodes during + the job. This information includes the names of the various processes and may + contain information about the linked libraries, loaded modules and process + environment.", + "type" => "nested", + "leaf" => true), \DataWarehouse\Query\RawQueryTypes::PEERS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::PEERS, - "dtype" => "infoid", - "text" => "Peers", - 'url' => '/rest/v1.0/warehouse/search/jobs/peers', - 'documentation' => 'Shows the list of other HPC jobs that ran concurrently using the same shared hardware resources.', - 'type' => 'ganttchart', - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::PEERS, + "dtype" => "infoid", + "text" => "Peers", + 'url' => '/rest/v1.0/warehouse/search/jobs/peers', + 'documentation' => 'Shows the list of other HPC jobs that ran concurrently using the same shared hardware resources.', + 'type' => 'ganttchart', + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::NORMALIZED_METRICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::NORMALIZED_METRICS, - "dtype" => "infoid", - "text" => "Summary metrics", - "url" => "/rest/v1.0/warehouse/search/jobs/metrics", - "documentation" => "shows a table with the performance metrics collected during - the job. These are typically average values over the job. The - label for each row has a tooltip that describes the metric. The - data are grouped into the following categories: -
    -
  • CPU Statistics: information about the cores on which the job was - assigned, such as CPU usage, FLOPs, CPI
  • -
  • File I/O Statistics: information about the data read from and - written to block devices and file system mount points.
  • -
  • Memory Statistics: information about the memory usage on the nodes - on which the job ran.
  • -
  • Network I/O Statistics: information about the data transmitted and - received over the network devices.
  • -
- ", - "type" => "metrics", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::NORMALIZED_METRICS, + "dtype" => "infoid", + "text" => "Summary metrics", + "url" => "/rest/v1.0/warehouse/search/jobs/metrics", + "documentation" => "shows a table with the performance metrics collected during + the job. These are typically average values over the job. The + label for each row has a tooltip that describes the metric. The + data are grouped into the following categories: +
    +
  • CPU Statistics: information about the cores on which the job was + assigned, such as CPU usage, FLOPs, CPI
  • +
  • File I/O Statistics: information about the data read from and + written to block devices and file system mount points.
  • +
  • Memory Statistics: information about the memory usage on the nodes + on which the job ran.
  • +
  • Network I/O Statistics: information about the data transmitted and + received over the network devices.
  • +
+ ", + "type" => "metrics", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::DETAILED_METRICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::DETAILED_METRICS, - "dtype" => "infoid", - "text" => "Detailed metrics", - "url" => "/rest/v1.0/warehouse/search/jobs/detailedmetrics", - "documentation" => "shows the data generated by the job summarization software. Please - consult the relevant job summarization software documentation for details - about these metrics.", - "type" => "detailedmetrics", - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::DETAILED_METRICS, + "dtype" => "infoid", + "text" => "Detailed metrics", + "url" => "/rest/v1.0/warehouse/search/jobs/detailedmetrics", + "documentation" => "shows the data generated by the job summarization software. Please + consult the relevant job summarization software documentation for details + about these metrics.", + "type" => "detailedmetrics", + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::ANALYTICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::ANALYTICS, - "dtype" => "infoid", - "text" => "Job analytics", - "url" => "/rest/v1.0/warehouse/search/jobs/analytics", - "documentation" => "Click the help icon on each plot to show the description of the analytic", - "type" => "analytics", - "hidden" => true, - "leaf" => true - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::ANALYTICS, + "dtype" => "infoid", + "text" => "Job analytics", + "url" => "/rest/v1.0/warehouse/search/jobs/analytics", + "documentation" => "Click the help icon on each plot to show the description of the analytic", + "type" => "analytics", + "hidden" => true, + "leaf" => true + ), \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS, - "dtype" => "infoid", - "text" => "Timeseries", - "leaf" => false - ), + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS, + "dtype" => "infoid", + "text" => "Timeseries", + "leaf" => false + ), \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE => - array( - "infoid" => \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE, - "dtype" => "infoid", - "text" => "VM State/Events", - "documentation" => "Show the lifecycle of a VM. Green signifies when a VM is active and red signifies when a VM is stopped.", - "url" => "/rest/v1.0/warehouse/search/cloud/vmstate", - "type" => "vmstate", - "leaf" => true - ) + array( + "infoid" => \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE, + "dtype" => "infoid", + "text" => "VM State/Events", + "documentation" => "Show the lifecycle of a VM. Green signifies when a VM is active and red signifies when a VM is stopped.", + "url" => "/rest/v1.0/warehouse/search/cloud/vmstate", + "type" => "vmstate", + "leaf" => true + ) ); /** @@ -1025,7 +1025,7 @@ public function getQuickFilters(Request $request, Application $app) )); } - /** + /** * Attempt to retrieve the the name for the provided dimensionId. * * @param Request $request @@ -1042,15 +1042,15 @@ public function getDimensionName(Request $request, Application $app, $dimensionI $status = $success ? 200 : 404; $payload = $success - ? array( - 'success' => $success, - 'results' => array( - 'name' => $dimensionName - )) - : array( - 'success' => false, - 'message' => "Unable to find a name for dimension: $dimensionId" - ); + ? array( + 'success' => $success, + 'results' => array( + 'name' => $dimensionName + )) + : array( + 'success' => false, + 'message' => "Unable to find a name for dimension: $dimensionId" + ); return $app->json( $payload, @@ -1077,16 +1077,16 @@ public function getDimensionValueName(Request $request, Application $app, $dimen $status = $success ? 200 : 404; $payload = $success - ? array( - 'success' => $success, - 'results' => array( - 'name' => $valueName - ) - ) - : array( - 'success' => $success, - 'message' => "Unable to find a name for dimesion: $dimensionId | value: $valueId" - ); + ? array( + 'success' => $success, + 'results' => array( + 'name' => $valueName + ) + ) + : array( + 'success' => $success, + 'message' => "Unable to find a name for dimesion: $dimensionId | value: $valueId" + ); return $app->json( $payload, @@ -1381,44 +1381,44 @@ public function processJobSearch(Request $request, Application $app, XDUser $use public function processJobSearchByAction(Request $request, Application $app, XDUser $user, $action, $realm, $jobId, $actionName) { switch ($action) { - case 'accounting': - case 'jobscript': - case 'analysis': - case 'metrics': - case 'analytics': - $results = $this->getJobData($app, $user, $realm, $jobId, $action, $actionName); - break; - case 'peers': - $start = $this->getIntParam($request, 'start', true); - $limit = $this->getIntParam($request, 'limit', true); - $results = $this->getJobPeers($app, $user, $realm, $jobId, $start, $limit); - break; - case 'executable': - $results = $this->getJobExecutable($app, $user, $realm, $jobId, $action, $actionName); - break; - case 'detailedmetrics': - $results = $this->getJobSummary($app, $user, $realm, $jobId, $action, $actionName); - break; - case 'timeseries': - $tsId = $this->getStringParam($request, 'tsid', true); - $nodeId = $this->getIntParam($request, 'nodeid', false); - $cpuId = $this->getIntParam($request, 'cpuid', false); - - $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, $tsId, $nodeId, $cpuId); - break; - case 'vmstate': - $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, null, null, null); - break; - default: - $results = $app->json( - array( - 'success' => false, - 'action' => $actionName, - 'message' => "Unable to process the requested operation. Unsupported action $action." - ), - 400 - ); - break; + case 'accounting': + case 'jobscript': + case 'analysis': + case 'metrics': + case 'analytics': + $results = $this->getJobData($app, $user, $realm, $jobId, $action, $actionName); + break; + case 'peers': + $start = $this->getIntParam($request, 'start', true); + $limit = $this->getIntParam($request, 'limit', true); + $results = $this->getJobPeers($app, $user, $realm, $jobId, $start, $limit); + break; + case 'executable': + $results = $this->getJobExecutable($app, $user, $realm, $jobId, $action, $actionName); + break; + case 'detailedmetrics': + $results = $this->getJobSummary($app, $user, $realm, $jobId, $action, $actionName); + break; + case 'timeseries': + $tsId = $this->getStringParam($request, 'tsid', true); + $nodeId = $this->getIntParam($request, 'nodeid', false); + $cpuId = $this->getIntParam($request, 'cpuid', false); + + $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, $tsId, $nodeId, $cpuId); + break; + case 'vmstate': + $results = $this->getJobTimeSeriesData($app, $request, $user, $realm, $jobId, null, null, null); + break; + default: + $results = $app->json( + array( + 'success' => false, + 'action' => $actionName, + 'message' => "Unable to process the requested operation. Unsupported action $action." + ), + 400 + ); + break; } return $results; @@ -1591,7 +1591,7 @@ private function getJobExecutable(Application $app, \XDUser $user, $realm, $jobI private function arraytostore(array $values) { - return array(array("key" => ".", "value" => "", "expanded" => true, "children" => $this->atosrecurse($values, false) )); + return array(array("key" => ".", "value" => "", "expanded" => true, "children" => $this->atosrecurse($values, false) )); } private function atosrecurse(array $values) @@ -1713,34 +1713,34 @@ private function processJobRequest( switch ($infoId) { - case "" . \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE: - $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; - $info = new $infoclass(); - - $result = array(); - foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { - $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/vmstate"; - $tsid['type'] = "timeseries"; - $tsid['dtype'] = "tsid"; - $result[] = $tsid; - } - return $app->json(array('success' => true, "results" => $result)); - break; - case "" . \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS: - $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; - $info = new $infoclass(); - - $result = array(); - foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { - $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/timeseries"; - $tsid['type'] = "timeseries"; - $tsid['dtype'] = "tsid"; - $result[] = $tsid; - } - return $app->json(array('success' => true, "results" => $result)); - break; - default: - throw new BadRequestException("Node is a leaf"); + case "" . \DataWarehouse\Query\RawQueryTypes::VM_INSTANCE: + $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; + $info = new $infoclass(); + + $result = array(); + foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { + $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/vmstate"; + $tsid['type'] = "timeseries"; + $tsid['dtype'] = "tsid"; + $result[] = $tsid; + } + return $app->json(array('success' => true, "results" => $result)); + break; + case "" . \DataWarehouse\Query\RawQueryTypes::TIMESERIES_METRICS: + $infoclass = "\\DataWarehouse\\Query\\$realm\\JobMetadata"; + $info = new $infoclass(); + + $result = array(); + foreach ($info->getJobTimeseriesMetaData($user, $jobId) as $tsid) { + $tsid['url'] = "/rest/v0.1/warehouse/search/jobs/timeseries"; + $tsid['type'] = "timeseries"; + $tsid['dtype'] = "tsid"; + $result[] = $tsid; + } + return $app->json(array('success' => true, "results" => $result)); + break; + default: + throw new BadRequestException("Node is a leaf"); } } @@ -1960,6 +1960,7 @@ private function chartImageResponse($data, $type, $settings) { // Enable plot marker only if a single point is present in the data series' plot data. // Otherwise plot the data with a line. + $markerEnabled = false; // check the series array passed in from the overall data array: @@ -1979,20 +1980,71 @@ private function chartImageResponse($data, $type, $settings) $lineWidth = 1 + $settings['scale']; $chartConfig = array( - 'width' => $settings['width'], - 'height' => $settings['height'], - 'data' => $data, - 'axisTickSize' => $axisLabelFontSize, - 'axisTitleSize' => $axisTitleFontSize, - 'lineWidth' => $lineWidth, - 'chartTitleSize' => $mainTitleFontSize + 'colors' => array( '#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', + '#f28f43', '#77a1e5', '#c42525', '#a6c96a' + ), + 'series' => $data['series'], + 'xaxis' => array( + 'tickfont' => array( + 'size' => $axisLabelFontSize + ), + 'zerolinewidth' => $lineWidth, + 'title' => '' + 'Time (' . $data['schema']['timezone'] . ')' + '', + 'titlefont' => array( + 'size' => $axisTitleFontSize, + 'color' => '#5078a0' + ) + ), + 'yaxis' => array( + 'title' => '' + 'Time (' . $data['schema']['units'] . ')' + '', + 'titlefont' => array( + 'size' => $axisTitleFontSize, + 'color' => '#5078a0' + ), + 'zerolinewidth' => $lineWidth, + 'ticfont' => array( + 'size' => $axisLabelFontSize + ), + 'rangemode' => 'nonnegative' + ), + 'showlegend' => false, + 'plotOptions' => array( + 'line' => array( + 'lineWidth' => $lineWidth, + 'marker' => array( + 'enabled' => $markerEnabled + ) + ) + ), + 'annotations' => array( + 'text' => $data['schema']['source'] . '. Powered by XDMoD/Plotly', + 'xref' => 'paper', + 'yref' => 'paper', + 'x' => 1, + 'xanchor' => 'left', + 'y' => 0, + 'yanchor' => 'top', + 'showarrow' => false + ), + 'title' => array( + 'font' => array( + 'color' => '#444b6e', + 'size' => $mainTitleFontSize + ), + + 'text' => $settings['show_title'] ? $data['schema']['description'] : null + ) ); + /*if (strpos($data['schema']['units'], '%') !== false) { + $chartConfig['yAxis']['max'] = 100.0; + }*/ + $globalConfig = array( 'timezone' => $data['schema']['timezone'] ); - $chartImage = \xd_charting\exportHighchart($chartConfig, $settings['width'], $settings['height'], $settings['scale'], $type, $globalConfig, $settings['fileMetadata'], true); + $chartImage = \xd_charting\exportHighchart($chartConfig, $settings['width'], $settings['height'], $settings['scale'], $type, $globalConfig, $settings['fileMetadata']); $chartFilename = $settings['fileMetadata']['title'] . '.' . $type; $mimeOverride = $type == 'svg' ? 'image/svg+xml' : null; @@ -2016,30 +2068,30 @@ private function getJobTimeSeriesData(Application $app, Request $request, \XDUse } switch ($format) { - case 'png': - case 'pdf': - case 'svg': - $exportConfig = array( - 'width' => $this->getIntParam($request, 'width', false, 916), - 'height' => $this->getIntParam($request, 'height', false, 484), - 'scale' => floatval($this->getStringParam($request, 'scale', false, '1')), - 'font_size' => $this->getIntParam($request, 'font_size', false, 3), - 'show_title' => $this->getStringParam($request, 'show_title', false, 'y') === 'y' ? true : false, - 'fileMetadata' => array( - 'author' => $user->getFormalName(), - 'subject' => 'Timeseries data for ' . $results['schema']['source'], - 'title' => $results['schema']['description'] - ) - ); - $response = $this->chartImageResponse($results, $format, $exportConfig); - break; - case 'csv': - $response = $this->chartDataResponse($results); - break; - case 'json': - default: - $response = $app->json(array("success" => true, "data" => array($results))); - break; + case 'png': + case 'pdf': + case 'svg': + $exportConfig = array( + 'width' => $this->getIntParam($request, 'width', false, 916), + 'height' => $this->getIntParam($request, 'height', false, 484), + 'scale' => floatval($this->getStringParam($request, 'scale', false, '1')), + 'font_size' => $this->getIntParam($request, 'font_size', false, 3), + 'show_title' => $this->getStringParam($request, 'show_title', false, 'y') === 'y' ? true : false, + 'fileMetadata' => array( + 'author' => $user->getFormalName(), + 'subject' => 'Timeseries data for ' . $results['schema']['source'], + 'title' => $results['schema']['description'] + ) + ); + $response = $this->chartImageResponse($results, $format, $exportConfig); + break; + case 'csv': + $response = $this->chartDataResponse($results); + break; + case 'json': + default: + $response = $app->json(array("success" => true, "data" => array($results))); + break; } return $response; diff --git a/composer-el7.json b/composer-el7.json index ddb1a3f226..a76f8af785 100644 --- a/composer-el7.json +++ b/composer-el7.json @@ -24,7 +24,7 @@ "ubccr/simplesamlphp-module-authoidcoauth2": "^1.1", "phpoffice/phpword": "^0.17.0", "monolog/monolog": "^1.25", - "plotly/plotly": "^2.24.2", + "plotly/plotly": "^1.57.1", "kassner/log-parser": "~1.5", "geoip2/geoip2": "~2.0", "ua-parser/uap-php": "^3.9" @@ -211,13 +211,13 @@ "package": { "name": "plotly/plotly", "type": "vanilla-plugin", - "version": "2.24.2", + "version": "1.57.1", "license": "MIT", "homepage": "https://github.com/plotly/plotly.js", "dist": { - "url": "https://cdn.plot.ly/plotly-2.24.2.min.js", + "url": "https://cdn.plot.ly/plotly-1.57.1.min.js", "type": "file", - "shasum": "18d8802e7767617cfc23b6008a56581a567c1015" + "shasum": "ccf99902ea104599c3b5b4811e83b8ee8118aad3" }, "require": { "composer/installers": "~1.0" diff --git a/composer-el7.lock b/composer-el7.lock index b720348849..1d7dba26e2 100644 --- a/composer-el7.lock +++ b/composer-el7.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d302793db10b0b49c5088813f41343e6", + "content-hash": "17fedb677e9a0759ac6e7dd79ad5ce89", "packages": [ { "name": "carlo/jquery-base64-file", @@ -28,16 +28,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.3.6", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb" + "reference": "fd5dd441932a7e10ca6e5b490e272d34c8430640" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/90d087e988ff194065333d16bc5cf649872d9cdb", - "reference": "90d087e988ff194065333d16bc5cf649872d9cdb", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/fd5dd441932a7e10ca6e5b490e272d34c8430640", + "reference": "fd5dd441932a7e10ca6e5b490e272d34c8430640", "shasum": "" }, "require": { @@ -81,6 +81,11 @@ "ssl", "tls" ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.3.2" + }, "funding": [ { "url": "https://packagist.com", @@ -95,7 +100,7 @@ "type": "tidelift" } ], - "time": "2023-06-06T12:02:59+00:00" + "time": "2022-05-24T11:56:16+00:00" }, { "name": "composer/installers", @@ -487,16 +492,16 @@ }, { "name": "gettext/languages", - "version": "2.10.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/php-gettext/Languages.git", - "reference": "4d61d67fe83a2ad85959fe6133d6d9ba7dddd1ab" + "reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-gettext/Languages/zipball/4d61d67fe83a2ad85959fe6133d6d9ba7dddd1ab", - "reference": "4d61d67fe83a2ad85959fe6133d6d9ba7dddd1ab", + "url": "https://api.github.com/repos/php-gettext/Languages/zipball/ed56dd2c7f4024cc953ed180d25f02f2640e3ffa", + "reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa", "shasum": "" }, "require": { @@ -543,6 +548,10 @@ "translations", "unicode" ], + "support": { + "issues": "https://github.com/php-gettext/Languages/issues", + "source": "https://github.com/php-gettext/Languages/tree/2.9.0" + }, "funding": [ { "url": "https://paypal.me/mlocati", @@ -553,7 +562,7 @@ "type": "github" } ], - "time": "2022-10-18T15:00:10+00:00" + "time": "2021-11-11T17:30:39+00:00" }, { "name": "google/recaptcha", @@ -607,16 +616,16 @@ }, { "name": "greenlion/php-sql-parser", - "version": "v4.6.0", + "version": "v4.5.0", "source": { "type": "git", "url": "https://github.com/greenlion/PHP-SQL-Parser.git", - "reference": "f0e4645eb1612f0a295e3d35bda4c7740ae8c366" + "reference": "a5d5c292d97271c95140192e6f0e962916e39b50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/greenlion/PHP-SQL-Parser/zipball/f0e4645eb1612f0a295e3d35bda4c7740ae8c366", - "reference": "f0e4645eb1612f0a295e3d35bda4c7740ae8c366", + "url": "https://api.github.com/repos/greenlion/PHP-SQL-Parser/zipball/a5d5c292d97271c95140192e6f0e962916e39b50", + "reference": "a5d5c292d97271c95140192e6f0e962916e39b50", "shasum": "" }, "require": { @@ -659,7 +668,11 @@ "parser", "sql" ], - "time": "2023-03-09T20:54:23+00:00" + "support": { + "issues": "https://github.com/greenlion/PHP-SQL-Parser/issues", + "source": "https://github.com/greenlion/PHP-SQL-Parser" + }, + "time": "2022-02-01T09:26:56+00:00" }, { "name": "highsoft/highcharts", @@ -1574,11 +1587,11 @@ }, { "name": "plotly/plotly", - "version": "2.24.2", + "version": "1.57.1", "dist": { "type": "file", - "url": "https://cdn.plot.ly/plotly-2.24.2.min.js", - "shasum": "18d8802e7767617cfc23b6008a56581a567c1015" + "url": "https://cdn.plot.ly/plotly-1.57.1.min.js", + "shasum": "ccf99902ea104599c3b5b4811e83b8ee8118aad3" }, "require": { "composer/installers": "~1.0" @@ -4518,5 +4531,5 @@ "platform-overrides": { "php": "5.4" }, - "plugin-api-version": "1.1.0" + "plugin-api-version": "2.2.0" } diff --git a/composer-el8.json b/composer-el8.json index 07813c0cca..ec7c3debd6 100644 --- a/composer-el8.json +++ b/composer-el8.json @@ -25,7 +25,7 @@ "ubccr/simplesamlphp-module-authoidcoauth2": "^1.1", "phpoffice/phpword": "^0.17.0", "monolog/monolog": "^1.25", - "plotly/plotly": "^2.24.2", + "plotly/plotly": "^1.57.1", "kassner/log-parser": "~1.5", "geoip2/geoip2": "~2.0", "ua-parser/uap-php": "^3.9", @@ -213,13 +213,13 @@ "package": { "name": "plotly/plotly", "type": "vanilla-plugin", - "version": "2.24.2", + "version": "1.57.1", "license": "MIT", "homepage": "https://github.com/plotly/plotly.js", "dist": { - "url": "https://cdn.plot.ly/plotly-2.24.2.min.js", + "url": "https://cdn.plot.ly/plotly-1.57.1.min.js", "type": "file", - "shasum": "18d8802e7767617cfc23b6008a56581a567c1015" + "shasum": "ccf99902ea104599c3b5b4811e83b8ee8118aad3" }, "require": { "composer/installers": "~1.0" diff --git a/html/gui/js/libraries/PlotlyUtilities.js b/html/gui/js/libraries/PlotlyUtilities.js deleted file mode 100644 index 07199bd32f..0000000000 --- a/html/gui/js/libraries/PlotlyUtilities.js +++ /dev/null @@ -1,152 +0,0 @@ -/* generateChartOptions - Generates data array and layout dict for Plotly Chart - * ** Currently assumes that data is in format of a record returned in the JobViewer ** - * - * @param{dict} Record containing chart data - * - */ -function generateChartOptions(record){ - let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', - '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - let data = []; - let isEnvelope = false; - let tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); - let ymin, ymax; - ymin = record.data.series[0].data[0].y; - ymax = ymin; - - for (let sid = 0; sid < record.data.series.length; sid++) { - if (record.data.series[sid].name === 'Range') { - isEnvelope = true; - ymin = record.data.series[1].data[0].y; - ymax = ymin; - tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); - continue; - } - let x = []; - let y = []; - let qtip = []; - let color = colors[sid % 10]; - - for(let i = 0; i < record.data.series[sid].data.length; i++) { - x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS')); - y.push(record.data.series[sid].data[i].y); - qtip.push(record.data.series[sid].data[i].qtip); - } - - let trace = { - x: x, - y: y, - marker: { - size: 0.1, - color: color - }, - line: { - width: 2, - color: color - }, - text: qtip, - hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ": %{y:}" + - "", - name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines' - }; - - if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ - trace['fill'] = 'tonexty'; - trace['fillcolor'] = '#5EA0E2'; - } - - if (isEnvelope){ - trace.hovertemplate = "[%{text}] " + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ": %{y:}" + - ""; - } - - if (record.data.series[sid].data.length === 1){ - trace.marker.size = 20; - trace.mode = 'markers'; - delete trace.line; - } - - data.push(trace); - const tempMin = Math.min(...y); - const tempMax = Math.max(...y); - if (tempMin < ymin) ymin = tempMin; - if (tempMax > ymax) ymax = tempMax; - } - - let layout = { - hoverlabel: { - bgcolor: '#ffffff' - }, - xaxis: { - title: '' + 'Time (' + record.data.schema.timezone + ')' + '', - titlefont: { - family: 'Open-Sans, verdana, arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - ticks: 'outside', - tickcolor: '#c0cfe0', - linecolor: '#c0cfe0', - automargin: true, - showgrid: false, - }, - yaxis: { - title: '' + record.data.schema.units + '', - titlefont: { - family: 'Open-Sans, verdana, arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - range: [0, ymax + (ymax * 0.2)], - showline: false, - zeroline: false, - gridcolor: '#d8d8d8', - automargin: true, - ticks: 'outside', - tickcolor: '#ffffff', - seperatethousands: true - }, - title: { - text: record.data.schema.description, - font: { - family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif', - color: '#444b6e', - size: 16 - }, - }, - annotations: [{ - text: record.data.schema.source + '. Powered by XDMoD/Plotly', - font: { - color: '#909090', - size: 10, - family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' - }, - xref: 'paper', - yref: 'paper', - xanchor: 'right', - yanchor: 'bottom', - x: 1, - y: 0, - yshift: -80, - showarrow: false - }], - hovermode: 'closest', - showlegend: false, - margin: { - t: 50 - } - }; - - let ret = { - chartData: data, - chartLayout: layout - }; - - return ret; -} - diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index f2d7bbe8d1..87f0eb4151 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -17,30 +17,36 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { _DEFAULT_CONFIG: { delimiter: ':', colorSteps: [ - { - value: .25, - color: '#ff0000', - bg_color: 'rgb(255,102,102)' - }, - { - value: .50, - color: '#ffb336', - bg_color: 'rgb(255,255,156)' - - }, - { - value: .75, - color: '#dddf00', - bg_color: 'rgb(255,255,102)' - }, - { - value: 1, - color: '#50b432', - bg_color: 'rgb(182,255,152)' - } + { + value: .25, + color: '#FF0000', + bg_color: 'rgb(255,102,102)' + }, + { + value: .50, + color: '#FFB336', + bg_color: 'rgb(255,255,156)' + + }, + { + value: .75, + color: '#DDDF00', + bg_color: 'rgb(255,255,102)' + }, + { + value: 1, + color: '#50B432', + bg_color: 'rgb(182,255,152)' + } ], - + + layout: { + hoverlabel: { + bgcolor: 'white' + }, + paper_bgcolor: 'white', + plot_bgcolor: 'white', height: 65, xaxis: { showticklabels: false, @@ -50,60 +56,68 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { tick0: 0.0, dtick: 0.2, ticklen: 2, - tickcolor: '#ffffff', + tickcolor: 'white', gridcolor: '#c0c0c0', - linecolor: '#ffffff', + linecolor: 'white', zeroline : false, showgrid: true, - zerolinecolor: '#000000', + zerolinecolor: 'black', showline: false, zerolinewidth: 0, - fixedrange: true + fixedrange: true }, yaxis: { showticklabels: false, color: '#606060', showgrid : false, gridcolor: '#c0c0c0', - linecolor: '#ffffff', + linecolor: 'white', zeroline: false, - zerolinecolor: '#ffffff', + zerolinecolor: 'white', showline: false, rangemode: 'tozero', zerolinewidth: 0, - fixedrange: true + fixedrange: true }, hovermode: false, - shapes: [], - images: [], - annotations: [], + shapes: [], showlegend: false, margin: { - t: 10, - l: 9, - r: 13, - b: 10, + t: 12.5, + l: 7.5, + r: 7.5, + b: 12.5, pad: 0 } }, - config: { - displayModeBar: false, - staticPlot: true + traces: [], + config: { + displayModeBar: false, + }, + bgColors: [] }, - }, // The instance of Highcharts used as this components primary display. chart: null, + // private member that stores the error message object errorMsg: null, - /** - * This components 'constructor'. - */ + /** + * This components 'constructor'. + */ initComponent: function() { - this.colorSteps = Ext.apply(this.colorSteps || [], this._DEFAULT_CONFIG.colorSteps); - XDMoD.Module.JobViewer.AnalyticChartPanel.superclass.initComponent.call(this); + + this.colorSteps = Ext.apply( + this.colorSteps || [], + this._DEFAULT_CONFIG.colorSteps + ); + + XDMoD.Module.JobViewer.AnalyticChartPanel.superclass.initComponent.call( + this + ); + }, // initComponent listeners: { @@ -114,7 +128,8 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * a visible element to hook into are handled here. */ render: function() { - this.chart = Plotly.newPlot(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + this.chart = true; + Plotly.newPlot(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // render /** @@ -126,6 +141,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ update_data: function(data) { this._updateData(data); + //Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // update_data /** @@ -134,17 +150,18 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ reset: function() { if (this.chart) { - Plotly.react(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + this._DEFAULT_CONFIG.traces = []; + Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); } }, // reset beforedestroy: function () { if (this.chart) { - Plotly.purge(this.id); - this.chart = null; + Plotly.purge(this.id); } }, + /** * Attempt to resize this components HighCharts instance such that it * falls with in the new adjWidth and adjHeight. @@ -157,57 +174,19 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (this.chart) { - const container = document.querySelector('#'+this.id); - const bgcolor = container._fullLayout.plot_bgcolor; - const annotation = structuredClone(container._fullLayout.annotations); - const image = structuredClone(container._fullLayout.images); - Plotly.relayout(this.id, {width: adjWidth}); - if (annotation.length > 0){ - Plotly.relayout(this.id, {images: image}); - Plotly.relayout(this.id, {annotations: annotation}); - let update = { - xaxis: { - showticklabels: false, - tickcolor: '#ffffff', - gidcolor: '#ffffff', - linecolor: '#ffffff', - zeroline : false, - showgrid: false, - zerolinecolor: '#000000', - showline: false, - zerolinewidth: 0 - } - }; - Plotly.relayout(this.id, update); - Plotly.relayout(this.id, {plot_bgcolor: bgcolor}); + Plotly.relayout(this.id, {width: adjWidth}); + var elements = document.querySelectorAll('.bglayer'); + if (elements){ + for (var i=0; i < elements.length; i++){ + if (elements[i].firstChild){ + elements[i].firstChild.style.fill = this._DEFAULT_CONFIG.bgColors[i]; + } + } } - else { - Plotly.relayout(this.id, {images: []}); - Plotly.relayout(this.id, {annotations: []}); - let update = { - xaxis: { - showticklabels: false, - range: [0,1], - color: '#606060', - ticks: 'inside', - tick0: 0.0, - dtick: 0.2, - ticklen: 2, - tickcolor: '#ffffff', - gridcolor: '#c0c0c0', - linecolor: '#ffffff', - zeroline : false, - showgrid: true, - zerolinecolor: '#000000', - showline: false, - zerolinewidth: 0, - fixedrange: true - } - }; - Plotly.relayout(this.id, update); - Plotly.relayout(this.id, {plot_bgcolor: bgcolor}); + if (this.errorMsg) { + this.updateErrorMessage(this.errorMsg.text.textStr); } - } + } } // resize }, // listeners @@ -220,45 +199,43 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ updateErrorMessage: function (errorStr) { if (this.errorMsg) { + this.errorMsg.text.destroy(); + this.errorMsg.image.destroy(); this.errorMsg = null; } if (errorStr) { - errorStr = errorStr, 60; - this.errorMsg = errorStr; - let errorImage = [ - { - source: '/gui/images/about_16.png', - align: 'left', - xref: 'paper', - yref: 'paper', - sizex: 0.4, - sizey: 0.4, - x: 0, - y: 1.2 - } - ]; - this._DEFAULT_CONFIG.layout.images = errorImage; - let errorText = [ - { - text: '' + errorStr + '', - align: 'left', - xref: 'paper', - yref: 'paper', - font:{ - size: 11, - }, - x : 0.05, - y : 1.2, - showarrow: false - } - ]; - this._DEFAULT_CONFIG.layout.annotations = errorText; - this._DEFAULT_CONFIG.layout.xaxis.showgrid = false; + this._DEFAULT_CONFIG.layout['images'] = [ + { + "source": '/gui/images/about_16.png', + "align": "left", + "xref": "paper", + "yref": "paper", + "sizex": 0.4, + "sizey": 0.4, + "x": 0, + "y": 1.2 + } + ] + this._DEFAULT_CONFIG.layout['annotations'] = [ + { + "text": '' + errorStr + '', + "align": "left", + "xref": "paper", + "yref": "paper", + "font":{ + "size": 11, + }, + "x" : 0.05, + "y" : 1.2, + "showarrow": false + } + ] + this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = false; } else { - this._DEFAULT_CONFIG.layout.images = []; - this._DEFAULT_CONFIG.layout.annotations = []; - this._DEFAULT_CONFIG.layout.xaxis.showgrid = true; + this._DEFAULT_CONFIG.layout['images'] = []; + this._DEFAULT_CONFIG.layout['annotations'] = []; + this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = true; } }, @@ -270,33 +247,37 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _updateData: function(data) { - let trace = {}; - if (data.error == '') { - let chartColor = this._getDataColor(data.value); - this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; + this._DEFAULT_CONFIG.traces = []; + + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = 'white'; + var trace = {}; + if (data.error == '') { + var chartColor = this._getDataColor(data.value); + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; + this._DEFAULT_CONFIG.bgColors.push(chartColor.bg_color); + trace = { - x: [data.value], - name: data.name ? data.name : '', - width: [0.5], - marker:{ - color: chartColor.color, - line:{ - color: '#ffffff', - width: 1 - } - }, - type: 'bar', - orientation: 'h', - }; - } - else { - this._DEFAULT_CONFIG.layout['plot_bgcolor'] = '#ffffff'; + x: [data.value], + name: data.name ? data.name : '', + width: [0.5], + marker:{ + color: chartColor.color, + line:{ + color: 'white', + width: 1 + } + }, + type: 'bar', + orientation: 'h', + }; } + this._DEFAULT_CONFIG.traces.push(trace); this.updateErrorMessage(data.error); this._updateTitle(data); this.ownerCt.doLayout(false, true); Plotly.react(this.id, [trace], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + }, // _updateData /** @@ -318,6 +299,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _getDataColor: function(data) { + var ret = {}; var color = null; if (typeof data === 'number') { var steps = this.colorSteps; @@ -325,10 +307,10 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { for ( var i = 0; i < count; i++) { var step = steps[i]; if (data <= step.value) { - let ret = { - color: step.color, - bg_color: step.bg_color - }; + ret = { + color: step.color, + bg_color: step.bg_color + }; return ret; } } diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index a570965bb9..7b127135ad 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -7,9 +7,18 @@ Ext.namespace('XDMoD', 'XDMoD.Module', 'XDMoD.Module.JobViewer'); */ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { + // The default chart config options. + _DEFAULT_CONFIG: { + chartPrefix: 'CreateChartPanel', + }, + // The chart instance. chart: null, + chartWidth: null, + + chartHeight: null, + /** * The component 'constructor'. */ @@ -26,9 +35,9 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { // ADD: The custom events that we're listening for. this.addEvents( - 'load_record', - 'record_loaded' - ); + 'load_record', + 'record_loaded' + ); var self = this; @@ -76,23 +85,25 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } }, // render - /** - * - * @param panel - * @param adjWidth - * @param adjHeight - * @param rawWidth - * @param rawHeight - */ + /** + * + * @param panel + * @param adjWidth + * @param adjHeight + * @param rawWidth + * @param rawHeight + */ resize: function(panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (panel.chart) { + this.chartWidth = adjWidth; + this.chartWidth = adjHeight; Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); } }, // resize beforedestroy: function () { if (this.chart) { - Plotly.purge(this.id); + Plotly.purge(this.id); this.chart = false; } }, @@ -103,45 +114,49 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { print_clicked: function () { if (this.chart) { - let chartDiv = document.querySelector('#' + this.id); - chartDiv = chartDiv.firstChild.firstChild; // parent div of the plotly SVGs - - // Make deep copy - const tmpWidth = structuredClone(chartDiv.clientWidth); - const tmpHeight = structuredClone(chartDiv.clientHeight); - - // Resize to 'medium' export width and height -- Currently placeholder width and height - Plotly.relayout(this.id, {width: 916, height: 484}); - - // Combine Plotly svg elements similar to export - let plotlyChart = chartDiv.children[0].outerHTML; - const plotlyLabels = chartDiv.children[2].innerHTML; - - plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); - let svg = plotlyChart + plotlyLabels + '' - - let printWindow = window.open(); - printWindow.document.write(' Printing '); - printWindow.document.write(svg); - printWindow.print(); - printWindow.close(); - - Plotly.relayout(this.id, {width: tmpWidth, height: tmpHeight}); + this.chart.print(); } }, - /** - * - * @param panel - * @param record - */ + /** + * + * @param panel + * @param record + */ load_record: function(panel, record) { var self = this; + var chartClickHandler = function(event) { + var userOptions = this.series.userOptions; + if (!userOptions || !userOptions.dtype) { + return; + } + var drilldown; + /* + * The drilldown data are stored on each point for envelope + * plots and for the series for simple plots. + */ + if (userOptions.dtype == 'index') { + drilldown = { + dtype: userOptions.index, + value: event.point.options[userOptions.index] + }; + } else { + drilldown = { + dtype: userOptions.dtype, + value: userOptions[userOptions.dtype] + }; + } + var path = self.path.concat([drilldown]); + var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); + Ext.History.add(token); + }; + if (record !== null && record !== undefined) { this.dataurl = record.store.proxy.url; this.displayTimezone = record.data.schema.timezone; + if (record.data.schema.help) { panel.helptext.documentation = record.data.schema.help; this.jobTab.fireEvent("display_help", panel.helptext); @@ -149,48 +164,180 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } if (record) { - let chartOptions = generateChartOptions(record); + let colorChoices = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', + '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + let data = []; + var isEnvelope = false; + var hasSingleDataPoint = false; + var tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); + var ymin, ymax; + ymin = record.data.series[0].data[0].y; + ymax = ymin; + for (let sid = 0; sid < record.data.series.length; sid++) { + if (record.data.series[sid].name === "Range") { + isEnvelope = true; + ymin = record.data.series[1].data[0].y; + ymax = ymin; + tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); + continue; + } + let x = []; + let y = []; + let qtip = []; + let colors = colorChoices[sid % 10]; + for(let i=0; i < record.data.series[sid].data.length; i++) { + if (record.data.series[sid].data.length == 1){ + hasSingleDataPoint = true; + } + x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); + y.push(record.data.series[sid].data[i].y); + qtip.push(record.data.series[sid].data[i].qtip); + } + + if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ + data.push({ + x: x, + y: y, + fill: 'tonexty', + fillcolor: '#5EA0E2', + marker: { + size: 0.1, + color: colors + }, + line: { + width: 2, + color: colors + }, + text: qtip, + hovertemplate: "[%{text}] " + + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y: .f}" + + "", + name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}); + } + else{ + var trace = { + x: x, + y: y, + marker: { + size: 0.1, + color: colors + }, + line: { + width: 2, + color: colors + }, + text: qtip, + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y: .f}" + + "", + name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}; + + if (isEnvelope){ + trace.hovertemplate = "[%{text}] " + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y: .f}" + + ""; + } + if (hasSingleDataPoint){ + trace.marker.size = 20; + trace.mode = 'markers'; + delete trace.line; + } + data.push(trace); + } + var tempyMin = Math.min(...y); + var tempyMax = Math.max(...y); + if (tempyMin < ymin) ymin = tempyMin; + if (tempyMax > ymax) ymax = tempyMax; + } + panel.getEl().unmask(); - + let layout = { + hoverlabel: { + bgcolor: 'white' + }, + xaxis: { + title: 'Time (' + record.data.schema.timezone + ')', + titlefont: { + family: 'Arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + ticks: 'outside', + ticklen: 10, + tickcolor: '#c0cfe0', + linecolor: '#c0cfe0', + automargin: true, + showgrid: false + }, + yaxis: { + title: '' + record.data.schema.units + '', + titlefont: { + family: 'Arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + range: [0, ymax + (ymax * 0.2)], + rangemode: 'nonnegative', + gridcolor: 'lightgray', + automargin: true, + linecolor: '#c0cfe0' + }, + title: { + text: record.data.schema.description, + font: { + color: '#444b6e', + size: 16 + } + }, + hovermode: 'closest', + showlegend: false, + margin: { + t: 50 + } + }; if (panel.chart) { - Plotly.react(this.id, chartOptions.chartData, chartOptions.chartLayout, {displayModeBar: false, doubleClick: 'reset'} ); + Plotly.react(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'} ); this.chart = true; } else { - Plotly.newPlot(this.id, chartOptions.chartData, chartOptions.chartLayout, {displayModeBar: false, doubleClick: 'reset'} ); + Plotly.newPlot(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'} ); this.chart = true; } - if (panel.chart) { + + if (this.chart) { panel.chart = document.getElementById(this.id); panel.chart.on('plotly_click', function(data, event){ - const userOptions = data.points[0].data.chartSeries; + var userOptions = data.points[0].data.chartSeries if (!userOptions || !userOptions.dtype) { return; } - let drilldown; + var drilldown; /* * The drilldown data are stored on each point for envelope * plots and for the series for simple plots. */ if (userOptions.dtype == 'index') { - const nodeidIndex = data.points[0].pointIndex; + var nodeidIndex = data.points[0].data.chartSeries.data.findIndex(({y}) => y === data.points[0].y); if (nodeidIndex === -1) return; drilldown = { dtype: userOptions.index, value: userOptions.data[nodeidIndex].nodeid }; - } - else { + } else { drilldown = { dtype: userOptions.dtype, value: userOptions[userOptions.dtype] }; } - const path = self.path.concat([drilldown]); - const token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); + var path = self.path.concat([drilldown]); + var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); Ext.History.add(token); - }); - } - } + }); + } + } if (!record) { panel.getEl().mask('Loading...'); @@ -201,12 +348,12 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, // listeners - /** - * - * @param series - * @returns {*} - * @private - */ + /** + * + * @param series + * @returns {*} + * @private + */ _findDtype: function(series) { if (!CCR.isType(series, CCR.Types.Array)) return null; @@ -222,16 +369,16 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { return result; }, - /** - * Helper function that adds an explicit 'load' listener to the provided - * this.series.userOptions;data store. This listener will ensure that each time the store receives - * a load event, if there is at least one record, then this components - * 'load_record' event will be fired with a reference to the first record - * returned. - * - * @param store to be listened to. - * @private - */ + /** + * Helper function that adds an explicit 'load' listener to the provided + * this.series.userOptions;data store. This listener will ensure that each time the store receives + * a load event, if there is at least one record, then this components + * 'load_record' event will be fired with a reference to the first record + * returned. + * + * @param store to be listened to. + * @private + */ _addStoreListeners: function(store) { if ( typeof store === 'object') { var self = this; diff --git a/html/gui/js/modules/job_viewer/ChartTab.js b/html/gui/js/modules/job_viewer/ChartTab.js index 48bc865e7b..46d898d13d 100644 --- a/html/gui/js/modules/job_viewer/ChartTab.js +++ b/html/gui/js/modules/job_viewer/ChartTab.js @@ -37,7 +37,68 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { var self = this; var createChart = function () { - this.chart = Plotly.newPlot(this.id, [], [], {displayModeBar: false, doubleClick: 'reset'}); + var defaultChartSettings = { + chart: { + renderTo: self.id + '_hc' + }, + colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a'], + title: { + style: { + color: '#444b6e', + fontSize: '16px' + }, + text: '' + }, + loading: { + style: { + opacity: 0.7 + } + }, + yAxis: { + title: { + style: { + fontWeight: 'bold', + color: '#5078a0' + } + } + }, + legend: { + enabled: false + }, + exporting: { + enabled: false + }, + tooltip: { + pointFormat: ' {series.name}: {point.low:%A, %b %e, %H:%M:%S} - {point.high:%A, %b %e, %H:%M:%S}
', + dateTimeLabelFormats: { + millisecond: '%A, %b %e, %H:%M:%S.%L %T', + second: '%A, %b %e, %H:%M:%S %T', + minute: '%A, %b %e, %H:%M:%S %T', + hour: '%A, %b %e, %H:%M:%S %T' + } + }, + plotOptions: { + line: { + marker: { + enabled: false + } + }, + columnrange: { + minPointLength: 3, + animation: false, + dataLabels: { + enabled: false + } + }, + series: { + allowPointSelect: false, + animation: false + } + } + }; + + var chartOptions = jQuery.extend(true, {}, defaultChartSettings, self.chartSettings); + var storeParams; if (self.panelSettings.pageSize) { storeParams = { @@ -87,9 +148,9 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { xtype: 'container', id: this.id + '_hc', listeners: { - resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { - if (this.chart) { - Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); + resize: function () { + if (self.chart) { + self.chart.reflow(); } }, render: createChart @@ -119,8 +180,7 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { beforedestroy: function () { if (this.chart) { Plotly.purge(this.id); - this.chart = false; } - }, + } } }); diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index 0e06e6d2b9..a098ba20f0 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -3,6 +3,20 @@ Ext.namespace('XDMoD', 'XDMoD.Module', 'XDMoD.Module.JobViewer'); XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, { initComponent: function () { + this.chartSettings = { + chart: { + type: 'columnrange', + grouping: false, + inverted: true + }, + yAxis: { + type: 'datetime', + minTickInterval: 1000, + title: { + text: 'Time (' + self.displayTimezone + ')' + } + } + }; this.panelSettings = { pageSize: 11, @@ -12,151 +26,140 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, fields: ['series', 'schema', 'categories'] } }; - + XDMoD.Module.JobViewer.GanttChart.superclass.initComponent.call(this, arguments); - + this.addListener('updateChart', function (store) { - if (store.getCount() < 1) { + if (store.getCount() < 1) { return; - } + } - var record = store.getAt(0); - let data = []; - let categories = []; - let count = 0; - let rect = []; - let yvals = []; - let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - for (let i = 0; i < record.data.series.length; i++) { - for (let j = 0; j < record.data.series[i].data.length; j++){ - let low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - let high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - categories.push(record.data.categories[count]); - let runtime = []; - let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; - let tooltip = [template]; - let ticks = [count]; - let start_time = record.data.series[i].data[j].low; - // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. - // Points on the scatter plot are created every min to create better coverage for tooltip information - while (start_time < record.data.series[i].data[j].high){ - ticks.push(count); - tooltip.push(template); - runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); - start_time += (60 * 1000); - } - runtime.push(high_data); + var record = store.getAt(0); + var i; + var data = []; + var categories = []; + var count = 0; + var rect = []; + var yvals = [0]; + let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + for (i = 0; i < record.data.series.length; i++) { + var j = 0; + for (j = 0; j < record.data.series[i].data.length; j++){ + low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + categories.push(record.data.categories[count]); + var runtime = []; + let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; + var tooltip = [template]; + var ticks = [count]; + var start_time = record.data.series[i].data[j].low; + // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. + // Points on the scatter plot are created every min to create better coverage for tooltip information + while (start_time < record.data.series[i].data[j].high){ + ticks.push(count); + tooltip.push(template); + runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); + start_time += (60 * 1000); + } + runtime.push(high_data); + + rect.push({ + x0: low_data, + x1: high_data, + y0: count-0.25, + y1: count+0.25, + type: 'rect', + xref: 'x', + yref: 'y', + fillcolor: colors[i % 10], + //color: colors[i % 10] + }); - rect.push({ - x0: low_data, - x1: high_data, - y0: count-0.175, - y1: count+0.175, - type: 'rect', - xref: 'x', - yref: 'y', - fillcolor: colors[i % 10], - }); + + var info = {} - let info = {}; - if (i > 0){ - info = { - realm: record.data.series[i].data[j].ref.realm, - recordid: store.baseParams.recordid, - jobref: record.data.series[i].data[j].ref.jobid, - infoid: 3 - }; - } - let peer = { - x: runtime, - y: ticks, + if (i > 0){ + info = { + realm: record.data.series[i].data[j].ref.realm, + recordid: store.baseParams.recordid, + jobref: record.data.series[i].data[j].ref.jobid, + infoid: 3 + }; + } + + peer = { + x: runtime, + y: ticks, type: 'scatter', - marker:{ - color: 'rgb(255,255,255)', - size: 20 - }, - orientation: 'h', - hovertemplate: record.data.categories[count] + '
'+ - "" + '%{text} ', - text: tooltip, - chartSeries: info + + marker:{ + color: 'rgb(255,255,255)' + }, + orientation: 'h', + hovertemplate: record.data.categories[count] + '
'+ + "" + '%{text} ', + text: tooltip, + chartSeries: info }; - data.push(peer); - yvals.push(count); - count++; - } - + data.push(peer); + count++; + yvals.push(count); } - let layout = { - hoverlabel: { - bgcolor: '#ffffff' - }, - xaxis: { - title: 'Time (' + record.data.schema.timezone + ')', - titlefont: { - family: 'Arial, sans-serif', - size: 12, - color: '#5078a0' + + } + let layout = { + hoverlabel: { + bgcolor: 'white' }, - color: '#606060', - zeroline: false, - gridcolor: '#d8d8d8', - type: 'date', - range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] - }, - yaxis: { - autorange: 'reversed', - ticktext: categories, - zeroline: false, - showgrid: false, - showline: true, - linecolor: '#c0cfe0', - ticks: 'outside', - tickcolor: '#c0cfe0', - tickvals: yvals - }, - title: { - text: record.data.schema.description, - font: { - color: '#444b6e', - size: 16 - } - }, - hovermode: 'closest', - annotations: [{ - text: 'Powered by XDMoD/Plotly', - font:{ - color: '#909090', - size: 10, - family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' + xaxis: { + title: 'Time (' + record.data.schema.timezone + ')', + titlefont: { + family: 'Arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + ticks: 'outside', + tickcolor: '#c0cfe0', + linecolor: '#c0cfe0', + type: 'date', + range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] }, - xref: 'paper', - yref: 'paper', - xanchor: 'right', - yanchor: 'bottom', - x: 1, - y: 0, - yshift: -80, - showarrow: false - }], - showlegend: false, - margin: { - t: 50, - l: 180, - } - }; + yaxis: { + autorange: 'reversed', + ticktext: categories, + zeroline: false, + tickvals: yvals + }, + title: { + text: record.data.schema.description, + font: { + color: '#444b6e', + size: 16 + } + }, + hovermode: 'closest', + showlegend: false, + margin: { + l: 175, + b: 150 + + } + }; - layout['shapes'] = rect; - Plotly.react(this.id + '_hc', data, layout, {displayModeBar: false, doubleClick: 'reset'}); + layout['shapes'] = rect; + Plotly.newPlot(this.id, data, layout, {displayModeBar: false}); - const panel = document.getElementById(this.id + '_hc'); - panel.on('plotly_click', function(data){ - const userOptions = data.points[0].data.chartSeries; - userOptions['action'] = 'show'; - Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); - }); + + var panel = document.getElementById(this.id); + panel.on('plotly_click', function(data){ + var userOptions = data.points[0].data.chartSeries; + userOptions['action'] = 'show'; + Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); + }); + }); } }); diff --git a/html/index.php b/html/index.php index 48c2c03e33..689536eeac 100644 --- a/html/index.php +++ b/html/index.php @@ -162,7 +162,6 @@ function isReferrer($referrer) - @@ -422,7 +421,7 @@ function ($item) { - + diff --git a/html/plotly_template.html b/html/plotly_template.html index 61d48ba1f8..d7fc7bff09 100644 --- a/html/plotly_template.html +++ b/html/plotly_template.html @@ -1,56 +1,163 @@ - - + + - - Plotly Template + + Plotly Template - - - + + + - - - - + - + + + - + +
+ + + + - diff --git a/libraries/charting.php b/libraries/charting.php index fbcc08f357..5a3366fe1a 100644 --- a/libraries/charting.php +++ b/libraries/charting.php @@ -42,15 +42,18 @@ function exportHighchart( $format, $globalChartConfig = null, $fileMetadata = null, - $isPlotly = false + $isPlotly = false ) { $effectiveWidth = (int)($width*$scale); $effectiveHeight = (int)($height*$scale); $html_dir = __DIR__ . "/../html"; - $template = file_get_contents($html_dir . "/highchart_template.html"); + $template; if ($isPlotly){ - $template = file_get_contents($html_dir . "/plotly_template.html"); + $template = file_get_contents($html_dir . "/plotly_template.html"); + } + else{ + $template = file_get_contents($html_dir . "/highchart_template.html"); } $template = str_replace('_html_dir_', $html_dir, $template); @@ -61,9 +64,9 @@ function exportHighchart( $globalChartOptions = array_merge($globalChartOptions, $globalChartConfig); } $template = str_replace('_globalChartOptions_', json_encode($globalChartOptions), $template); + $template = str_replace('_chartOptions_', json_encode($chartConfig), $template); - - $svg = getSvgViaChromiumHelper($template, $effectiveWidth, $effectiveHeight, $isPlotly); + $svg = getSvgViaChromiumHelper($template, $effectiveWidth, $effectiveHeight); switch($format){ case 'png': return convertSvg($svg, 'png', $effectiveWidth, $effectiveHeight, $fileMetadata); @@ -87,7 +90,7 @@ function exportHighchart( * * @throws \Exception on invalid format, command execution failure, or non zero exit status */ -function getSvgViaChromiumHelper($html, $width, $height, $isPlotly){ +function getSvgViaChromiumHelper($html, $width, $height){ // Chromium requires the file to have a .html extension // cant use datauri as it will not execute embdeeded javascript @@ -104,8 +107,7 @@ function getSvgViaChromiumHelper($html, $width, $height, $isPlotly){ $command = LIB_DIR . '/chrome-helper/chrome-helper.js' . ' --window-size=' . $width . ',' . $height . ' --path-to-chrome=' . $chromiumPath . - ' --input-file=' . $tmpHtmlFile . - ' --plotly=' . $isPlotly; + ' --input-file=' . $tmpHtmlFile; $pipes = array(); $descriptor_spec = array( @@ -124,7 +126,7 @@ function getSvgViaChromiumHelper($html, $width, $height, $isPlotly){ fclose($pipes[2]); $return_value = proc_close($process); - @unlink($tmpHtmlFile); + //@unlink($tmpHtmlFile); $chartSvg = json_decode($out); @@ -165,9 +167,6 @@ function convertSvg($svgData, $format, $width, $height, $docmeta){ } $rsvgCommand = 'rsvg-convert -w ' .$width. ' -h '.$height.' -f ' . $format; - if ($format == 'png'){ - $rsvgCommand = 'rsvg-convert -b "#FFFFFF" -w ' .$width. ' -h '.$height.' -f ' . $format; - } $exifCommand = 'exiftool ' . $exifArgs . ' -o - -'; $command = $rsvgCommand . ' | ' . $exifCommand; From a74265d3fa8475ac112893f1388dfd2c428b3658 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Thu, 15 Jun 2023 22:47:47 -0400 Subject: [PATCH 28/47] Fix composer files --- .../chrome-helper/chrome-helper.js | 21 +- .../WarehouseControllerProvider.php | 67 +--- composer-el7.json | 8 +- composer-el7.lock | 6 +- composer-el8.json | 8 +- composer-el8.lock | 6 +- html/gui/js/libraries/PlotlyUtilities.js | 152 ++++++++++ .../modules/job_viewer/AnalyticChartPanel.js | 287 ++++++++++-------- html/gui/js/modules/job_viewer/ChartPanel.js | 233 +++----------- html/gui/js/modules/job_viewer/ChartTab.js | 72 +---- html/gui/js/modules/job_viewer/GanttChart.js | 263 ++++++++-------- html/index.php | 3 +- html/plotly_template.html | 177 +++-------- libraries/charting.php | 23 +- 14 files changed, 568 insertions(+), 758 deletions(-) create mode 100644 html/gui/js/libraries/PlotlyUtilities.js diff --git a/background_scripts/chrome-helper/chrome-helper.js b/background_scripts/chrome-helper/chrome-helper.js index ff304435cf..db1b8fa811 100755 --- a/background_scripts/chrome-helper/chrome-helper.js +++ b/background_scripts/chrome-helper/chrome-helper.js @@ -20,17 +20,24 @@ const args = require('yargs').argv; await page.goto('file://' + args['input-file']); - const highchartInnerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); + let svgInnerHtml; - if (highchartInnerHtml !== null){ - console.log(JSON.stringify(highchartInnerHtml)); + if (args.plotly) { + // Chart traces and axis values svg + let plotlyChart = await page.evaluate(() => document.querySelector('.user-select-none.svg-container').children[0].outerHTML); + // Chart title and axis titles svg + const plotlyLabels = await page.evaluate(() => document.querySelector('.user-select-none.svg-container').children[2].innerHTML); + + plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); + const plotlyImage = plotlyChart + "" + plotlyLabels + ""; + + svgInnerHtml = plotlyImage.replace(/
||<\/b>/gm,""); //HTML tags in titles throw xml error } - else{ - const plotlyInnerHtml = await page.evaluate(() => document.querySelector('#container').innerHTML); - console.log(JSON.stringify(plotlyInnerHtml)); + else { + svgInnerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); } - console.log(JSON.stringify(innerHtml)); + console.log(JSON.stringify(svgInnerHtml)); await browser.close(); })(); diff --git a/classes/Rest/Controllers/WarehouseControllerProvider.php b/classes/Rest/Controllers/WarehouseControllerProvider.php index 2ab9191d7c..51ca7f35a5 100644 --- a/classes/Rest/Controllers/WarehouseControllerProvider.php +++ b/classes/Rest/Controllers/WarehouseControllerProvider.php @@ -1980,71 +1980,20 @@ private function chartImageResponse($data, $type, $settings) $lineWidth = 1 + $settings['scale']; $chartConfig = array( - 'colors' => array( '#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', - '#f28f43', '#77a1e5', '#c42525', '#a6c96a' - ), - 'series' => $data['series'], - 'xaxis' => array( - 'tickfont' => array( - 'size' => $axisLabelFontSize - ), - 'zerolinewidth' => $lineWidth, - 'title' => '' + 'Time (' . $data['schema']['timezone'] . ')' + '', - 'titlefont' => array( - 'size' => $axisTitleFontSize, - 'color' => '#5078a0' - ) - ), - 'yaxis' => array( - 'title' => '' + 'Time (' . $data['schema']['units'] . ')' + '', - 'titlefont' => array( - 'size' => $axisTitleFontSize, - 'color' => '#5078a0' - ), - 'zerolinewidth' => $lineWidth, - 'ticfont' => array( - 'size' => $axisLabelFontSize - ), - 'rangemode' => 'nonnegative' - ), - 'showlegend' => false, - 'plotOptions' => array( - 'line' => array( - 'lineWidth' => $lineWidth, - 'marker' => array( - 'enabled' => $markerEnabled - ) - ) - ), - 'annotations' => array( - 'text' => $data['schema']['source'] . '. Powered by XDMoD/Plotly', - 'xref' => 'paper', - 'yref' => 'paper', - 'x' => 1, - 'xanchor' => 'left', - 'y' => 0, - 'yanchor' => 'top', - 'showarrow' => false - ), - 'title' => array( - 'font' => array( - 'color' => '#444b6e', - 'size' => $mainTitleFontSize - ), - - 'text' => $settings['show_title'] ? $data['schema']['description'] : null - ) + 'width' => $settings['width'], + 'height' => $settings['height'], + 'data' => $data, + 'axisTickSize' => $axisLabelFontSize, + 'axisTitleSize' => $axisTitleFontSize, + 'lineWidth' => $lineWidth, + 'chartTitleSize' => $mainTitleFontSize ); - /*if (strpos($data['schema']['units'], '%') !== false) { - $chartConfig['yAxis']['max'] = 100.0; - }*/ - $globalConfig = array( 'timezone' => $data['schema']['timezone'] ); - $chartImage = \xd_charting\exportHighchart($chartConfig, $settings['width'], $settings['height'], $settings['scale'], $type, $globalConfig, $settings['fileMetadata']); + $chartImage = \xd_charting\exportHighchart($chartConfig, $settings['width'], $settings['height'], $settings['scale'], $type, $globalConfig, $settings['fileMetadata'], true); $chartFilename = $settings['fileMetadata']['title'] . '.' . $type; $mimeOverride = $type == 'svg' ? 'image/svg+xml' : null; diff --git a/composer-el7.json b/composer-el7.json index a76f8af785..ddb1a3f226 100644 --- a/composer-el7.json +++ b/composer-el7.json @@ -24,7 +24,7 @@ "ubccr/simplesamlphp-module-authoidcoauth2": "^1.1", "phpoffice/phpword": "^0.17.0", "monolog/monolog": "^1.25", - "plotly/plotly": "^1.57.1", + "plotly/plotly": "^2.24.2", "kassner/log-parser": "~1.5", "geoip2/geoip2": "~2.0", "ua-parser/uap-php": "^3.9" @@ -211,13 +211,13 @@ "package": { "name": "plotly/plotly", "type": "vanilla-plugin", - "version": "1.57.1", + "version": "2.24.2", "license": "MIT", "homepage": "https://github.com/plotly/plotly.js", "dist": { - "url": "https://cdn.plot.ly/plotly-1.57.1.min.js", + "url": "https://cdn.plot.ly/plotly-2.24.2.min.js", "type": "file", - "shasum": "ccf99902ea104599c3b5b4811e83b8ee8118aad3" + "shasum": "18d8802e7767617cfc23b6008a56581a567c1015" }, "require": { "composer/installers": "~1.0" diff --git a/composer-el7.lock b/composer-el7.lock index 1d7dba26e2..627a8b819f 100644 --- a/composer-el7.lock +++ b/composer-el7.lock @@ -1587,11 +1587,11 @@ }, { "name": "plotly/plotly", - "version": "1.57.1", + "version": "2.24.2, "dist": { "type": "file", - "url": "https://cdn.plot.ly/plotly-1.57.1.min.js", - "shasum": "ccf99902ea104599c3b5b4811e83b8ee8118aad3" + "url": "https://cdn.plot.ly/plotly-2.24.2.min.js", + "shasum": "18d8802e7767617cfc23b6008a56581a567c1015" }, "require": { "composer/installers": "~1.0" diff --git a/composer-el8.json b/composer-el8.json index ec7c3debd6..07813c0cca 100644 --- a/composer-el8.json +++ b/composer-el8.json @@ -25,7 +25,7 @@ "ubccr/simplesamlphp-module-authoidcoauth2": "^1.1", "phpoffice/phpword": "^0.17.0", "monolog/monolog": "^1.25", - "plotly/plotly": "^1.57.1", + "plotly/plotly": "^2.24.2", "kassner/log-parser": "~1.5", "geoip2/geoip2": "~2.0", "ua-parser/uap-php": "^3.9", @@ -213,13 +213,13 @@ "package": { "name": "plotly/plotly", "type": "vanilla-plugin", - "version": "1.57.1", + "version": "2.24.2", "license": "MIT", "homepage": "https://github.com/plotly/plotly.js", "dist": { - "url": "https://cdn.plot.ly/plotly-1.57.1.min.js", + "url": "https://cdn.plot.ly/plotly-2.24.2.min.js", "type": "file", - "shasum": "ccf99902ea104599c3b5b4811e83b8ee8118aad3" + "shasum": "18d8802e7767617cfc23b6008a56581a567c1015" }, "require": { "composer/installers": "~1.0" diff --git a/composer-el8.lock b/composer-el8.lock index 75d1a26c44..dc8244c3c9 100644 --- a/composer-el8.lock +++ b/composer-el8.lock @@ -1737,11 +1737,11 @@ }, { "name": "plotly/plotly", - "version": "1.57.1", + "version": "2.24.2", "dist": { "type": "file", - "url": "https://cdn.plot.ly/plotly-1.57.1.min.js", - "shasum": "ccf99902ea104599c3b5b4811e83b8ee8118aad3" + "url": "https://cdn.plot.ly/plotly-2.24.2.min.js", + "shasum": "18d8802e7767617cfc23b6008a56581a567c1015" }, "require": { "composer/installers": "~1.0" diff --git a/html/gui/js/libraries/PlotlyUtilities.js b/html/gui/js/libraries/PlotlyUtilities.js new file mode 100644 index 0000000000..07199bd32f --- /dev/null +++ b/html/gui/js/libraries/PlotlyUtilities.js @@ -0,0 +1,152 @@ +/* generateChartOptions - Generates data array and layout dict for Plotly Chart + * ** Currently assumes that data is in format of a record returned in the JobViewer ** + * + * @param{dict} Record containing chart data + * + */ +function generateChartOptions(record){ + let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', + '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + let data = []; + let isEnvelope = false; + let tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); + let ymin, ymax; + ymin = record.data.series[0].data[0].y; + ymax = ymin; + + for (let sid = 0; sid < record.data.series.length; sid++) { + if (record.data.series[sid].name === 'Range') { + isEnvelope = true; + ymin = record.data.series[1].data[0].y; + ymax = ymin; + tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); + continue; + } + let x = []; + let y = []; + let qtip = []; + let color = colors[sid % 10]; + + for(let i = 0; i < record.data.series[sid].data.length; i++) { + x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS')); + y.push(record.data.series[sid].data[i].y); + qtip.push(record.data.series[sid].data[i].qtip); + } + + let trace = { + x: x, + y: y, + marker: { + size: 0.1, + color: color + }, + line: { + width: 2, + color: color + }, + text: qtip, + hovertemplate: + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y:}" + + "", + name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines' + }; + + if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ + trace['fill'] = 'tonexty'; + trace['fillcolor'] = '#5EA0E2'; + } + + if (isEnvelope){ + trace.hovertemplate = "[%{text}] " + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + + " " + record.data.series[sid].name + ": %{y:}" + + ""; + } + + if (record.data.series[sid].data.length === 1){ + trace.marker.size = 20; + trace.mode = 'markers'; + delete trace.line; + } + + data.push(trace); + const tempMin = Math.min(...y); + const tempMax = Math.max(...y); + if (tempMin < ymin) ymin = tempMin; + if (tempMax > ymax) ymax = tempMax; + } + + let layout = { + hoverlabel: { + bgcolor: '#ffffff' + }, + xaxis: { + title: '' + 'Time (' + record.data.schema.timezone + ')' + '', + titlefont: { + family: 'Open-Sans, verdana, arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + ticks: 'outside', + tickcolor: '#c0cfe0', + linecolor: '#c0cfe0', + automargin: true, + showgrid: false, + }, + yaxis: { + title: '' + record.data.schema.units + '', + titlefont: { + family: 'Open-Sans, verdana, arial, sans-serif', + size: 12, + color: '#5078a0' + }, + color: '#606060', + range: [0, ymax + (ymax * 0.2)], + showline: false, + zeroline: false, + gridcolor: '#d8d8d8', + automargin: true, + ticks: 'outside', + tickcolor: '#ffffff', + seperatethousands: true + }, + title: { + text: record.data.schema.description, + font: { + family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif', + color: '#444b6e', + size: 16 + }, + }, + annotations: [{ + text: record.data.schema.source + '. Powered by XDMoD/Plotly', + font: { + color: '#909090', + size: 10, + family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' + }, + xref: 'paper', + yref: 'paper', + xanchor: 'right', + yanchor: 'bottom', + x: 1, + y: 0, + yshift: -80, + showarrow: false + }], + hovermode: 'closest', + showlegend: false, + margin: { + t: 50 + } + }; + + let ret = { + chartData: data, + chartLayout: layout + }; + + return ret; +} + diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 87f0eb4151..8a9733ede7 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -17,36 +17,30 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { _DEFAULT_CONFIG: { delimiter: ':', colorSteps: [ - { - value: .25, - color: '#FF0000', - bg_color: 'rgb(255,102,102)' - }, - { - value: .50, - color: '#FFB336', - bg_color: 'rgb(255,255,156)' - - }, - { - value: .75, - color: '#DDDF00', - bg_color: 'rgb(255,255,102)' - }, - { - value: 1, - color: '#50B432', - bg_color: 'rgb(182,255,152)' - } + { + value: .25, + color: '#ff0000', + bg_color: 'rgb(255,102,102)' + }, + { + value: .50, + color: '#ffb336', + bg_color: 'rgb(255,255,156)' + + }, + { + value: .75, + color: '#dddf00', + bg_color: 'rgb(255,255,102)' + }, + { + value: 1, + color: '#50b432', + bg_color: 'rgb(182,255,152)' + } ], - - + layout: { - hoverlabel: { - bgcolor: 'white' - }, - paper_bgcolor: 'white', - plot_bgcolor: 'white', height: 65, xaxis: { showticklabels: false, @@ -56,51 +50,51 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { tick0: 0.0, dtick: 0.2, ticklen: 2, - tickcolor: 'white', + tickcolor: '#ffffff', gridcolor: '#c0c0c0', - linecolor: 'white', + linecolor: '#ffffff', zeroline : false, showgrid: true, - zerolinecolor: 'black', + zerolinecolor: '#000000', showline: false, zerolinewidth: 0, - fixedrange: true + fixedrange: true }, yaxis: { showticklabels: false, color: '#606060', showgrid : false, gridcolor: '#c0c0c0', - linecolor: 'white', + linecolor: '#ffffff', zeroline: false, - zerolinecolor: 'white', + zerolinecolor: '#ffffff', showline: false, rangemode: 'tozero', zerolinewidth: 0, - fixedrange: true + fixedrange: true }, hovermode: false, - shapes: [], + shapes: [], + images: [], + annotations: [], showlegend: false, margin: { - t: 12.5, - l: 7.5, - r: 7.5, - b: 12.5, + t: 10, + l: 9, + r: 13, + b: 10, pad: 0 } }, - traces: [], - config: { - displayModeBar: false, - }, - bgColors: [] + config: { + displayModeBar: false, + staticPlot: true }, + }, // The instance of Highcharts used as this components primary display. chart: null, - // private member that stores the error message object errorMsg: null, @@ -108,16 +102,8 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * This components 'constructor'. */ initComponent: function() { - - this.colorSteps = Ext.apply( - this.colorSteps || [], - this._DEFAULT_CONFIG.colorSteps - ); - - XDMoD.Module.JobViewer.AnalyticChartPanel.superclass.initComponent.call( - this - ); - + this.colorSteps = Ext.apply(this.colorSteps || [], this._DEFAULT_CONFIG.colorSteps); + XDMoD.Module.JobViewer.AnalyticChartPanel.superclass.initComponent.call(this); }, // initComponent listeners: { @@ -128,8 +114,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * a visible element to hook into are handled here. */ render: function() { - this.chart = true; - Plotly.newPlot(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + this.chart = Plotly.newPlot(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // render /** @@ -141,7 +126,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ update_data: function(data) { this._updateData(data); - //Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // update_data /** @@ -150,18 +134,17 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ reset: function() { if (this.chart) { - this._DEFAULT_CONFIG.traces = []; - Plotly.react(this.id, this._DEFAULT_CONFIG.traces, this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); + Plotly.react(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); } }, // reset beforedestroy: function () { if (this.chart) { - Plotly.purge(this.id); + Plotly.purge(this.id); + this.chart = null; } }, - /** * Attempt to resize this components HighCharts instance such that it * falls with in the new adjWidth and adjHeight. @@ -174,19 +157,57 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (this.chart) { - Plotly.relayout(this.id, {width: adjWidth}); - var elements = document.querySelectorAll('.bglayer'); - if (elements){ - for (var i=0; i < elements.length; i++){ - if (elements[i].firstChild){ - elements[i].firstChild.style.fill = this._DEFAULT_CONFIG.bgColors[i]; - } - } + const container = document.querySelector('#'+this.id); + const bgcolor = container._fullLayout.plot_bgcolor; + const annotation = structuredClone(container._fullLayout.annotations); + const image = structuredClone(container._fullLayout.images); + Plotly.relayout(this.id, {width: adjWidth}); + if (annotation.length > 0){ + Plotly.relayout(this.id, {images: image}); + Plotly.relayout(this.id, {annotations: annotation}); + let update = { + xaxis: { + showticklabels: false, + tickcolor: '#ffffff', + gidcolor: '#ffffff', + linecolor: '#ffffff', + zeroline : false, + showgrid: false, + zerolinecolor: '#000000', + showline: false, + zerolinewidth: 0 + } + }; + Plotly.relayout(this.id, update); + Plotly.relayout(this.id, {plot_bgcolor: bgcolor}); } - if (this.errorMsg) { - this.updateErrorMessage(this.errorMsg.text.textStr); + else { + Plotly.relayout(this.id, {images: []}); + Plotly.relayout(this.id, {annotations: []}); + let update = { + xaxis: { + showticklabels: false, + range: [0,1], + color: '#606060', + ticks: 'inside', + tick0: 0.0, + dtick: 0.2, + ticklen: 2, + tickcolor: '#ffffff', + gridcolor: '#c0c0c0', + linecolor: '#ffffff', + zeroline : false, + showgrid: true, + zerolinecolor: '#000000', + showline: false, + zerolinewidth: 0, + fixedrange: true + } + }; + Plotly.relayout(this.id, update); + Plotly.relayout(this.id, {plot_bgcolor: bgcolor}); } - } + } } // resize }, // listeners @@ -199,43 +220,44 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ updateErrorMessage: function (errorStr) { if (this.errorMsg) { - this.errorMsg.text.destroy(); - this.errorMsg.image.destroy(); this.errorMsg = null; } if (errorStr) { - this._DEFAULT_CONFIG.layout['images'] = [ - { - "source": '/gui/images/about_16.png', - "align": "left", - "xref": "paper", - "yref": "paper", - "sizex": 0.4, - "sizey": 0.4, - "x": 0, - "y": 1.2 - } - ] - this._DEFAULT_CONFIG.layout['annotations'] = [ - { - "text": '' + errorStr + '', - "align": "left", - "xref": "paper", - "yref": "paper", - "font":{ - "size": 11, - }, - "x" : 0.05, - "y" : 1.2, - "showarrow": false - } - ] - this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = false; + this.errorMsg = errorStr; + let errorImage = [ + { + source: '/gui/images/about_16.png', + align: 'left', + xref: 'paper', + yref: 'paper', + sizex: 0.4, + sizey: 0.4, + x: 0, + y: 1.2 + } + ]; + this._DEFAULT_CONFIG.layout.images = errorImage; + let errorText = [ + { + text: '' + errorStr + '', + align: 'left', + xref: 'paper', + yref: 'paper', + font:{ + size: 11, + }, + x : 0.05, + y : 1.2, + showarrow: false + } + ]; + this._DEFAULT_CONFIG.layout.annotations = errorText; + this._DEFAULT_CONFIG.layout.xaxis.showgrid = false; } else { - this._DEFAULT_CONFIG.layout['images'] = []; - this._DEFAULT_CONFIG.layout['annotations'] = []; - this._DEFAULT_CONFIG.layout['xaxis']['showgrid'] = true; + this._DEFAULT_CONFIG.layout.images = []; + this._DEFAULT_CONFIG.layout.annotations = []; + this._DEFAULT_CONFIG.layout.xaxis.showgrid = true; } }, @@ -247,37 +269,33 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _updateData: function(data) { - this._DEFAULT_CONFIG.traces = []; - - this._DEFAULT_CONFIG.layout['plot_bgcolor'] = 'white'; - var trace = {}; - if (data.error == '') { + let trace = {}; + if (data.error == '') { + let chartColor = this._getDataColor(data.value); + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; - var chartColor = this._getDataColor(data.value); - this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; - this._DEFAULT_CONFIG.bgColors.push(chartColor.bg_color); - trace = { - x: [data.value], - name: data.name ? data.name : '', - width: [0.5], - marker:{ - color: chartColor.color, - line:{ - color: 'white', - width: 1 - } - }, - type: 'bar', - orientation: 'h', - }; + x: [data.value], + name: data.name ? data.name : '', + width: [0.5], + marker:{ + color: chartColor.color, + line:{ + color: '#ffffff', + width: 1 + } + }, + type: 'bar', + orientation: 'h', + }; + } + else { + this._DEFAULT_CONFIG.layout['plot_bgcolor'] = '#ffffff'; } - this._DEFAULT_CONFIG.traces.push(trace); this.updateErrorMessage(data.error); this._updateTitle(data); this.ownerCt.doLayout(false, true); Plotly.react(this.id, [trace], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); - }, // _updateData /** @@ -299,7 +317,6 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _getDataColor: function(data) { - var ret = {}; var color = null; if (typeof data === 'number') { var steps = this.colorSteps; @@ -307,10 +324,10 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { for ( var i = 0; i < count; i++) { var step = steps[i]; if (data <= step.value) { - ret = { - color: step.color, - bg_color: step.bg_color - }; + let ret = { + color: step.color, + bg_color: step.bg_color + }; return ret; } } diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 7b127135ad..f31ad3de7b 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -7,18 +7,9 @@ Ext.namespace('XDMoD', 'XDMoD.Module', 'XDMoD.Module.JobViewer'); */ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { - // The default chart config options. - _DEFAULT_CONFIG: { - chartPrefix: 'CreateChartPanel', - }, - // The chart instance. chart: null, - chartWidth: null, - - chartHeight: null, - /** * The component 'constructor'. */ @@ -35,9 +26,9 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { // ADD: The custom events that we're listening for. this.addEvents( - 'load_record', - 'record_loaded' - ); + 'load_record', + 'record_loaded' + ); var self = this; @@ -95,15 +86,13 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { */ resize: function(panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (panel.chart) { - this.chartWidth = adjWidth; - this.chartWidth = adjHeight; Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); } }, // resize beforedestroy: function () { if (this.chart) { - Plotly.purge(this.id); + Plotly.purge(this.id); this.chart = false; } }, @@ -114,7 +103,30 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { print_clicked: function () { if (this.chart) { - this.chart.print(); + let chartDiv = document.querySelector('#' + this.id); + chartDiv = chartDiv.firstChild.firstChild; // parent div of the plotly SVGs + + // Make deep copy + const tmpWidth = structuredClone(chartDiv.clientWidth); + const tmpHeight = structuredClone(chartDiv.clientHeight); + + // Resize to 'medium' export width and height -- Currently placeholder width and height + Plotly.relayout(this.id, {width: 916, height: 484}); + + // Combine Plotly svg elements similar to export + let plotlyChart = chartDiv.children[0].outerHTML; + const plotlyLabels = chartDiv.children[2].innerHTML; + + plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); + let svg = plotlyChart + plotlyLabels + '' + + let printWindow = window.open(); + printWindow.document.write(' Printing '); + printWindow.document.write(svg); + printWindow.print(); + printWindow.close(); + + Plotly.relayout(this.id, {width: tmpWidth, height: tmpHeight}); } }, @@ -127,36 +139,9 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { var self = this; - var chartClickHandler = function(event) { - var userOptions = this.series.userOptions; - if (!userOptions || !userOptions.dtype) { - return; - } - var drilldown; - /* - * The drilldown data are stored on each point for envelope - * plots and for the series for simple plots. - */ - if (userOptions.dtype == 'index') { - drilldown = { - dtype: userOptions.index, - value: event.point.options[userOptions.index] - }; - } else { - drilldown = { - dtype: userOptions.dtype, - value: userOptions[userOptions.dtype] - }; - } - var path = self.path.concat([drilldown]); - var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); - Ext.History.add(token); - }; - if (record !== null && record !== undefined) { this.dataurl = record.store.proxy.url; this.displayTimezone = record.data.schema.timezone; - if (record.data.schema.help) { panel.helptext.documentation = record.data.schema.help; this.jobTab.fireEvent("display_help", panel.helptext); @@ -164,180 +149,48 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } if (record) { - let colorChoices = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', - '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - let data = []; - var isEnvelope = false; - var hasSingleDataPoint = false; - var tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); - var ymin, ymax; - ymin = record.data.series[0].data[0].y; - ymax = ymin; - for (let sid = 0; sid < record.data.series.length; sid++) { - if (record.data.series[sid].name === "Range") { - isEnvelope = true; - ymin = record.data.series[1].data[0].y; - ymax = ymin; - tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); - continue; - } - let x = []; - let y = []; - let qtip = []; - let colors = colorChoices[sid % 10]; - for(let i=0; i < record.data.series[sid].data.length; i++) { - if (record.data.series[sid].data.length == 1){ - hasSingleDataPoint = true; - } - x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS ')); - y.push(record.data.series[sid].data[i].y); - qtip.push(record.data.series[sid].data[i].qtip); - } - - if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ - data.push({ - x: x, - y: y, - fill: 'tonexty', - fillcolor: '#5EA0E2', - marker: { - size: 0.1, - color: colors - }, - line: { - width: 2, - color: colors - }, - text: qtip, - hovertemplate: "[%{text}] " + - "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ": %{y: .f}" + - "", - name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}); - } - else{ - var trace = { - x: x, - y: y, - marker: { - size: 0.1, - color: colors - }, - line: { - width: 2, - color: colors - }, - text: qtip, - hovertemplate: - "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ": %{y: .f}" + - "", - name: record.data.series[sid].name, chartSeries: record.data.series[sid], type: 'scatter', mode: 'markers+lines'}; - - if (isEnvelope){ - trace.hovertemplate = "[%{text}] " + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + - " " + record.data.series[sid].name + ": %{y: .f}" + - ""; - } - if (hasSingleDataPoint){ - trace.marker.size = 20; - trace.mode = 'markers'; - delete trace.line; - } - data.push(trace); - } - var tempyMin = Math.min(...y); - var tempyMax = Math.max(...y); - if (tempyMin < ymin) ymin = tempyMin; - if (tempyMax > ymax) ymax = tempyMax; - } - + let chartOptions = generateChartOptions(record); panel.getEl().unmask(); - let layout = { - hoverlabel: { - bgcolor: 'white' - }, - xaxis: { - title: 'Time (' + record.data.schema.timezone + ')', - titlefont: { - family: 'Arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - ticks: 'outside', - ticklen: 10, - tickcolor: '#c0cfe0', - linecolor: '#c0cfe0', - automargin: true, - showgrid: false - }, - yaxis: { - title: '' + record.data.schema.units + '', - titlefont: { - family: 'Arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - range: [0, ymax + (ymax * 0.2)], - rangemode: 'nonnegative', - gridcolor: 'lightgray', - automargin: true, - linecolor: '#c0cfe0' - }, - title: { - text: record.data.schema.description, - font: { - color: '#444b6e', - size: 16 - } - }, - hovermode: 'closest', - showlegend: false, - margin: { - t: 50 - } - }; + if (panel.chart) { - Plotly.react(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'} ); + Plotly.react(this.id, chartOptions.chartData, chartOptions.chartLayout, {displayModeBar: false, doubleClick: 'reset'} ); this.chart = true; } else { - Plotly.newPlot(this.id, data, layout, {displayModeBar: false, doubleClick: 'reset'} ); + Plotly.newPlot(this.id, chartOptions.chartData, chartOptions.chartLayout, {displayModeBar: false, doubleClick: 'reset'} ); this.chart = true; } - - if (this.chart) { + if (panel.chart) { panel.chart = document.getElementById(this.id); panel.chart.on('plotly_click', function(data, event){ - var userOptions = data.points[0].data.chartSeries + const userOptions = data.points[0].data.chartSeries; if (!userOptions || !userOptions.dtype) { return; } - var drilldown; + let drilldown; /* * The drilldown data are stored on each point for envelope * plots and for the series for simple plots. */ if (userOptions.dtype == 'index') { - var nodeidIndex = data.points[0].data.chartSeries.data.findIndex(({y}) => y === data.points[0].y); + const nodeidIndex = data.points[0].pointIndex; if (nodeidIndex === -1) return; drilldown = { dtype: userOptions.index, value: userOptions.data[nodeidIndex].nodeid }; - } else { + } + else { drilldown = { dtype: userOptions.dtype, value: userOptions[userOptions.dtype] }; } - var path = self.path.concat([drilldown]); - var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); + const path = self.path.concat([drilldown]); + const token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); Ext.History.add(token); - }); - } - } + }); + } + } if (!record) { panel.getEl().mask('Loading...'); diff --git a/html/gui/js/modules/job_viewer/ChartTab.js b/html/gui/js/modules/job_viewer/ChartTab.js index 46d898d13d..48bc865e7b 100644 --- a/html/gui/js/modules/job_viewer/ChartTab.js +++ b/html/gui/js/modules/job_viewer/ChartTab.js @@ -37,68 +37,7 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { var self = this; var createChart = function () { - var defaultChartSettings = { - chart: { - renderTo: self.id + '_hc' - }, - colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a'], - title: { - style: { - color: '#444b6e', - fontSize: '16px' - }, - text: '' - }, - loading: { - style: { - opacity: 0.7 - } - }, - yAxis: { - title: { - style: { - fontWeight: 'bold', - color: '#5078a0' - } - } - }, - legend: { - enabled: false - }, - exporting: { - enabled: false - }, - tooltip: { - pointFormat: ' {series.name}: {point.low:%A, %b %e, %H:%M:%S} - {point.high:%A, %b %e, %H:%M:%S}
', - dateTimeLabelFormats: { - millisecond: '%A, %b %e, %H:%M:%S.%L %T', - second: '%A, %b %e, %H:%M:%S %T', - minute: '%A, %b %e, %H:%M:%S %T', - hour: '%A, %b %e, %H:%M:%S %T' - } - }, - plotOptions: { - line: { - marker: { - enabled: false - } - }, - columnrange: { - minPointLength: 3, - animation: false, - dataLabels: { - enabled: false - } - }, - series: { - allowPointSelect: false, - animation: false - } - } - }; - - var chartOptions = jQuery.extend(true, {}, defaultChartSettings, self.chartSettings); - + this.chart = Plotly.newPlot(this.id, [], [], {displayModeBar: false, doubleClick: 'reset'}); var storeParams; if (self.panelSettings.pageSize) { storeParams = { @@ -148,9 +87,9 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { xtype: 'container', id: this.id + '_hc', listeners: { - resize: function () { - if (self.chart) { - self.chart.reflow(); + resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { + if (this.chart) { + Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); } }, render: createChart @@ -180,7 +119,8 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { beforedestroy: function () { if (this.chart) { Plotly.purge(this.id); + this.chart = false; } - } + }, } }); diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index a098ba20f0..0e06e6d2b9 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -3,20 +3,6 @@ Ext.namespace('XDMoD', 'XDMoD.Module', 'XDMoD.Module.JobViewer'); XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, { initComponent: function () { - this.chartSettings = { - chart: { - type: 'columnrange', - grouping: false, - inverted: true - }, - yAxis: { - type: 'datetime', - minTickInterval: 1000, - title: { - text: 'Time (' + self.displayTimezone + ')' - } - } - }; this.panelSettings = { pageSize: 11, @@ -26,140 +12,151 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, fields: ['series', 'schema', 'categories'] } }; - + XDMoD.Module.JobViewer.GanttChart.superclass.initComponent.call(this, arguments); - + this.addListener('updateChart', function (store) { - if (store.getCount() < 1) { + if (store.getCount() < 1) { return; - } + } - var record = store.getAt(0); - var i; - var data = []; - var categories = []; - var count = 0; - var rect = []; - var yvals = [0]; - let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - for (i = 0; i < record.data.series.length; i++) { - var j = 0; - for (j = 0; j < record.data.series[i].data.length; j++){ - low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - categories.push(record.data.categories[count]); - var runtime = []; - let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; - var tooltip = [template]; - var ticks = [count]; - var start_time = record.data.series[i].data[j].low; - // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. - // Points on the scatter plot are created every min to create better coverage for tooltip information - while (start_time < record.data.series[i].data[j].high){ - ticks.push(count); - tooltip.push(template); - runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); - start_time += (60 * 1000); - } - runtime.push(high_data); - - rect.push({ - x0: low_data, - x1: high_data, - y0: count-0.25, - y1: count+0.25, - type: 'rect', - xref: 'x', - yref: 'y', - fillcolor: colors[i % 10], - //color: colors[i % 10] - }); + var record = store.getAt(0); + let data = []; + let categories = []; + let count = 0; + let rect = []; + let yvals = []; + let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + for (let i = 0; i < record.data.series.length; i++) { + for (let j = 0; j < record.data.series[i].data.length; j++){ + let low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + let high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + categories.push(record.data.categories[count]); + let runtime = []; + let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; + let tooltip = [template]; + let ticks = [count]; + let start_time = record.data.series[i].data[j].low; + // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. + // Points on the scatter plot are created every min to create better coverage for tooltip information + while (start_time < record.data.series[i].data[j].high){ + ticks.push(count); + tooltip.push(template); + runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); + start_time += (60 * 1000); + } + runtime.push(high_data); - - var info = {} + rect.push({ + x0: low_data, + x1: high_data, + y0: count-0.175, + y1: count+0.175, + type: 'rect', + xref: 'x', + yref: 'y', + fillcolor: colors[i % 10], + }); - if (i > 0){ - info = { - realm: record.data.series[i].data[j].ref.realm, - recordid: store.baseParams.recordid, - jobref: record.data.series[i].data[j].ref.jobid, - infoid: 3 - }; - } - - peer = { - x: runtime, - y: ticks, + let info = {}; + if (i > 0){ + info = { + realm: record.data.series[i].data[j].ref.realm, + recordid: store.baseParams.recordid, + jobref: record.data.series[i].data[j].ref.jobid, + infoid: 3 + }; + } + let peer = { + x: runtime, + y: ticks, type: 'scatter', - - marker:{ - color: 'rgb(255,255,255)' - }, - orientation: 'h', - hovertemplate: record.data.categories[count] + '
'+ - "" + '%{text} ', - text: tooltip, - chartSeries: info + marker:{ + color: 'rgb(255,255,255)', + size: 20 + }, + orientation: 'h', + hovertemplate: record.data.categories[count] + '
'+ + "" + '%{text} ', + text: tooltip, + chartSeries: info }; - data.push(peer); - count++; - yvals.push(count); + data.push(peer); + yvals.push(count); + count++; + } + } - - } - let layout = { - hoverlabel: { - bgcolor: 'white' - }, - xaxis: { - title: 'Time (' + record.data.schema.timezone + ')', - titlefont: { - family: 'Arial, sans-serif', - size: 12, - color: '#5078a0' - }, - color: '#606060', - ticks: 'outside', - tickcolor: '#c0cfe0', - linecolor: '#c0cfe0', - type: 'date', - range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] + let layout = { + hoverlabel: { + bgcolor: '#ffffff' + }, + xaxis: { + title: 'Time (' + record.data.schema.timezone + ')', + titlefont: { + family: 'Arial, sans-serif', + size: 12, + color: '#5078a0' }, - yaxis: { - autorange: 'reversed', - ticktext: categories, - zeroline: false, - tickvals: yvals - }, - title: { - text: record.data.schema.description, - font: { - color: '#444b6e', - size: 16 - } - }, - hovermode: 'closest', - showlegend: false, - margin: { - l: 175, - b: 150 - + color: '#606060', + zeroline: false, + gridcolor: '#d8d8d8', + type: 'date', + range: [record.data.series[0].data[0].low - (60 * 1000), record.data.series[0].data[0].high + (60 * 1000)] + }, + yaxis: { + autorange: 'reversed', + ticktext: categories, + zeroline: false, + showgrid: false, + showline: true, + linecolor: '#c0cfe0', + ticks: 'outside', + tickcolor: '#c0cfe0', + tickvals: yvals + }, + title: { + text: record.data.schema.description, + font: { + color: '#444b6e', + size: 16 } - }; + }, + hovermode: 'closest', + annotations: [{ + text: 'Powered by XDMoD/Plotly', + font:{ + color: '#909090', + size: 10, + family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' + }, + xref: 'paper', + yref: 'paper', + xanchor: 'right', + yanchor: 'bottom', + x: 1, + y: 0, + yshift: -80, + showarrow: false + }], + showlegend: false, + margin: { + t: 50, + l: 180, + } + }; - layout['shapes'] = rect; - Plotly.newPlot(this.id, data, layout, {displayModeBar: false}); + layout['shapes'] = rect; + Plotly.react(this.id + '_hc', data, layout, {displayModeBar: false, doubleClick: 'reset'}); - - var panel = document.getElementById(this.id); - panel.on('plotly_click', function(data){ - var userOptions = data.points[0].data.chartSeries; - userOptions['action'] = 'show'; - Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); + const panel = document.getElementById(this.id + '_hc'); + panel.on('plotly_click', function(data){ + const userOptions = data.points[0].data.chartSeries; + userOptions['action'] = 'show'; + Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); + }); - }); - }); } }); diff --git a/html/index.php b/html/index.php index 689536eeac..48c2c03e33 100644 --- a/html/index.php +++ b/html/index.php @@ -162,6 +162,7 @@ function isReferrer($referrer) + @@ -421,7 +422,7 @@ function ($item) { - + diff --git a/html/plotly_template.html b/html/plotly_template.html index d7fc7bff09..61d48ba1f8 100644 --- a/html/plotly_template.html +++ b/html/plotly_template.html @@ -1,163 +1,56 @@ - - + + - - Plotly Template + + Plotly Template - - - + + + + + + - + - - - + - -
- - - + + diff --git a/libraries/charting.php b/libraries/charting.php index 5a3366fe1a..fbcc08f357 100644 --- a/libraries/charting.php +++ b/libraries/charting.php @@ -42,18 +42,15 @@ function exportHighchart( $format, $globalChartConfig = null, $fileMetadata = null, - $isPlotly = false + $isPlotly = false ) { $effectiveWidth = (int)($width*$scale); $effectiveHeight = (int)($height*$scale); $html_dir = __DIR__ . "/../html"; - $template; + $template = file_get_contents($html_dir . "/highchart_template.html"); if ($isPlotly){ - $template = file_get_contents($html_dir . "/plotly_template.html"); - } - else{ - $template = file_get_contents($html_dir . "/highchart_template.html"); + $template = file_get_contents($html_dir . "/plotly_template.html"); } $template = str_replace('_html_dir_', $html_dir, $template); @@ -64,9 +61,9 @@ function exportHighchart( $globalChartOptions = array_merge($globalChartOptions, $globalChartConfig); } $template = str_replace('_globalChartOptions_', json_encode($globalChartOptions), $template); - $template = str_replace('_chartOptions_', json_encode($chartConfig), $template); - $svg = getSvgViaChromiumHelper($template, $effectiveWidth, $effectiveHeight); + + $svg = getSvgViaChromiumHelper($template, $effectiveWidth, $effectiveHeight, $isPlotly); switch($format){ case 'png': return convertSvg($svg, 'png', $effectiveWidth, $effectiveHeight, $fileMetadata); @@ -90,7 +87,7 @@ function exportHighchart( * * @throws \Exception on invalid format, command execution failure, or non zero exit status */ -function getSvgViaChromiumHelper($html, $width, $height){ +function getSvgViaChromiumHelper($html, $width, $height, $isPlotly){ // Chromium requires the file to have a .html extension // cant use datauri as it will not execute embdeeded javascript @@ -107,7 +104,8 @@ function getSvgViaChromiumHelper($html, $width, $height){ $command = LIB_DIR . '/chrome-helper/chrome-helper.js' . ' --window-size=' . $width . ',' . $height . ' --path-to-chrome=' . $chromiumPath . - ' --input-file=' . $tmpHtmlFile; + ' --input-file=' . $tmpHtmlFile . + ' --plotly=' . $isPlotly; $pipes = array(); $descriptor_spec = array( @@ -126,7 +124,7 @@ function getSvgViaChromiumHelper($html, $width, $height){ fclose($pipes[2]); $return_value = proc_close($process); - //@unlink($tmpHtmlFile); + @unlink($tmpHtmlFile); $chartSvg = json_decode($out); @@ -167,6 +165,9 @@ function convertSvg($svgData, $format, $width, $height, $docmeta){ } $rsvgCommand = 'rsvg-convert -w ' .$width. ' -h '.$height.' -f ' . $format; + if ($format == 'png'){ + $rsvgCommand = 'rsvg-convert -b "#FFFFFF" -w ' .$width. ' -h '.$height.' -f ' . $format; + } $exifCommand = 'exiftool ' . $exifArgs . ' -o - -'; $command = $rsvgCommand . ' | ' . $exifCommand; From b8d75eccbfa297f6fc08c3de2f5eef5e44ca09b5 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Thu, 15 Jun 2023 22:57:56 -0400 Subject: [PATCH 29/47] Fix complexity code smell --- html/gui/js/libraries/PlotlyUtilities.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/html/gui/js/libraries/PlotlyUtilities.js b/html/gui/js/libraries/PlotlyUtilities.js index 07199bd32f..b65876e057 100644 --- a/html/gui/js/libraries/PlotlyUtilities.js +++ b/html/gui/js/libraries/PlotlyUtilities.js @@ -13,8 +13,14 @@ function generateChartOptions(record){ let ymin, ymax; ymin = record.data.series[0].data[0].y; ymax = ymin; - - for (let sid = 0; sid < record.data.series.length; sid++) { + let sid = 0; + if (record.data.series[sid].name === 'Range') { + sid = 1; + isEnvelope = true; + ymin = record.data.series[1].data[0].y; + ymax = ymin; + } + for (sid = 0; sid < record.data.series.length; sid++) { if (record.data.series[sid].name === 'Range') { isEnvelope = true; ymin = record.data.series[1].data[0].y; From 7be88a70a76a074ef3814ee3bbd01d8fe3e3fef3 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Thu, 15 Jun 2023 23:05:28 -0400 Subject: [PATCH 30/47] Fix code smell --- html/gui/js/libraries/PlotlyUtilities.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/html/gui/js/libraries/PlotlyUtilities.js b/html/gui/js/libraries/PlotlyUtilities.js index b65876e057..c74cbcb884 100644 --- a/html/gui/js/libraries/PlotlyUtilities.js +++ b/html/gui/js/libraries/PlotlyUtilities.js @@ -15,19 +15,12 @@ function generateChartOptions(record){ ymax = ymin; let sid = 0; if (record.data.series[sid].name === 'Range') { - sid = 1; + sid++; isEnvelope = true; ymin = record.data.series[1].data[0].y; ymax = ymin; } - for (sid = 0; sid < record.data.series.length; sid++) { - if (record.data.series[sid].name === 'Range') { - isEnvelope = true; - ymin = record.data.series[1].data[0].y; - ymax = ymin; - tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[1].data[0].x); - continue; - } + for (sid; sid < record.data.series.length; sid++) { let x = []; let y = []; let qtip = []; From afb8671c81d30b36aebc1716e9fa37d70187599b Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Fri, 16 Jun 2023 12:59:51 -0400 Subject: [PATCH 31/47] Fix open string for plotly lib --- composer-el7.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer-el7.lock b/composer-el7.lock index 627a8b819f..995238a282 100644 --- a/composer-el7.lock +++ b/composer-el7.lock @@ -1587,7 +1587,7 @@ }, { "name": "plotly/plotly", - "version": "2.24.2, + "version": "2.24.2", "dist": { "type": "file", "url": "https://cdn.plot.ly/plotly-2.24.2.min.js", From a31a418518b0f515b74a625d98066331e3c4538e Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Fri, 16 Jun 2023 13:14:41 -0400 Subject: [PATCH 32/47] Change composer file when upgrading to composer-el7.json instead of just composer.json --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 08314550e0..8c8a9dd16b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -101,7 +101,7 @@ jobs: - run: name: Make sure that we reset COMPOSER when upgrading. command: | - echo "export COMPOSER=composer.json" >> $BASH_ENV + echo "export COMPOSER=composer-el7.json" >> $BASH_ENV - run: name: Setup & Run QA Tests command: | From 2c47f16a1dfdb58433ff8459cd8f6d5db55c1186 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 20 Jun 2023 11:11:53 -0400 Subject: [PATCH 33/47] Revert "Change composer file when upgrading to composer-el7.json instead of just composer.json" This reverts commit a31a418518b0f515b74a625d98066331e3c4538e. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8c8a9dd16b..08314550e0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -101,7 +101,7 @@ jobs: - run: name: Make sure that we reset COMPOSER when upgrading. command: | - echo "export COMPOSER=composer-el7.json" >> $BASH_ENV + echo "export COMPOSER=composer.json" >> $BASH_ENV - run: name: Setup & Run QA Tests command: | From a268968354f7b55103bfea59412c61f675327fcb Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 20 Jun 2023 14:21:20 -0400 Subject: [PATCH 34/47] Adjust JS linter to not throw errors on ES6 syntax --- tests/regression/.eslintrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/regression/.eslintrc.json b/tests/regression/.eslintrc.json index 10d2238837..13e216f583 100644 --- a/tests/regression/.eslintrc.json +++ b/tests/regression/.eslintrc.json @@ -1,5 +1,6 @@ { "env": { - "node": true + "node": true, + "es6": true } } From f99f8aefbdec6fd3e3df85d1546534df7506df35 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 20 Jun 2023 14:34:29 -0400 Subject: [PATCH 35/47] Minor adjustment to ES6 linting --- tests/regression/.eslintrc.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/regression/.eslintrc.json b/tests/regression/.eslintrc.json index 13e216f583..c4dc213c8a 100644 --- a/tests/regression/.eslintrc.json +++ b/tests/regression/.eslintrc.json @@ -1,4 +1,7 @@ { + "parserOptions": { + "ecmaVersion": '2017' + }, "env": { "node": true, "es6": true From 088b0f4d11e11bca001080e799da054cb1cd90cd Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 20 Jun 2023 15:10:17 -0400 Subject: [PATCH 36/47] Revert "Minor adjustment to ES6 linting" This reverts commit f99f8aefbdec6fd3e3df85d1546534df7506df35. --- tests/regression/.eslintrc.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/regression/.eslintrc.json b/tests/regression/.eslintrc.json index c4dc213c8a..13e216f583 100644 --- a/tests/regression/.eslintrc.json +++ b/tests/regression/.eslintrc.json @@ -1,7 +1,4 @@ { - "parserOptions": { - "ecmaVersion": '2017' - }, "env": { "node": true, "es6": true From 981909386aa1ff1c3a1fce7c897bf6508b7b6732 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 20 Jun 2023 15:10:18 -0400 Subject: [PATCH 37/47] Revert "Adjust JS linter to not throw errors on ES6 syntax" This reverts commit a268968354f7b55103bfea59412c61f675327fcb. --- tests/regression/.eslintrc.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/regression/.eslintrc.json b/tests/regression/.eslintrc.json index 13e216f583..10d2238837 100644 --- a/tests/regression/.eslintrc.json +++ b/tests/regression/.eslintrc.json @@ -1,6 +1,5 @@ { "env": { - "node": true, - "es6": true + "node": true } } From 457c69589b3fb55db66df1d42ebc541bff304eea Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 20 Jun 2023 15:36:14 -0400 Subject: [PATCH 38/47] Make JS linter happy --- .../chrome-helper/chrome-helper.js | 11 +++-- .../WarehouseControllerProvider.php | 2 +- html/gui/js/libraries/PlotlyUtilities.js | 7 ++- .../modules/job_viewer/AnalyticChartPanel.js | 22 +++++----- html/gui/js/modules/job_viewer/ChartPanel.js | 37 ++++++++-------- html/gui/js/modules/job_viewer/ChartTab.js | 6 +-- html/gui/js/modules/job_viewer/GanttChart.js | 44 +++++++++---------- libraries/charting.php | 2 +- 8 files changed, 64 insertions(+), 67 deletions(-) diff --git a/background_scripts/chrome-helper/chrome-helper.js b/background_scripts/chrome-helper/chrome-helper.js index db1b8fa811..27e2685f81 100755 --- a/background_scripts/chrome-helper/chrome-helper.js +++ b/background_scripts/chrome-helper/chrome-helper.js @@ -28,12 +28,11 @@ const args = require('yargs').argv; // Chart title and axis titles svg const plotlyLabels = await page.evaluate(() => document.querySelector('.user-select-none.svg-container').children[2].innerHTML); - plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); - const plotlyImage = plotlyChart + "" + plotlyLabels + ""; - - svgInnerHtml = plotlyImage.replace(/
||<\/b>/gm,""); //HTML tags in titles throw xml error - } - else { + plotlyChart = plotlyChart.substring(0, plotlyChart.length - 6); + const plotlyImage = plotlyChart + '' + plotlyLabels + ''; + // HTML tags in titles thorw xml not well-formed error + svgInnerHtml = plotlyImage.replace(/
||<\/b>/gm, ''); + } else { svgInnerHtml = await page.evaluate(() => document.querySelector('.highcharts-container').innerHTML); } diff --git a/classes/Rest/Controllers/WarehouseControllerProvider.php b/classes/Rest/Controllers/WarehouseControllerProvider.php index 51ca7f35a5..dde2f3353b 100644 --- a/classes/Rest/Controllers/WarehouseControllerProvider.php +++ b/classes/Rest/Controllers/WarehouseControllerProvider.php @@ -1986,7 +1986,7 @@ private function chartImageResponse($data, $type, $settings) 'axisTickSize' => $axisLabelFontSize, 'axisTitleSize' => $axisTitleFontSize, 'lineWidth' => $lineWidth, - 'chartTitleSize' => $mainTitleFontSize + 'chartTitleSize' => $mainTitleFontSize ); $globalConfig = array( diff --git a/html/gui/js/libraries/PlotlyUtilities.js b/html/gui/js/libraries/PlotlyUtilities.js index c74cbcb884..bd561e44a9 100644 --- a/html/gui/js/libraries/PlotlyUtilities.js +++ b/html/gui/js/libraries/PlotlyUtilities.js @@ -19,7 +19,7 @@ function generateChartOptions(record){ isEnvelope = true; ymin = record.data.series[1].data[0].y; ymax = ymin; - } + } for (sid; sid < record.data.series.length; sid++) { let x = []; let y = []; @@ -54,7 +54,7 @@ function generateChartOptions(record){ if (record.data.series[sid].name === "Median" || record.data.series[sid].name === "Minimum"){ trace['fill'] = 'tonexty'; trace['fillcolor'] = '#5EA0E2'; - } + } if (isEnvelope){ trace.hovertemplate = "[%{text}] " + "%{x|%A, %b %e, %H:%M:%S.%L} " + tz + "
" + @@ -66,7 +66,7 @@ function generateChartOptions(record){ trace.marker.size = 20; trace.mode = 'markers'; delete trace.line; - } + } data.push(trace); const tempMin = Math.min(...y); @@ -148,4 +148,3 @@ function generateChartOptions(record){ return ret; } - diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 8a9733ede7..bcf607581e 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -157,15 +157,15 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (this.chart) { - const container = document.querySelector('#'+this.id); - const bgcolor = container._fullLayout.plot_bgcolor; - const annotation = structuredClone(container._fullLayout.annotations); - const image = structuredClone(container._fullLayout.images); + var container = document.querySelector('#'+this.id); + var bgcolor = container._fullLayout.plot_bgcolor; + var annotation = structuredClone(container._fullLayout.annotations); + var image = structuredClone(container._fullLayout.images); Plotly.relayout(this.id, {width: adjWidth}); if (annotation.length > 0){ Plotly.relayout(this.id, {images: image}); Plotly.relayout(this.id, {annotations: annotation}); - let update = { + var update = { xaxis: { showticklabels: false, tickcolor: '#ffffff', @@ -184,7 +184,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { else { Plotly.relayout(this.id, {images: []}); Plotly.relayout(this.id, {annotations: []}); - let update = { + var update = { xaxis: { showticklabels: false, range: [0,1], @@ -224,7 +224,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { } if (errorStr) { this.errorMsg = errorStr; - let errorImage = [ + var errorImage = [ { source: '/gui/images/about_16.png', align: 'left', @@ -237,7 +237,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { } ]; this._DEFAULT_CONFIG.layout.images = errorImage; - let errorText = [ + var errorText = [ { text: '' + errorStr + '', align: 'left', @@ -269,9 +269,9 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @private */ _updateData: function(data) { - let trace = {}; + var trace = {}; if (data.error == '') { - let chartColor = this._getDataColor(data.value); + var chartColor = this._getDataColor(data.value); this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; trace = { @@ -324,7 +324,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { for ( var i = 0; i < count; i++) { var step = steps[i]; if (data <= step.value) { - let ret = { + var ret = { color: step.color, bg_color: step.bg_color }; diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index f31ad3de7b..5746fcf8c5 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -86,7 +86,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { */ resize: function(panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (panel.chart) { - Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); + Plotly.relayout(this.id, { width: adjWidth, height: adjHeight }); } }, // resize @@ -103,30 +103,30 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { print_clicked: function () { if (this.chart) { - let chartDiv = document.querySelector('#' + this.id); + var chartDiv = document.querySelector('#' + this.id); chartDiv = chartDiv.firstChild.firstChild; // parent div of the plotly SVGs // Make deep copy - const tmpWidth = structuredClone(chartDiv.clientWidth); - const tmpHeight = structuredClone(chartDiv.clientHeight); + var tmpWidth = structuredClone(chartDiv.clientWidth); + var tmpHeight = structuredClone(chartDiv.clientHeight); // Resize to 'medium' export width and height -- Currently placeholder width and height Plotly.relayout(this.id, {width: 916, height: 484}); // Combine Plotly svg elements similar to export - let plotlyChart = chartDiv.children[0].outerHTML; - const plotlyLabels = chartDiv.children[2].innerHTML; + var plotlyChart = chartDiv.children[0].outerHTML; + var plotlyLabels = chartDiv.children[2].innerHTML; plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); - let svg = plotlyChart + plotlyLabels + '' + var svg = plotlyChart + plotlyLabels + '' - let printWindow = window.open(); + var printWindow = window.open(); printWindow.document.write(' Printing '); printWindow.document.write(svg); printWindow.print(); printWindow.close(); - Plotly.relayout(this.id, {width: tmpWidth, height: tmpHeight}); + Plotly.relayout(this.id, { width: tmpWidth, height: tmpHeight }); } }, @@ -149,44 +149,43 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { } if (record) { - let chartOptions = generateChartOptions(record); + var chartOptions = generateChartOptions(record); panel.getEl().unmask(); if (panel.chart) { - Plotly.react(this.id, chartOptions.chartData, chartOptions.chartLayout, {displayModeBar: false, doubleClick: 'reset'} ); + Plotly.react(this.id, chartOptions.chartData, chartOptions.chartLayout, { displayModeBar: false, doubleClick: 'reset' } ); this.chart = true; } else { - Plotly.newPlot(this.id, chartOptions.chartData, chartOptions.chartLayout, {displayModeBar: false, doubleClick: 'reset'} ); + Plotly.newPlot(this.id, chartOptions.chartData, chartOptions.chartLayout, { displayModeBar: false, doubleClick: 'reset' } ); this.chart = true; } if (panel.chart) { panel.chart = document.getElementById(this.id); panel.chart.on('plotly_click', function(data, event){ - const userOptions = data.points[0].data.chartSeries; + var userOptions = data.points[0].data.chartSeries; if (!userOptions || !userOptions.dtype) { return; } - let drilldown; + var drilldown; /* * The drilldown data are stored on each point for envelope * plots and for the series for simple plots. */ if (userOptions.dtype == 'index') { - const nodeidIndex = data.points[0].pointIndex; + var nodeidIndex = data.points[0].pointIndex; if (nodeidIndex === -1) return; drilldown = { dtype: userOptions.index, value: userOptions.data[nodeidIndex].nodeid }; - } - else { + } else { drilldown = { dtype: userOptions.dtype, value: userOptions[userOptions.dtype] }; } - const path = self.path.concat([drilldown]); - const token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); + var path = self.path.concat([drilldown]); + var token = self.jobViewer.module_id + '?' + self.jobViewer._createHistoryTokenFromArray(path); Ext.History.add(token); }); } diff --git a/html/gui/js/modules/job_viewer/ChartTab.js b/html/gui/js/modules/job_viewer/ChartTab.js index 48bc865e7b..bdad293509 100644 --- a/html/gui/js/modules/job_viewer/ChartTab.js +++ b/html/gui/js/modules/job_viewer/ChartTab.js @@ -37,7 +37,7 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { var self = this; var createChart = function () { - this.chart = Plotly.newPlot(this.id, [], [], {displayModeBar: false, doubleClick: 'reset'}); + this.chart = Plotly.newPlot(this.id, [], [], { displayModeBar: false, doubleClick: 'reset' }); var storeParams; if (self.panelSettings.pageSize) { storeParams = { @@ -89,7 +89,7 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { listeners: { resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (this.chart) { - Plotly.relayout(this.id, {width: adjWidth, height: adjHeight}); + Plotly.relayout(this.id, { width: adjWidth, height: adjHeight }); } }, render: createChart @@ -121,6 +121,6 @@ XDMoD.Module.JobViewer.ChartTab = Ext.extend(Ext.Panel, { Plotly.purge(this.id); this.chart = false; } - }, + } } }); diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index 0e06e6d2b9..37fcbeabe1 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -21,22 +21,22 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, } var record = store.getAt(0); - let data = []; - let categories = []; - let count = 0; - let rect = []; - let yvals = []; - let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - for (let i = 0; i < record.data.series.length; i++) { - for (let j = 0; j < record.data.series[i].data.length; j++){ - let low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); - let high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + var data = []; + var categories = []; + var count = 0; + var rect = []; + var yvals = []; + var colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; + for (var i = 0; i < record.data.series.length; i++) { + for (var j = 0; j < record.data.series[i].data.length; j++){ + var low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); + var high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); categories.push(record.data.categories[count]); - let runtime = []; - let template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; - let tooltip = [template]; - let ticks = [count]; - let start_time = record.data.series[i].data[j].low; + var runtime = []; + var template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; + var tooltip = [template]; + var ticks = [count]; + var start_time = record.data.series[i].data[j].low; // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. // Points on the scatter plot are created every min to create better coverage for tooltip information while (start_time < record.data.series[i].data[j].high){ @@ -58,7 +58,7 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, fillcolor: colors[i % 10], }); - let info = {}; + var info = {}; if (i > 0){ info = { realm: record.data.series[i].data[j].ref.realm, @@ -67,7 +67,7 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, infoid: 3 }; } - let peer = { + var peer = { x: runtime, y: ticks, type: 'scatter', @@ -88,7 +88,7 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, } } - let layout = { + var layout = { hoverlabel: { bgcolor: '#ffffff' }, @@ -108,7 +108,7 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, yaxis: { autorange: 'reversed', ticktext: categories, - zeroline: false, + zeroline: false, showgrid: false, showline: true, linecolor: '#c0cfe0', @@ -150,12 +150,12 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, layout['shapes'] = rect; Plotly.react(this.id + '_hc', data, layout, {displayModeBar: false, doubleClick: 'reset'}); - const panel = document.getElementById(this.id + '_hc'); + var panel = document.getElementById(this.id + '_hc'); panel.on('plotly_click', function(data){ - const userOptions = data.points[0].data.chartSeries; + var userOptions = data.points[0].data.chartSeries; userOptions['action'] = 'show'; Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); - }); + }); }); } diff --git a/libraries/charting.php b/libraries/charting.php index fbcc08f357..77f0df9c9d 100644 --- a/libraries/charting.php +++ b/libraries/charting.php @@ -50,7 +50,7 @@ function exportHighchart( $html_dir = __DIR__ . "/../html"; $template = file_get_contents($html_dir . "/highchart_template.html"); if ($isPlotly){ - $template = file_get_contents($html_dir . "/plotly_template.html"); + $template = file_get_contents($html_dir . "/plotly_template.html"); } $template = str_replace('_html_dir_', $html_dir, $template); From 8f5e9dbbe603d13adfbf618b1cb8a1d0ce825e51 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Tue, 20 Jun 2023 16:23:12 -0400 Subject: [PATCH 39/47] More syntax and styling changes --- html/gui/js/libraries/PlotlyUtilities.js | 34 ++++----- .../modules/job_viewer/AnalyticChartPanel.js | 76 +++++++++---------- html/gui/js/modules/job_viewer/ChartPanel.js | 49 ++++++------ html/gui/js/modules/job_viewer/GanttChart.js | 34 ++++----- html/plotly_template.html | 12 +-- libraries/charting.php | 2 +- 6 files changed, 102 insertions(+), 105 deletions(-) diff --git a/html/gui/js/libraries/PlotlyUtilities.js b/html/gui/js/libraries/PlotlyUtilities.js index bd561e44a9..70a3496347 100644 --- a/html/gui/js/libraries/PlotlyUtilities.js +++ b/html/gui/js/libraries/PlotlyUtilities.js @@ -4,16 +4,16 @@ * @param{dict} Record containing chart data * */ -function generateChartOptions(record){ - let colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', +function generateChartOptions (record) { + var colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; - let data = []; - let isEnvelope = false; - let tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); - let ymin, ymax; + var data = []; + var isEnvelope = false; + var tz = moment.tz.zone(record.data.schema.timezone).abbr(record.data.series[0].data[0].x); + var ymin, ymax; ymin = record.data.series[0].data[0].y; ymax = ymin; - let sid = 0; + var sid = 0; if (record.data.series[sid].name === 'Range') { sid++; isEnvelope = true; @@ -21,18 +21,18 @@ function generateChartOptions(record){ ymax = ymin; } for (sid; sid < record.data.series.length; sid++) { - let x = []; - let y = []; - let qtip = []; - let color = colors[sid % 10]; + var x = []; + var y = []; + var qtip = []; + var color = colors[sid % 10]; - for(let i = 0; i < record.data.series[sid].data.length; i++) { + for(var i = 0; i < record.data.series[sid].data.length; i++) { x.push(moment.tz(record.data.series[sid].data[i].x, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS')); y.push(record.data.series[sid].data[i].y); qtip.push(record.data.series[sid].data[i].qtip); } - let trace = { + var trace = { x: x, y: y, marker: { @@ -69,13 +69,13 @@ function generateChartOptions(record){ } data.push(trace); - const tempMin = Math.min(...y); - const tempMax = Math.max(...y); + var tempMin = Math.min(...y); + var tempMax = Math.max(...y); if (tempMin < ymin) ymin = tempMin; if (tempMax > ymax) ymax = tempMax; } - let layout = { + var layout = { hoverlabel: { bgcolor: '#ffffff' }, @@ -141,7 +141,7 @@ function generateChartOptions(record){ } }; - let ret = { + var ret = { chartData: data, chartLayout: layout }; diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index bcf607581e..09513be044 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -18,18 +18,18 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { delimiter: ':', colorSteps: [ { - value: .25, + value: 0.25, color: '#ff0000', bg_color: 'rgb(255,102,102)' }, { - value: .50, + value: 0.50, color: '#ffb336', bg_color: 'rgb(255,255,156)' }, { - value: .75, + value: 0.75, color: '#dddf00', bg_color: 'rgb(255,255,102)' }, @@ -44,7 +44,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { height: 65, xaxis: { showticklabels: false, - range: [0,1], + range: [0, 1], color: '#606060', ticks: 'inside', tick0: 0.0, @@ -53,7 +53,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { tickcolor: '#ffffff', gridcolor: '#c0c0c0', linecolor: '#ffffff', - zeroline : false, + zeroline: false, showgrid: true, zerolinecolor: '#000000', showline: false, @@ -63,7 +63,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { yaxis: { showticklabels: false, color: '#606060', - showgrid : false, + showgrid: false, gridcolor: '#c0c0c0', linecolor: '#ffffff', zeroline: false, @@ -89,7 +89,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { config: { displayModeBar: false, staticPlot: true - }, + } }, // The instance of Highcharts used as this components primary display. @@ -101,7 +101,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { /** * This components 'constructor'. */ - initComponent: function() { + initComponent: function () { this.colorSteps = Ext.apply(this.colorSteps || [], this._DEFAULT_CONFIG.colorSteps); XDMoD.Module.JobViewer.AnalyticChartPanel.superclass.initComponent.call(this); }, // initComponent @@ -113,7 +113,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * Processes that require the HighCharts component to be created / * a visible element to hook into are handled here. */ - render: function() { + render: function () { this.chart = Plotly.newPlot(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); }, // render @@ -124,7 +124,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @param {Array|Number|Object} data that should be used to update this * component. */ - update_data: function(data) { + update_data: function (data) { this._updateData(data); }, // update_data @@ -132,7 +132,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * Attempt to reset this components' data ensuring that the HighCharts * instance updates correctly to reflect the change, if any. */ - reset: function() { + reset: function () { if (this.chart) { Plotly.react(this.id, [], this._DEFAULT_CONFIG.layout, this._DEFAULT_CONFIG.config); } @@ -157,37 +157,36 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { */ resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (this.chart) { - var container = document.querySelector('#'+this.id); + var container = document.querySelector('#' + this.id); var bgcolor = container._fullLayout.plot_bgcolor; var annotation = structuredClone(container._fullLayout.annotations); var image = structuredClone(container._fullLayout.images); - Plotly.relayout(this.id, {width: adjWidth}); - if (annotation.length > 0){ - Plotly.relayout(this.id, {images: image}); - Plotly.relayout(this.id, {annotations: annotation}); - var update = { + Plotly.relayout(this.id, { width: adjWidth }); + if (annotation.length > 0) { + Plotly.relayout(this.id, { images: image }); + Plotly.relayout(this.id, { annotations: annotation }); + var errorTextUpdate = { xaxis: { showticklabels: false, tickcolor: '#ffffff', gidcolor: '#ffffff', linecolor: '#ffffff', - zeroline : false, + zeroline: false, showgrid: false, zerolinecolor: '#000000', showline: false, zerolinewidth: 0 } }; - Plotly.relayout(this.id, update); - Plotly.relayout(this.id, {plot_bgcolor: bgcolor}); - } - else { - Plotly.relayout(this.id, {images: []}); - Plotly.relayout(this.id, {annotations: []}); + Plotly.relayout(this.id, errorTextUpdate); + Plotly.relayout(this.id, { plot_bgcolor: bgcolor }); + } else { + Plotly.relayout(this.id, { images: [] }); + Plotly.relayout(this.id, { annotations: [] }); var update = { xaxis: { showticklabels: false, - range: [0,1], + range: [0, 1], color: '#606060', ticks: 'inside', tick0: 0.0, @@ -205,7 +204,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { } }; Plotly.relayout(this.id, update); - Plotly.relayout(this.id, {plot_bgcolor: bgcolor}); + Plotly.relayout(this.id, { plot_bgcolor: bgcolor }); } } } // resize @@ -243,18 +242,17 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { align: 'left', xref: 'paper', yref: 'paper', - font:{ + font: { size: 11, }, - x : 0.05, - y : 1.2, + x: 0.05, + y: 1.2, showarrow: false } ]; this._DEFAULT_CONFIG.layout.annotations = errorText; this._DEFAULT_CONFIG.layout.xaxis.showgrid = false; - } - else { + } else { this._DEFAULT_CONFIG.layout.images = []; this._DEFAULT_CONFIG.layout.annotations = []; this._DEFAULT_CONFIG.layout.xaxis.showgrid = true; @@ -268,9 +266,9 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @param {Array|Object} data * @private */ - _updateData: function(data) { + _updateData: function (data) { var trace = {}; - if (data.error == '') { + if (data.error === '') { var chartColor = this._getDataColor(data.value); this._DEFAULT_CONFIG.layout['plot_bgcolor'] = chartColor.bg_color; @@ -278,15 +276,15 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { x: [data.value], name: data.name ? data.name : '', width: [0.5], - marker:{ + marker: { color: chartColor.color, - line:{ + line: { color: '#ffffff', width: 1 } }, type: 'bar', - orientation: 'h', + orientation: 'h' }; } else { @@ -304,7 +302,7 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @param {Object} data * @private */ - _updateTitle: function(data) { + _updateTitle: function (data) { var title = data.name + this._DEFAULT_CONFIG.delimiter + ' ' + data.value; this.ownerCt.setTitle(title); }, // _updateTitle @@ -316,12 +314,12 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { * @returns {Null|String} * @private */ - _getDataColor: function(data) { + _getDataColor: function (data) { var color = null; if (typeof data === 'number') { var steps = this.colorSteps; var count = steps.length; - for ( var i = 0; i < count; i++) { + for (var i = 0; i < count; i++) { var step = steps[i]; if (data <= step.value) { var ret = { diff --git a/html/gui/js/modules/job_viewer/ChartPanel.js b/html/gui/js/modules/job_viewer/ChartPanel.js index 5746fcf8c5..ff410712bf 100644 --- a/html/gui/js/modules/job_viewer/ChartPanel.js +++ b/html/gui/js/modules/job_viewer/ChartPanel.js @@ -13,8 +13,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { /** * The component 'constructor'. */ - initComponent: function() { - + initComponent: function () { this.options = this.options || {}; this.loaded = false; @@ -34,14 +33,14 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { // We need this for some of it's helper functions. var jv = this.jobViewer; - this.store.proxy.on('beforeload', function(proxy){ + this.store.proxy.on('beforeload', function (proxy) { var path = self.path; var token = jv._createHistoryTokenFromArray(path); self.loaded = true; var url = self.baseUrl + '?' + token + '&token=' + XDMoD.REST.token; proxy.setUrl(url, true); }); - this.store.on('load', function(store, records, params) { + this.store.on('load', function (store, records, params) { self.doLayout(); }); @@ -50,11 +49,10 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { }, // initComponent listeners: { - /** * */ - activate: function(tab, reload) { + activate: function (tab, reload) { reload = reload || false; // This is here so that when the chart is / panel is loaded // via one of it's child nodes that it triggers a re-load. @@ -67,7 +65,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { /** * */ - render: function() { + render: function () { if (this.store && this.store.getCount() > 0) { var record = this.store.getAt(0); this.fireEvent('load_record', this, record); @@ -111,14 +109,14 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { var tmpHeight = structuredClone(chartDiv.clientHeight); // Resize to 'medium' export width and height -- Currently placeholder width and height - Plotly.relayout(this.id, {width: 916, height: 484}); + Plotly.relayout(this.id, { width: 916, height: 484 }); // Combine Plotly svg elements similar to export var plotlyChart = chartDiv.children[0].outerHTML; var plotlyLabels = chartDiv.children[2].innerHTML; - plotlyChart = plotlyChart.substring(0, plotlyChart.length-6); - var svg = plotlyChart + plotlyLabels + '' + plotlyChart = plotlyChart.substring(0, plotlyChart.length - 6); + var svg = plotlyChart + plotlyLabels + ''; var printWindow = window.open(); printWindow.document.write(' Printing '); @@ -135,8 +133,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { * @param panel * @param record */ - load_record: function(panel, record) { - + load_record: function (panel, record) { var self = this; if (record !== null && record !== undefined) { @@ -144,7 +141,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { this.displayTimezone = record.data.schema.timezone; if (record.data.schema.help) { panel.helptext.documentation = record.data.schema.help; - this.jobTab.fireEvent("display_help", panel.helptext); + this.jobTab.fireEvent('display_help', panel.helptext); } } @@ -153,15 +150,15 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { panel.getEl().unmask(); if (panel.chart) { - Plotly.react(this.id, chartOptions.chartData, chartOptions.chartLayout, { displayModeBar: false, doubleClick: 'reset' } ); + Plotly.react(this.id, chartOptions.chartData, chartOptions.chartLayout, { displayModeBar: false, doubleClick: 'reset' }); this.chart = true; } else { - Plotly.newPlot(this.id, chartOptions.chartData, chartOptions.chartLayout, { displayModeBar: false, doubleClick: 'reset' } ); + Plotly.newPlot(this.id, chartOptions.chartData, chartOptions.chartLayout, { displayModeBar: false, doubleClick: 'reset' }); this.chart = true; } if (panel.chart) { panel.chart = document.getElementById(this.id); - panel.chart.on('plotly_click', function(data, event){ + panel.chart.on('plotly_click', function (data, event) { var userOptions = data.points[0].data.chartSeries; if (!userOptions || !userOptions.dtype) { return; @@ -171,9 +168,11 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { * The drilldown data are stored on each point for envelope * plots and for the series for simple plots. */ - if (userOptions.dtype == 'index') { + if (userOptions.dtype === 'index') { var nodeidIndex = data.points[0].pointIndex; - if (nodeidIndex === -1) return; + if (nodeidIndex === -1) { + return; + } drilldown = { dtype: userOptions.index, value: userOptions.data[nodeidIndex].nodeid @@ -195,7 +194,9 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { panel.getEl().mask('Loading...'); } panel.fireEvent('record_loaded'); - if (CCR.isType(panel.layout, CCR.Types.Object) && panel.rendered) panel.doLayout(); + if (CCR.isType(panel.layout, CCR.Types.Object) && panel.rendered) { + panel.doLayout(); + } } // load_record }, // listeners @@ -206,7 +207,7 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { * @returns {*} * @private */ - _findDtype: function(series) { + _findDtype: function (series) { if (!CCR.isType(series, CCR.Types.Array)) return null; var result = null; @@ -231,11 +232,11 @@ XDMoD.Module.JobViewer.ChartPanel = Ext.extend(Ext.Panel, { * @param store to be listened to. * @private */ - _addStoreListeners: function(store) { - if ( typeof store === 'object') { + _addStoreListeners: function (store) { + if (typeof store === 'object') { var self = this; - store.on('load', function(tor, records, options) { - if (tor.getCount() == 0) { + store.on('load', function (tor, records, options) { + if (tor.getCount() === 0) { return; } var record = tor.getAt(0); diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index 37fcbeabe1..60a650c38e 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -3,7 +3,6 @@ Ext.namespace('XDMoD', 'XDMoD.Module', 'XDMoD.Module.JobViewer'); XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, { initComponent: function () { - this.panelSettings = { pageSize: 11, url: this.url, @@ -28,18 +27,18 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, var yvals = []; var colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; for (var i = 0; i < record.data.series.length; i++) { - for (var j = 0; j < record.data.series[i].data.length; j++){ + for (var j = 0; j < record.data.series[i].data.length; j++) { var low_data = moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); var high_data = moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('Y-MM-DD HH:mm:ss.SSS '); categories.push(record.data.categories[count]); var runtime = []; - var template = record.data.series[i].name + ": " + "" + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " - " + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + " "; + var template = record.data.series[i].name + ': ' + moment.tz(record.data.series[i].data[j].low, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + ' - ' + moment.tz(record.data.series[i].data[j].high, record.data.schema.timezone).format('ddd, MMM DD, HH:mm:ss z') + ''; var tooltip = [template]; var ticks = [count]; var start_time = record.data.series[i].data[j].low; // Need to create a underlying scatter plot for each peer due to drawing gantt chart with rect shapes. // Points on the scatter plot are created every min to create better coverage for tooltip information - while (start_time < record.data.series[i].data[j].high){ + while (start_time < record.data.series[i].data[j].high) { ticks.push(count); tooltip.push(template); runtime.push(moment(start_time).format('Y-MM-DD HH:mm:ss z')); @@ -50,16 +49,16 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, rect.push({ x0: low_data, x1: high_data, - y0: count-0.175, - y1: count+0.175, + y0: count - 0.175, + y1: count + 0.175, type: 'rect', xref: 'x', yref: 'y', - fillcolor: colors[i % 10], + fillcolor: colors[i % 10] }); var info = {}; - if (i > 0){ + if (i > 0) { info = { realm: record.data.series[i].data[j].ref.realm, recordid: store.baseParams.recordid, @@ -71,13 +70,13 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, x: runtime, y: ticks, type: 'scatter', - marker:{ + marker: { color: 'rgb(255,255,255)', size: 20 }, orientation: 'h', - hovertemplate: record.data.categories[count] + '
'+ - "" + '%{text} ', + hovertemplate: record.data.categories[count] + + '
%{text} ', text: tooltip, chartSeries: info }; @@ -86,7 +85,6 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, yvals.push(count); count++; } - } var layout = { hoverlabel: { @@ -117,7 +115,7 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, tickvals: yvals }, title: { - text: record.data.schema.description, + text: record.data.schema.description, font: { color: '#444b6e', size: 16 @@ -126,7 +124,7 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, hovermode: 'closest', annotations: [{ text: 'Powered by XDMoD/Plotly', - font:{ + font: { color: '#909090', size: 10, family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif' @@ -143,16 +141,16 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, showlegend: false, margin: { t: 50, - l: 180, + l: 180 } }; layout['shapes'] = rect; - Plotly.react(this.id + '_hc', data, layout, {displayModeBar: false, doubleClick: 'reset'}); + Plotly.react(this.id + '_hc', data, layout, { displayModeBar: false, doubleClick: 'reset' }); var panel = document.getElementById(this.id + '_hc'); - panel.on('plotly_click', function(data){ - var userOptions = data.points[0].data.chartSeries; + panel.on('plotly_click', function (eventData) { + var userOptions = eventData.points[0].data.chartSeries; userOptions['action'] = 'show'; Ext.History.add('job_viewer?' + Ext.urlEncode(userOptions)); }); diff --git a/html/plotly_template.html b/html/plotly_template.html index 61d48ba1f8..f414e8300b 100644 --- a/html/plotly_template.html +++ b/html/plotly_template.html @@ -22,15 +22,15 @@
From db1ce720d1ecbc4a8c69edb3f3a87abf36c5f722 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Wed, 5 Jul 2023 15:25:05 -0400 Subject: [PATCH 45/47] Fixes for JS linter --- html/gui/js/libraries/PlotlyUtilities.js | 2 +- html/gui/js/modules/job_viewer/AnalyticChartPanel.js | 6 +++--- html/gui/js/modules/job_viewer/GanttChart.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/html/gui/js/libraries/PlotlyUtilities.js b/html/gui/js/libraries/PlotlyUtilities.js index ad08136dee..b7843f2d63 100644 --- a/html/gui/js/libraries/PlotlyUtilities.js +++ b/html/gui/js/libraries/PlotlyUtilities.js @@ -134,7 +134,7 @@ function generateChartOptions(record, args = null) { // eslint-disable-line no-u font: { family: 'Lucida Grande, Lucida Sans Unicode, Arial, Helvetica, sans-serif', color: '#444b6e', - size: mainTitleFontSize + size: mainTitleFontSize } }, annotations: [{ diff --git a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js index 3b183d8990..2cf3957ad2 100644 --- a/html/gui/js/modules/job_viewer/AnalyticChartPanel.js +++ b/html/gui/js/modules/job_viewer/AnalyticChartPanel.js @@ -159,14 +159,14 @@ XDMoD.Module.JobViewer.AnalyticChartPanel = Ext.extend(Ext.Panel, { resize: function (panel, adjWidth, adjHeight, rawWidth, rawHeight) { if (this.chart) { Plotly.relayout(this.id, { width: adjWidth }); - if (this.chartOptions.annotations.length > 0){ + if (this.chartOptions.annotations.length > 0) { var update = { showticklabels: false, zeroline: false, showgrid: false, - showline: false, + showline: false }; - Plotly.relayout(this.id, {xaxis: update}); + Plotly.relayout(this.id, { xaxis: update }); } } } // resize diff --git a/html/gui/js/modules/job_viewer/GanttChart.js b/html/gui/js/modules/job_viewer/GanttChart.js index 975d3a39a4..41ba9ab0ae 100644 --- a/html/gui/js/modules/job_viewer/GanttChart.js +++ b/html/gui/js/modules/job_viewer/GanttChart.js @@ -80,7 +80,7 @@ XDMoD.Module.JobViewer.GanttChart = Ext.extend(XDMoD.Module.JobViewer.ChartTab, text: tooltip, chartSeries: info }; - + data.push(peer); yvals.push(count); count++; From c58440feccb0e95c73c54891f00d71a837c78d29 Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Wed, 5 Jul 2023 15:31:34 -0400 Subject: [PATCH 46/47] Linter does not like default parameters, adjust approach to assign default value inside function block. --- html/gui/js/libraries/PlotlyUtilities.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/html/gui/js/libraries/PlotlyUtilities.js b/html/gui/js/libraries/PlotlyUtilities.js index b7843f2d63..981017e2db 100644 --- a/html/gui/js/libraries/PlotlyUtilities.js +++ b/html/gui/js/libraries/PlotlyUtilities.js @@ -4,7 +4,8 @@ * @param{dict} Record containing chart data * */ -function generateChartOptions(record, args = null) { // eslint-disable-line no-unused-vars +function generateChartOptions(record, args) { // eslint-disable-line no-unused-vars + var args = args || {}; var colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; var mainTitleFontSize = 16; From ea1edfcadd0019c8a038f5008a93e8f0beef349c Mon Sep 17 00:00:00 2001 From: Andrew Stoltman Date: Wed, 5 Jul 2023 15:37:56 -0400 Subject: [PATCH 47/47] Updates to styling --- html/gui/js/libraries/PlotlyUtilities.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/html/gui/js/libraries/PlotlyUtilities.js b/html/gui/js/libraries/PlotlyUtilities.js index 981017e2db..1609bd20d4 100644 --- a/html/gui/js/libraries/PlotlyUtilities.js +++ b/html/gui/js/libraries/PlotlyUtilities.js @@ -4,15 +4,15 @@ * @param{dict} Record containing chart data * */ -function generateChartOptions(record, args) { // eslint-disable-line no-unused-vars - var args = args || {}; +function generateChartOptions(record, params) { // eslint-disable-line no-unused-vars + var args = params || {}; var colors = ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']; var mainTitleFontSize = 16; var axisLabelFontSize = 11; var axisTitleFontSize = 12; var lineWidth = 2; - if (args){ + if (args) { mainTitleFontSize = args.mainTitleFontSize; axisLabelFontSize = args.axisLabelFontSize; axisTitleFontSize = args.axisTitleFontSize; @@ -71,7 +71,7 @@ function generateChartOptions(record, args) { // eslint-disable-line no-unused-v if (isEnvelope) { trace.hovertemplate = '[%{text}] %{x|%A, %b %e, %H:%M:%S.%L} ' + tz + '
' + record.data.series[sid].name + ': %{y:} '; } - console.log(trace.hovertemplate); + if (record.data.series[sid].data.length === 1) { trace.marker.size = 20; trace.mode = 'markers';