From 0c2d6aa743f3601ab9cc3e6d957d12ed00a82eaa Mon Sep 17 00:00:00 2001 From: Marco Musich Date: Sat, 21 Jun 2025 17:27:56 +0200 Subject: [PATCH 1/4] add more missing miscellaneous modules in the Phase2 menu to groups (#37) --- web/groups/hlt.json | 2 ++ web/groups/hlt_no_gpu.json | 2 ++ web/groups/hlt_summary.json | 2 ++ 3 files changed, 6 insertions(+) diff --git a/web/groups/hlt.json b/web/groups/hlt.json index 19886f3..c02efb1 100644 --- a/web/groups/hlt.json +++ b/web/groups/hlt.json @@ -183,6 +183,7 @@ "FilteredLayerClustersProducer|filteredLayerClustersHADForEgamma": "E/Gamma", "FilteredLayerClustersProducer|": "HGCal", "FixedGridRhoProducerFastjet|": "Jets/MET", + "FixedGridRhoProducerFastjetFromRecHit|hltFixedGridRhoFastjetAllCaloForEGamma": "E/Gamma", "FixedGridRhoProducerFastjetFromRecHit|hltFixedGridRhoFastjetAllCaloForMuons": "E/Gamma", "FixedGridRhoProducerFastjetFromRecHit|hltFixedGridRhoFastjetECALMFForMuons": "Muons", "FixedGridRhoProducerFastjetFromRecHit|hltFixedGridRhoFastjetHCAL": "Muons", @@ -236,6 +237,7 @@ "HLT1PFTau|": "HLT", "HLT1Photon|": "HLT", "HLT2CaloJetCaloJet|": "HLT", + "HLT2L1P2GTCandL1P2GTCandDZ|": "HLT", "HLT2L1TkMuonL1TkMuonDZ|": "HLT", "HLT2L1TkMuonL1TkMuonMuRefDR|": "HLT", "HLT2MuonMuonDZ|": "HLT", diff --git a/web/groups/hlt_no_gpu.json b/web/groups/hlt_no_gpu.json index 4cfb8ee..117739b 100644 --- a/web/groups/hlt_no_gpu.json +++ b/web/groups/hlt_no_gpu.json @@ -183,6 +183,7 @@ "FilteredLayerClustersProducer|filteredLayerClustersHADForEgamma": "E/Gamma", "FilteredLayerClustersProducer|": "HGCal", "FixedGridRhoProducerFastjet|": "Jets/MET", + "FixedGridRhoProducerFastjetFromRecHit|hltFixedGridRhoFastjetAllCaloForEGamma": "E/Gamma", "FixedGridRhoProducerFastjetFromRecHit|hltFixedGridRhoFastjetAllCaloForMuons": "E/Gamma", "FixedGridRhoProducerFastjetFromRecHit|hltFixedGridRhoFastjetECALMFForMuons": "Muons", "FixedGridRhoProducerFastjetFromRecHit|hltFixedGridRhoFastjetHCAL": "Muons", @@ -236,6 +237,7 @@ "HLT1PFTau|": "HLT", "HLT1Photon|": "HLT", "HLT2CaloJetCaloJet|": "HLT", + "HLT2L1P2GTCandL1P2GTCandDZ|": "HLT", "HLT2L1TkMuonL1TkMuonDZ|": "HLT", "HLT2L1TkMuonL1TkMuonMuRefDR|": "HLT", "HLT2MuonMuonDZ|": "HLT", diff --git a/web/groups/hlt_summary.json b/web/groups/hlt_summary.json index 5770f5f..7a565ec 100644 --- a/web/groups/hlt_summary.json +++ b/web/groups/hlt_summary.json @@ -183,6 +183,7 @@ "FilteredLayerClustersProducer|filteredLayerClustersHADForEgamma": "E/Gamma", "FilteredLayerClustersProducer|": "HGCal", "FixedGridRhoProducerFastjet|": "Jets/MET", + "FixedGridRhoProducerFastjetFromRecHit|hltFixedGridRhoFastjetAllCaloForEGamma": "E/Gamma", "FixedGridRhoProducerFastjetFromRecHit|hltFixedGridRhoFastjetAllCaloForMuons": "E/Gamma", "FixedGridRhoProducerFastjetFromRecHit|hltFixedGridRhoFastjetECALMFForMuons": "Muons", "FixedGridRhoProducerFastjetFromRecHit|hltFixedGridRhoFastjetHCAL": "Muons", @@ -236,6 +237,7 @@ "HLT1PFTau|": "others", "HLT1Photon|": "others", "HLT2CaloJetCaloJet|": "others", + "HLT2L1P2GTCandL1P2GTCandDZ|": "HLT", "HLT2L1TkMuonL1TkMuonDZ|": "others", "HLT2L1TkMuonL1TkMuonMuRefDR|": "others", "HLT2MuonMuonDZ|": "others", From 05d5c3daae843d5351a2242bb3fd2d1ab9bad58b Mon Sep 17 00:00:00 2001 From: Marco Musich Date: Fri, 1 Aug 2025 17:58:45 +0200 Subject: [PATCH 2/4] assign MergeClusterProducer to HGCal (#38) --- web/groups/hlt.json | 2 +- web/groups/hlt_no_gpu.json | 2 +- web/groups/hlt_summary.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/groups/hlt.json b/web/groups/hlt.json index c02efb1..ad352ab 100644 --- a/web/groups/hlt.json +++ b/web/groups/hlt.json @@ -305,7 +305,6 @@ "HGCalLayerClustersFromSoAProducer|": "HGCal|Conversion", "HGCalSoALayerClustersProducer@alpaka|": "HGCal|Alpaka", "HGCalSoARecHitsLayerClustersProducer@alpaka|": "HGCal|Alpaka", - "MergeClusterProducer|hltHgcalMergeLayerClusters*": "HGCal", "HLTHPDFilter|": "HLT", "HLTHcalCalibTypeFilter|": "AlCa", "HLTHcalTowerNoiseCleanerWithrechit|": "Jets/MET", @@ -478,6 +477,7 @@ "MaskedMeasurementTrackerEventProducer|hltIter?L3Muon*": "Muons", "MaskedMeasurementTrackerEventProducer|": "Tracking", "MeasurementTrackerEventProducer|": "Tracking", + "MergeClusterProducer|": "HGCal", "MkFitEventOfHitsProducer|": "Tracking", "MkFitOutputConverter|" : "Tracking", "MkFitProducer|": "Tracking", diff --git a/web/groups/hlt_no_gpu.json b/web/groups/hlt_no_gpu.json index 117739b..8807026 100644 --- a/web/groups/hlt_no_gpu.json +++ b/web/groups/hlt_no_gpu.json @@ -305,7 +305,6 @@ "HGCalLayerClustersFromSoAProducer|": "HGCal|Conversion", "HGCalSoALayerClustersProducer@alpaka|": "HGCal", "HGCalSoARecHitsLayerClustersProducer@alpaka|": "HGCal", - "MergeClusterProducer|hltHgcalMergeLayerClusters*": "HGCal", "HLTHPDFilter|": "HLT", "HLTHcalCalibTypeFilter|": "AlCa", "HLTHcalTowerNoiseCleanerWithrechit|": "Jets/MET", @@ -478,6 +477,7 @@ "MaskedMeasurementTrackerEventProducer|hltIter?L3Muon*": "Muons", "MaskedMeasurementTrackerEventProducer|": "Tracking", "MeasurementTrackerEventProducer|": "Tracking", + "MergeClusterProducer|": "HGCal", "MkFitEventOfHitsProducer|": "Tracking", "MkFitOutputConverter|" : "Tracking", "MkFitProducer|": "Tracking", diff --git a/web/groups/hlt_summary.json b/web/groups/hlt_summary.json index 7a565ec..f438eee 100644 --- a/web/groups/hlt_summary.json +++ b/web/groups/hlt_summary.json @@ -305,7 +305,6 @@ "HGCalLayerClustersFromSoAProducer|": "HGCal", "HGCalSoALayerClustersProducer@alpaka|": "HGCal", "HGCalSoARecHitsLayerClustersProducer@alpaka|": "HGCal", - "MergeClusterProducer|hltHgcalMergeLayerClusters*": "HGCal", "HLTHPDFilter|": "others", "HLTHcalCalibTypeFilter|": "others", "HLTHcalTowerNoiseCleanerWithrechit|": "Jets/MET", @@ -478,6 +477,7 @@ "MaskedMeasurementTrackerEventProducer|hltIter?L3Muon*": "Muons", "MaskedMeasurementTrackerEventProducer|": "Full track and vertex", "MeasurementTrackerEventProducer|": "Full track and vertex", + "MergeClusterProducer|": "HGCal", "MkFitEventOfHitsProducer|": "Full track and vertex", "MkFitOutputConverter|" : "Full track and vertex", "MkFitProducer|": "Full track and vertex", From 41536d7760de584ccbd26067b6d8f3be70ce3258 Mon Sep 17 00:00:00 2001 From: Simone Rossi Tisbeni Date: Wed, 13 Aug 2025 09:43:13 +0200 Subject: [PATCH 3/4] Multiple upgrades (#33) Refactor scripts to common.js Fix tooltip overflow Fix value sorting Update absolute sorting Add favicon Add save file prompt Add default data folder Zoom on datatable click Fix case sensitive sorting Add ellipses to long element name Fix second column missing arrows Add option to hide animations Fix default behaviour of no dataset selected Fix undefined default unit Visualize selected groups on table --- .../plug-ins/1.12.1/sorting/absolute.js | 184 ---- .../plug-ins/dataRender/ellipsis.js | 142 +++ web/DataTables/plug-ins/sorting/absolute.js | 189 ++++ web/DataTables/plug-ins/sorting/any-number.js | 98 +++ web/common.js | 810 +++++++++++++++++- web/favicon.ico | Bin 0 -> 4286 bytes web/piechart.php | 675 +-------------- 7 files changed, 1263 insertions(+), 835 deletions(-) delete mode 100644 web/DataTables/plug-ins/1.12.1/sorting/absolute.js create mode 100644 web/DataTables/plug-ins/dataRender/ellipsis.js create mode 100644 web/DataTables/plug-ins/sorting/absolute.js create mode 100644 web/DataTables/plug-ins/sorting/any-number.js create mode 100644 web/favicon.ico diff --git a/web/DataTables/plug-ins/1.12.1/sorting/absolute.js b/web/DataTables/plug-ins/1.12.1/sorting/absolute.js deleted file mode 100644 index f14ad5a..0000000 --- a/web/DataTables/plug-ins/1.12.1/sorting/absolute.js +++ /dev/null @@ -1,184 +0,0 @@ -/** - * When sorting a DataTable you might find that you want to keep a specific - * item at the top or bottom of the table. For example when sorting a column - * of numbers, if a value is `null` or `N/A` you might want to keep it at the - * bottom of the table, regardless of if ascending or descending sorting is - * applied. This plug-in provides that ability. - * - * You must call the `$.fn.dataTable.absoluteOrder` with information about the - * value(s) you wish to make absolute in the sorting order and store the - * returned value, assigning it to the `columns.type` option for the column - * you wish this sorting to be applied to. - * - * For number based columns a `$.fn.dataTable.absoluteOrderNumber` function is - * also available. - * - * @name Absolute sorting - * @summary Keep one or more items at the top and/or bottom of a table when sorting - * @author [Allan Jardine](//datatables.net) - * @depends DataTables 1.10+ - * - * @example - * var namesType = $.fn.dataTable.absoluteOrder( [ - * { value: '', position: 'top' } - * ] ); - * - * var numbersType = $.fn.dataTable.absoluteOrderNumber( [ - * { value: 'N/A', position: 'bottom' } - * ] ); - * - * $('#example').DataTable( { - * columnDefs: [ - * { type: namesType, targets: 0 }, - * { type: numbersType, targets: 1 } - * ] - * } ); - */ - -(function( factory ){ - if ( typeof define === 'function' && define.amd ) { - // AMD - define( ['jquery', 'datatables.net'], function ( $ ) { - return factory( $, window, document ); - } ); - } - else if ( typeof exports === 'object' ) { - // CommonJS - module.exports = function (root, $) { - if ( ! root ) { - root = window; - } - - if ( ! $ || ! $.fn.dataTable ) { - $ = require('datatables.net')(root, $).$; - } - - return factory( $, root, root.document ); - }; - } - else { - // Browser - factory( jQuery, window, document ); - } -}(function( $, window, document, undefined ) { -'use strict'; - -// Unique value allowing multiple absolute ordering use cases on a single page. -var _unique = 0; - -// Function to encapsulate code that is common to both the string and number -// ordering plug-ins. -var _setup = function ( values ) { - if ( ! Array.isArray( values ) ) { - values = [ values ]; - } - - var o = { - name: 'absoluteOrder'+(_unique++), - alwaysTop: {}, - alwaysBottom: {} - }; - - // In order to provide performance, the symbols that are to be looked for - // are stored as parameter keys in an object, allowing O(1) lookup, rather - // than O(n) if it were in an array. - for ( var i=0, ien=values.length ; i b) ? 1 : 0)); - }; - - // Descending ordering method - o.desc = function ( a, b, isNumber ) { - if ( o.alwaysTop[ a ] && o.alwaysTop[ b ] ) { - return 0; - } - else if ( o.alwaysBottom[ a ] && o.alwaysBottom[ b ] ) { - return 0; - } - else if ( o.alwaysTop[ a ] || o.alwaysBottom[ b ] ) { - return -1; - } - else if ( o.alwaysBottom[ a ] || o.alwaysTop[ b ] ) { - return 1; - } - - if ( isNumber ) { - if ( typeof a === 'string' ) { - a = a.replace(/[^\d\-\.]/g, '') * 1; - } - if ( typeof b === 'string' ) { - b = b.replace(/[^\d\-\.]/g, '') * 1; - } - } - - return ((a < b) ? 1 : ((a > b) ? -1 : 0)); - }; - - return o; -}; - -// String based ordering -$.fn.dataTable.absoluteOrder = function ( values ) { - var conf = _setup( values ); - - $.fn.dataTable.ext.type.order[ conf.name+'-asc' ] = conf.asc; - $.fn.dataTable.ext.type.order[ conf.name+'-desc' ] = conf.desc; - - // Return the name of the sorting plug-in that was created so it can be used - // with the `columns.type` parameter. There is no auto-detection here. - return conf.name; -}; - -// Number based ordering - strips out everything but the number information -$.fn.dataTable.absoluteOrderNumber = function ( values ) { - var conf = _setup( values ); - - $.fn.dataTable.ext.type.order[ conf.name+'-asc' ] = function ( a, b ) { - return conf.asc( a, b, true ); - }; - $.fn.dataTable.ext.type.order[ conf.name+'-desc' ] = function ( a, b ) { - return conf.desc( a, b, true ); - }; - - return conf.name; -}; - - -})); diff --git a/web/DataTables/plug-ins/dataRender/ellipsis.js b/web/DataTables/plug-ins/dataRender/ellipsis.js new file mode 100644 index 0000000..d61302c --- /dev/null +++ b/web/DataTables/plug-ins/dataRender/ellipsis.js @@ -0,0 +1,142 @@ +/*! © SpryMedia Ltd - datatables.net/license */ + +(function( factory ){ + if ( typeof define === 'function' && define.amd ) { + // AMD + define( ['jquery', 'datatables.net'], function ( $ ) { + return factory( $, window, document ); + } ); + } + else if ( typeof exports === 'object' ) { + // CommonJS + var jq = require('jquery'); + var cjsRequires = function (root, $) { + if ( ! $.fn.dataTable ) { + require('datatables.net')(root, $); + } + }; + + if (typeof window === 'undefined') { + module.exports = function (root, $) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = jq( root ); + } + + cjsRequires( root, $ ); + return factory( $, root, root.document ); + }; + } + else { + cjsRequires( window, jq ); + module.exports = factory( jq, window, window.document ); + } + } + else { + // Browser + factory( jQuery, window, document ); + } +}(function( $, window, document, undefined ) { +'use strict'; +var DataTable = $.fn.dataTable; + + +/** + * This data rendering helper method can be useful for cases where you have + * potentially large data strings to be shown in a column that is restricted by + * width. The data for the column is still fully searchable and sortable, but if + * it is longer than a give number of characters, it will be truncated and + * shown with ellipsis. A browser provided tooltip will show the full string + * to the end user on mouse hover of the cell. + * + * This function should be used with the `dt-init columns.render` configuration + * option of DataTables. + * + * It accepts three parameters: + * + * 1. `-type integer` - The number of characters to restrict the displayed data + * to. + * 2. `-type boolean` (optional - default `false`) - Indicate if the truncation + * of the string should not occur in the middle of a word (`true`) or if it + * can (`false`). This can allow the display of strings to look nicer, at the + * expense of showing less characters. + * 2. `-type boolean` (optional - default `false`) - Escape HTML entities + * (`true`) or not (`false` - default). + * + * @name ellipsis + * @summary Restrict output data to a particular length, showing anything + * longer with ellipsis and a browser provided tooltip on hover. + * @author [Allan Jardine](http://datatables.net) + * @requires DataTables 1.10+ + * + * @returns {Number} Calculated average + * + * @example + * // Restrict a column to 17 characters, don't split words + * $('#example').DataTable( { + * columnDefs: [ { + * targets: 1, + * render: DataTable.render.ellipsis( 17, true ) + * } ] + * } ); + * + * @example + * // Restrict a column to 10 characters, do split words + * $('#example').DataTable( { + * columnDefs: [ { + * targets: 2, + * render: DataTable.render.ellipsis( 10 ) + * } ] + * } ); + */ +DataTable.render.ellipsis = function (cutoff, wordbreak, escapeHtml) { + var esc = function (t) { + return ('' + t) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + }; + return function (d, type, row) { + // Order, search and type get the original data + if (type !== 'display') { + return d; + } + if (typeof d !== 'number' && typeof d !== 'string') { + if (escapeHtml) { + return esc(d); + } + return d; + } + d = d.toString(); // cast numbers + if (d.length <= cutoff) { + if (escapeHtml) { + return esc(d); + } + return d; + } + var shortened = d.substr(0, cutoff - 1); + // Find the last white space character in the string + if (wordbreak) { + shortened = shortened.replace(/\s([^\s]*)$/, ''); + } + // Protect against uncontrolled HTML input + if (escapeHtml) { + shortened = esc(shortened); + } + return ('' + + shortened + + '…'); + }; +}; + + +return DataTable; +})); diff --git a/web/DataTables/plug-ins/sorting/absolute.js b/web/DataTables/plug-ins/sorting/absolute.js new file mode 100644 index 0000000..5053b4a --- /dev/null +++ b/web/DataTables/plug-ins/sorting/absolute.js @@ -0,0 +1,189 @@ +/*! © SpryMedia Ltd - datatables.net/license */ + +(function( factory ){ + if ( typeof define === 'function' && define.amd ) { + // AMD + define( ['jquery', 'datatables.net'], function ( $ ) { + return factory( $, window, document ); + } ); + } + else if ( typeof exports === 'object' ) { + // CommonJS + var jq = require('jquery'); + var cjsRequires = function (root, $) { + if ( ! $.fn.dataTable ) { + require('datatables.net')(root, $); + } + }; + + if (typeof window === 'undefined') { + module.exports = function (root, $) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = jq( root ); + } + + cjsRequires( root, $ ); + return factory( $, root, root.document ); + }; + } + else { + cjsRequires( window, jq ); + module.exports = factory( jq, window, window.document ); + } + } + else { + // Browser + factory( jQuery, window, document ); + } +}(function( $, window, document ) { +'use strict'; +var DataTable = $.fn.dataTable; + + +/** + * When sorting a DataTable you might find that you want to keep a specific + * item at the top or bottom of the table. For example when sorting a column + * of numbers, if a value is `null` or `N/A` you might want to keep it at the + * bottom of the table, regardless of if ascending or descending sorting is + * applied. This plug-in provides that ability. + * + * You must call the `$.fn.dataTable.absoluteOrder` with information about the + * value(s) you wish to make absolute in the sorting order and store the + * returned value, assigning it to the `columns.type` option for the column + * you wish this sorting to be applied to. + * + * For number based columns a `$.fn.dataTable.absoluteOrderNumber` function is + * also available. + * + * @name Absolute sorting + * @summary Keep one or more items at the top and/or bottom of a table when sorting + * @author [Allan Jardine](//datatables.net) + * @depends DataTables 1.10+ + * + * @example + * var namesType = $.fn.dataTable.absoluteOrder( [ + * { value: '', position: 'top' } + * ] ); + * + * var numbersType = $.fn.dataTable.absoluteOrderNumber( [ + * { value: 'N/A', position: 'bottom' } + * ] ); + * + * $('#example').DataTable( { + * columnDefs: [ + * { type: namesType, targets: 0 }, + * { type: numbersType, targets: 1 } + * ] + * } ); + */ +// Unique value allowing multiple absolute ordering use cases on a single page. +var _unique = 0; +// Function to encapsulate code that is common to both the string and number +// ordering plug-ins. +var _setup = function (values) { + if (!Array.isArray(values)) { + values = [values]; + } + var o = { + name: 'absoluteOrder' + _unique++, + alwaysTop: {}, + alwaysBottom: {}, + asc: function (a, b, isNumber) { }, + desc: function (a, b, isNumber) { }, + }; + // In order to provide performance, the symbols that are to be looked for + // are stored as parameter keys in an object, allowing O(1) lookup, rather + // than O(n) if it were in an array. + for (var i = 0, ien = values.length; i < ien; i++) { + var conf = values[i]; + if (typeof conf === 'string') { + o.alwaysTop[conf] = true; + } + else if (conf.position === undefined || conf.position === 'top') { + o.alwaysTop[conf.value] = true; + } + else { + o.alwaysBottom[conf.value] = true; + } + } + // Ascending ordering method + o.asc = function (a, b, isNumber) { + if (o.alwaysTop[a] && o.alwaysTop[b]) { + return 0; + } + else if (o.alwaysBottom[a] && o.alwaysBottom[b]) { + return 0; + } + else if (o.alwaysTop[a] || o.alwaysBottom[b]) { + return -1; + } + else if (o.alwaysBottom[a] || o.alwaysTop[b]) { + return 1; + } + if (isNumber) { + // Cast as a number if required + if (typeof a === 'string') { + a = a.replace(/[^\d\-\.]/g, '') * 1; + } + if (typeof b === 'string') { + b = b.replace(/[^\d\-\.]/g, '') * 1; + } + } + return a.localeCompare(b, undefined, { sensitivity: 'accent' }); + }; + // Descending ordering method + o.desc = function (a, b, isNumber) { + if (o.alwaysTop[a] && o.alwaysTop[b]) { + return 0; + } + else if (o.alwaysBottom[a] && o.alwaysBottom[b]) { + return 0; + } + else if (o.alwaysTop[a] || o.alwaysBottom[b]) { + return -1; + } + else if (o.alwaysBottom[a] || o.alwaysTop[b]) { + return 1; + } + if (isNumber) { + if (typeof a === 'string') { + a = a.replace(/[^\d\-\.]/g, '') * 1; + } + if (typeof b === 'string') { + b = b.replace(/[^\d\-\.]/g, '') * 1; + } + } + return b.localeCompare(a, undefined, { sensitivity: 'accent' }); + }; + return o; +}; +// String based ordering +DataTable.absoluteOrder = function (values) { + var conf = _setup(values); + DataTable.ext.type.order[conf.name + '-asc'] = conf.asc; + DataTable.ext.type.order[conf.name + '-desc'] = conf.desc; + // Return the name of the sorting plug-in that was created so it can be used + // with the `columns.type` parameter. There is no auto-detection here. + return conf.name; +}; +// Number based ordering - strips out everything but the number information +DataTable.absoluteOrderNumber = function (values) { + var conf = _setup(values); + DataTable.ext.type.order[conf.name + '-asc'] = function (a, b) { + return conf.asc(a, b, true); + }; + DataTable.ext.type.order[conf.name + '-desc'] = function (a, b) { + return conf.desc(a, b, true); + }; + return conf.name; +}; + + +return DataTable; +})); \ No newline at end of file diff --git a/web/DataTables/plug-ins/sorting/any-number.js b/web/DataTables/plug-ins/sorting/any-number.js new file mode 100644 index 0000000..98381ae --- /dev/null +++ b/web/DataTables/plug-ins/sorting/any-number.js @@ -0,0 +1,98 @@ +/*! © SpryMedia Ltd, David Konrad - datatables.net/license */ + +(function( factory ){ + if ( typeof define === 'function' && define.amd ) { + // AMD + define( ['jquery', 'datatables.net'], function ( $ ) { + return factory( $, window, document ); + } ); + } + else if ( typeof exports === 'object' ) { + // CommonJS + var jq = require('jquery'); + var cjsRequires = function (root, $) { + if ( ! $.fn.dataTable ) { + require('datatables.net')(root, $); + } + }; + + if (typeof window === 'undefined') { + module.exports = function (root, $) { + if ( ! root ) { + // CommonJS environments without a window global must pass a + // root. This will give an error otherwise + root = window; + } + + if ( ! $ ) { + $ = jq( root ); + } + + cjsRequires( root, $ ); + return factory( $, root, root.document ); + }; + } + else { + cjsRequires( window, jq ); + module.exports = factory( jq, window, window.document ); + } + } + else { + // Browser + factory( jQuery, window, document ); + } +}(function( $, window, document ) { +'use strict'; +var DataTable = $.fn.dataTable; + + +/** + * Sorts columns by any number, ignoring text. This plugin is useful if you have + * mixed content in a column, but still want to sort by numbers. Any number means + * + * - integers, like 42 + * - decimal numbers, like 42.42 / 42,42 + * - signed numbers, like -42.42 / +42.42 + * - scientific numbers, like 42.42e+10 + * - illegal numbers, like 042, which is considered as 42, + * - currency numbers, like €42,00 + * + * Plain text is ignored; columns with no recognizable numerical content + * is pushed to the bottom of the table, both ascending and descending. + * + * @demo http://jsfiddle.net/vkkL5tv7/ + * + * @name Any number + * @summary Sort column with mixed numerical content by number + * @author David Konrad + * + * @example + * $('#example').dataTable( { + * columnDefs: [ + * { type: 'any-number', targets : 0 } + * ] + * } ); + * + */ +function _anyNumberSort(a, b, high) { + var reg = /[+-]?((\d+(\.\d*)?)|\.\d+)([eE][+-]?[0-9]+)?/; + if (typeof a === 'string') { + a = a.replace(',', '.').match(reg); + a = a !== null ? parseFloat(a[0]) : high; + } + if (typeof b === 'string') { + b = b.replace(',', '.').match(reg); + b = b !== null ? parseFloat(b[0]) : high; + } + return a < b ? -1 : a > b ? 1 : 0; +} +DataTable.ext.type.order['any-number-asc'] = function (a, b) { + return _anyNumberSort(a, b, Number.POSITIVE_INFINITY); +}; +DataTable.ext.type.order['any-number-desc'] = function (a, b) { + return _anyNumberSort(a, b, Number.NEGATIVE_INFINITY) * -1; +}; + + +return DataTable; +})); \ No newline at end of file diff --git a/web/common.js b/web/common.js index aeb97bc..e87dfa5 100644 --- a/web/common.js +++ b/web/common.js @@ -1,3 +1,27 @@ +// Current configuration +var config = loadConfigFromURL(); + +// Input data to parse and visualise +var current = { + colours: null, + compiled: null, + data: null, + dataset: null, + groups: null, + metric: null, // description of the current mteric + processing: false, // the new configuration is being processed + show_labels: true, + show_animations: true, + title: null, // column title associated to the current metric + unit: null // unit associated to the current metric +}; + +// Circles data view +var circles = null; + +var unassigned = []; + +var tooltip = document.getElementById("tooltip"); function fixed(value) { return Number.parseFloat(value).toFixed(1); @@ -11,18 +35,21 @@ function fixed(value) { // { data: "value" } function parseGetData() { var data = {}; - window.location.search.substr(1).split("&").forEach(function(value, index, array) { - var [key, val] = value.split("=").map(decodeURIComponent); - data[key] = (val == undefined) ? null : val; + window.location.search.slice(1).split("&").forEach(function (value) { + [key, val] = value.split("=").map(decodeURIComponent); + data[key] = ( + (val == undefined)? null: val + ); }); return data; } // load JSON data from the given URL, and sets the target's dataObject to it function loadJsonInto(target, attribute, url, then) { + console.log("Loading " + url); var xhttp = new XMLHttpRequest(); xhttp.overrideMimeType("application/json"); - xhttp.open('GET', url); + xhttp.open("GET", url); xhttp.onreadystatechange = function () { if (xhttp.readyState == 4) { target[attribute] = JSON.parse(xhttp.responseText); @@ -34,8 +61,9 @@ function loadJsonInto(target, attribute, url, then) { function groupColorDecorator(options, properties, variables) { // customize only the top level groups' colours - if (properties.level > 0) + if (properties.level > 0){ return; + } // use the group color defined in the dataset if ("color" in properties.group) { @@ -49,10 +77,780 @@ function circlesVisibilityDecorator(group) { return (group.label != "other"); } -function foamtreeVisibilityDecorator(options, properties, variables) { +function foamtreeVisibilityDecorator(properties, variables) { // hide the "other" groups if (properties.group.label == "other") { variables.groupLabelDrawn = false; variables.groupPolygonDrawn = false; } } + +// Load the configuration from the URL +function loadConfigFromURL() { + var local_config = { + colours: null, + data_name: null, + dataset: null, + filter: null, + groups: null, + local: false, + resource: null, + show_labels: null, + show_animations: null + }; + var url = new URL(window.location.href); + for (key in local_config) { + local_config[key] = url.searchParams.get(key); + } + if (local_config["data_name"] == null) { + local_config["data_name"] = data_name; + } + if (local_config.show_labels === null) { + local_config.show_labels = true; + } + if (local_config.show_animations === null) { + local_config.show_animations = true; + } + if (local_config.data_name === null) { + local_config.data_name = "data"; + } + local_config.threshold = 0.0; + return local_config; +} + +// Write the configuration as URL parameters +function convertConfigToURL(config) { + var params = [] + for (key in config) { + if (config[key] != null) + params.push(encodeURIComponent(key) + "=" + encodeURIComponent(config[key])); + } + return "?" + params.join("&"); +} + +// Current configuration +var config = loadConfigFromURL(); +if (config.colours == null) + config.colours = "default"; +if (config.groups == null) + config.groups = "hlt"; +if (config.show_labels == null) + config.show_labels = true; +config.threshold = 0.; + +// Input data to parse and visualise +var current = { + dataset: null, + colours: null, + groups: null, + show_labels: true, + compiled: null, + metric: null, // description of the current mteric + title: null, // column title associated to the current metric + unit: null, // unit associated to the current metric + data: null, + processing: false, // the new configuration is being processed +}; + +// Circles data view +var circles = null; + + +function escape(text) { + return text.replace(/&/g, "&").replace(//g, ">"); +} + + +function embed() { + // We respin until the visualization container has non-zero area (there are race + // conditions on Chrome which permit that) and the visualization class is loaded. + var container = document.getElementById("visualization"); + if (container.clientWidth <= 0 || container.clientHeight <= 0 || !window["CarrotSearchCircles"]) { + window.setTimeout(embed, 250); + return; + } + + // Create an empty CarrotSearchCircles without any data + circles = new CarrotSearchCircles({ + id: "visualization", + captureMouseEvents: false, + pixelRatio: Math.min(2, window.devicePixelRatio || 1), + visibleGroupCount: 0, + showZeroWeightGroups: false, + titleBar: "inscribed", + titleBarTextColor: "#444", + dataObject: null + }); + + installResizeHandlerFor(circles, 300); + updateAnimations(); + + circles.set("groupColorDecorator", groupColorDecorator); + circles.set("isGroupVisible", circlesVisibilityDecorator); + //circles.set("groupSelectionColor", "#babdb633"); + circles.set("groupSelectionColor", "#cc000022"); + circles.set("groupSelectionOutlineColor", "#cc0000aa"); + circles.set("groupSelectionOutlineWidth", 4); + circles.set("groupSelectionOutlineStyle", "none"); // unused + + circles.set("titleBarLabelDecorator", function (attrs) { + var table = $('#properties').DataTable(); + table.clear(); + $(".property_value span.dt-column-title").text(current.title); + $("#selected_label").text(); + $("#selected_value").text(); + $("#selected_percent").text(); + $("#selected").hide(); + + var total = circles.get("dataObject").weight; + + if (attrs.hoverGroup) { + var group = attrs.hoverGroup; + var value = fixed(group.weight) + current.unit; + var percent = fixed(group.weight / total * 100.) + " %"; + table.row.add([escape(group.label), value, percent]); + attrs.label = value; + } else if (attrs.selectedGroups.length > 0) { + var sum = 0.; + for (var i = 0; i < attrs.selectedGroups.length; i++) { + var group = attrs.selectedGroups[i]; + var value = fixed(group.weight) + current.unit; + var percent = fixed(group.weight / total * 100.) + " %"; + sum += group.weight; + table.row.add([escape(group.label), value, percent]); + attrs.label = value; + } + if (attrs.selectedGroups.length > 1) { + var label = "selected"; + var value = fixed(sum) + current.unit; + var percent = fixed(sum / total * 100.) + " %"; + $('#selected_label').text(label); + $('#selected_value').text(value); + $('#selected_percent').text(percent); + $('#selected').show(); + attrs.label = value; + } + } else { + // Show all top level groups + var groups = circles.get("dataObject").groups; + for (var i = 0; i < groups.length; i++) { + var group = groups[i]; + var value = fixed(group.weight) + current.unit; + var percent = fixed(group.weight / total * 100.) + " %"; + table.row.add([escape(group.label), value, percent]); + } + var label = "total"; + var value = fixed(total) + current.unit; + var percent = fixed(100.) + " %"; + $('#selected_label').text(label); + $('#selected_value').text(value); + $('#selected_percent').text(percent); + $('#selected').show(); + attrs.label = value; + } + + table.draw(); + }); +} + +// Load the available datasets +function loadAvailableDatasets() { + var menu = document.getElementById("dataset_menu"); + while (menu.options.length > 1) { + menu.remove(1); + } + menu.selectedIndex = 0; + for (var i = 0; i < datasets.length; i += 1) { + if ((config.filter !== null) && (!datasets[i].includes(config.filter))) { + continue; + } + var entry = document.createElement("option"); + entry.text = datasets[i]; + entry.value = datasets[i]; + menu.options.add(entry); + if (datasets[i] == config.dataset) { + menu.selectedIndex = menu.options.length - 1; + } + } + console.log(menu.selectedIndex); + // if a dataset is selected, load it and the associated resources + if (menu.selectedIndex !== 0) { + updateDataset(); + } +} + +// Update the selected dataset +function updateDataset() { + // Update the configuration with the selected dataset + var menu = document.getElementById("dataset_menu"); + var index = menu.selectedIndex; + config.dataset = menu.options[index].value; + config.local = false; + + // Load the selected dataset, and the associated resource metrics + loadJsonInto(current, "dataset", config.data_name + "/" + config.dataset + ".json", loadAvailableMetrics); +} + +// Upload a JSON file +function uploadDataset(files) { + // Reset the dataset selection in the drop-down menu + document.getElementById("dataset_menu").selectedIndex = 0; + config.dataset = null; + config.local = true; + var file = files[0]; + file.text().then(function (content) { + current.dataset = JSON.parse(content); + loadAvailableMetrics(); + }); +} + +function downloadDataset() { + var data = JSON.stringify(current.dataset, null, 2); + var blob = new Blob([data], { type: "application/json" }); + var url = URL.createObjectURL(blob); + var a = document.createElement("a"); + a.href = url; + a.download = config.dataset + '.json'; + a.click(); +} + +function loadAvailableMetrics() { + var menu = document.getElementById("metric_menu"); + + // Clear the current resources + while (menu.length) { + menu.remove(0); + } + var resources = current.dataset.resources; + + for (var i = 0; i < resources.length; i++) { + var entry = document.createElement("option"); + var keys = Object.keys(resources[i]); + // if you find description title name and unit use them + if (keys.includes("description") && keys.includes("title") && keys.includes("name") && keys.includes("unit")) { + entry.text = resources[i].description; + entry.value = resources[i].name; + entry.dataset.title = resources[i].title; + entry.dataset.unit = resources[i].unit; + menu.add(entry); + if (key == config.resource) { + menu.selectedIndex = i; + } + } else { + for (key in resources[i]) { + var entry = document.createElement("option"); + entry.text = resources[i][key]; + entry.value = key; + menu.add(entry); + if (key == config.resource) { + menu.selectedIndex = i; + } + } + } + } + + updateMetrics(); +} + + +// Update the configuration with the selected metric +function updateMetrics() { + var menu = document.getElementById("metric_menu"); + var index = menu.selectedIndex; + config.resource = menu.options[index].value; + current.metric = menu.options[index].text; + if (menu.options[index].dataset.unit === undefined || menu.options[index].dataset.title === undefined) { + if (config.resource.startsWith("hs23_")) { + current.unit = " HS23/Hz"; + current.title = "Capacity"; + } else if (config.resource.startsWith("time_")) { + current.unit = " ms"; + current.title = "Time"; + } else if (config.resource.startsWith("mem_")) { + current.unit = " kB"; + current.title = "Memory"; + } else { + current.unit = ""; + current.title = ""; + } + } else { + current.unit = " " + menu.options[index].dataset.unit; + current.title = menu.options[index].dataset.title; + } + updatePage(); +} + +// Update the configuration with the selected grouping +function updateGroups() { + var menu = document.getElementById("groups_menu"); + var index = menu.selectedIndex; + config.groups = menu.options[index].value; + + // load the module groups, then update the page + loadJsonInto(current, "groups", "groups/" + config.groups + ".json", compileGroups); +} + +// Update the configuration with the selected colour scheme +function updateColours() { + var menu = document.getElementById("colours_menu"); + var index = menu.selectedIndex; + config.colours = menu.options[index].value; + + // load the colour scheme, then update the page + loadJsonInto(current, "colours", "colours/" + config.colours + ".json", updatePage); +} + +// Update the configuration with the visibility of the leaf labels +function updateShowLabels() { + var checkbox = document.getElementById("show_labels_checkbox"); + var value = checkbox.checked; + config.show_labels = value; + current.show_labels = value; + + // update the page + updatePage(); +} + +function updateShowAnimations() { + var checkbox = document.getElementById("show_animations_checkbox"); + var value = checkbox.checked; + config.show_animations = value; + + // update the animations + updateAnimations(); + updateURL(); +} + + +// Update the title, URL, history and visualisation based on the current +// configuration +function updateURL() { + var title = ( + (config.local ? "local file" : config.dataset) + " - " + current.metric + ); + window.history.pushState(config, title, convertConfigToURL(config)); + document.title = "CMSSW resource utilisation: " + title; +} + +function updatePage() { + updateURL(); + + // Handle navigation of the History + window.onpopstate = function (event) { + config = event.state; + updateDataset(); + } + + updateDataView(); +} + +// Update the animations based on the current configuration +function updateAnimations() { + if (!config.show_animations) { + circles.set("expandTime", 0); + circles.set("zoomTime", 0); + circles.set("rolloutTime", 0); + circles.set("pullbackTime", 0); + circles.set("updateTime", 0); + } else { + circles.set("expandTime", 1); + circles.set("zoomTime", 1); + circles.set("rolloutTime", 1); + circles.set("pullbackTime", 1); + circles.set("updateTime", 1); + } +} + +// Load the available groupings +function loadAvailableGroups() { + var menu = document.getElementById("groups_menu"); + for (var i = 0; i < groups.length; i++) { + var entry = document.createElement("option"); + entry.text = groups[i]; + entry.value = groups[i]; + menu.options.add(entry); + if (groups[i] == config.groups) { + menu.selectedIndex = i; + } + } + updateGroups(); +} + + +// Load the available colour scheme +function loadAvailableColours() { + var menu = document.getElementById("colours_menu"); + for (var i = 0; i < colours.length; i++) { + var entry = document.createElement("option"); + entry.text = colours[i]; + entry.value = colours[i]; + menu.options.add(entry); + if (colours[i] == config.colours) { + menu.selectedIndex = i; + } + } + updateColours(); +} + + +// Compile a pattern +// - an empty pattern "" compiles to a null object, and matches anything +// - a literal pattern "module" compiles to a string object +// - a glob pattern "module*" compiles to a regex object /^module.*$/ +function compilePattern(pattern) { + // empty string, return a null object + if (pattern == "") + return null; + + // glob pattern, convert ot a regular expression + if (pattern.includes("?") || pattern.includes("*")) { + // convert glob to regex patterns + pattern = "^" + pattern.replace(/\?/g, ".").replace(/\*/g, ".*") + "$"; + return new RegExp(pattern); + } + + // literal string, leave unchanged + return pattern; +} + +// Match a pattern +// - a null pattern matches any string +// - a literal string pattern matches a full string +// - a regex pattern matches accrding to the regex +function matchPattern(pattern, text) { + if (pattern == null) + return true; + + if (pattern instanceof RegExp) + return pattern.test(text); + + return (pattern == text); +} + +// Compile the group definition as a pair of patterns for the module's type and label +function compileGroups() { + current.compiled = null; + var compiled = []; + + for (key in current.groups) { + // convert a glob pattern into a regular expression + var t, l; + if (key.includes("|")) { + [t, l] = key.split("|") + t = compilePattern(t); + l = compilePattern(l); + } else { + // leave the type null + t = null + l = compilePattern(key); + } + compiled.push([t, l, current.groups[key]]); + } + + // Insert the group "other" + if (current.groups["other"] == undefined) { + compiled.push([new RegExp(".*"), new RegExp("^other$"), "other"]); + } + + current.compiled = compiled; + updatePage(); +} + +unassigned = []; + +function findGroup(module) { + var assigned = false; + var group = "Unassigned"; + for ([t, l, g] of current.compiled) { + if (matchPattern(t, module.type) && matchPattern(l, module.label)) { + assigned = true; + group = g; + break; + } + } + if (!assigned) { + unassigned.push({ type: module.type, label: module.label }); + } + + return group.split("|"); +} + +// group is an array of nested group labels, +// e.g. [ "GPU", "Pixels", "PixelTrackProducer" ] +function getGroup(group) { + // data should always have a "groups" property of array type + var data = current.data; + for (label of group) { + // check if data.groups has an element wih the given label + var found = false; + for (element of data.groups) { + if (element.label == label) { + found = true; + data = element; + break; + } + } + if (!found) { + return null; + } + } + + return data; +} + +// group is an array of nested group labels, +// e.g. [ "GPU", "Pixels", "PixelTrackProducer" ] +function makeOrUpdateGroup(group, module) { + // data should always have a "groups" property of array type + var data = current.data; + data.elements = 0 + for (label of group) { + // add the module's resource to the group's + data.weight += module[config.resource]; + // make sure that data.groups has an element wih the given label + var found = false; + for (element of data.groups) { + if (element.label == label) { + element.id = label; + found = true; + data = element; + break; + } + } + if (!found) { + var len = data.groups.push({ "label": label, "weight": 0., "groups": [] }) + data = data.groups[len - 1]; + } + } + // add the module and its resource to the group + var label = ""; + if (current.show_labels || module.label == "other") + label = module.label + var entry = { "label": label, "weight": module[config.resource], "events": module.events }; + if ("ratio" in module) + entry.ratio = module.ratio; + data.groups.push(entry); + data.weight += module[config.resource]; +} + +function normalise(data, events) { + data.weight /= events; + if ("events" in data) { + data.events /= events; + } + if ("groups" in data) { + for (group of data.groups) { + normalise(group, events); + } + } +} + +function updateDataView() { + // Do not draw if the configuration is incomplete + if ((config.local == false && config.dataset == null) || !config.resource) { + return; + } + + // Do not draw if the configuration has been updated + if (current.processing) { + return; + } + + // Respin until all configurations are available + if (current.dataset == null || current.colours == null || current.groups == null || current.compiled == null) { + window.setTimeout(updateDataView, 250); + return; + } + + current.processing = true; + + current.data = { + "label": current.dataset.total.label, + "expected": current.dataset.total[config.resource], + "ratio": current.dataset.total.ratio, + "weight": 0., + "groups": [] + } + + for (module of current.dataset.modules) { + var group = findGroup(module); + group.push(module.type); + makeOrUpdateGroup(group, module); + } + if (unassigned.length) { + console.log("Unassigned modules:"); + console.table(unassigned); + } + normalise(current.data, current.dataset.total.events); + + for (key in current.colours) { + group = getGroup(key.split("|")); + if (group != null) { + group.color = current.colours[key]; + } + } + + if (circles != null) { + circles.set("onRolloutComplete", function () { + current.processing = false; + }); + + circles.set("dataObject", current.data); + + } +} + + +async function getImage() { + var canvas1 = document + .getElementById("visualization") + .getElementsByTagName("canvas")[0]; + var canvas2 = document + .getElementById("visualization") + .getElementsByTagName("canvas")[1]; + var logo = document + .getElementById("logo") + .getElementsByTagName("img")[0]; + + var canvas = document.createElement("canvas"); + var ctx = canvas.getContext("2d"); + canvas.width = canvas1.width; + canvas.height = canvas1.height; + ctx.drawImage(canvas1, 0, 0); + ctx.drawImage(canvas2, 0, 0); + ctx.drawImage(logo, 32, 32, 72, 72); // Adjust the position and size as needed + + const image = await new Promise((res) => canvas.toBlob(res)); + if (window.showSaveFilePicker) { + const handle = await self.showSaveFilePicker({ + suggestedName: 'piechart.png', + types: [{ + description: 'PNG file', + accept: { + 'image/png': ['.png'], + }, + }], + }); + const writable = await handle.createWritable(); + await writable.write(image); + writable.close(); + } + else { + const saveImg = document.createElement("a"); + saveImg.href = URL.createObjectURL(image); + saveImg.download = "piechart.png"; + saveImg.click(); + setTimeout(() => URL.revokeObjectURL(saveImg.href), 60000); + } +} + +$(document).ready(function () { + console.log("Loading configuration: " + JSON.stringify(config)); + // load the colour scheme + loadJsonInto(current, "colours", "colours/" + config.colours + ".json", function () { }); + + // load the available datasets, and the resources actually available from the dataset + loadAvailableDatasets(); + + // load the available groups and colours + loadAvailableGroups(); + loadAvailableColours(); + + embed(); + var sortable_label = $.fn.dataTable.absoluteOrder( + { value: "other", position: "bottom" } + ); + + if (!$.fn.DataTable.isDataTable("#properties")) { + $("#properties").dataTable({ + "columns": [{ + "className": "property_label", + "type": sortable_label, + "render": $.fn.dataTable.render.ellipsis(36) + }, + { + "className": "property_value", + "type": "any-number" + }, + { + "className": "property_fraction" + } + ], + //"info": true, + "paging": false, + "searching": false + }); + } + + + var tooltip = document.getElementById("tooltip"); + + circles.set("onGroupHover", function (hover) { + if (hover.group) { + tooltip.innerHTML = escape(hover.group.label) + "
" + hover.group.weight.toFixed(1) + " " + current.unit; + if ("events" in hover.group) { + tooltip.innerHTML += "
" + (hover.group.events * 100.).toFixed(1) + "% events"; + } + if ("ratio" in hover.group) { + tooltip.innerHTML += "
" + (hover.group.ratio * 100.).toFixed(1) + "% ratio"; + } + tooltip.style.visibility = "visible"; + } else { + tooltip.innerHTML = null; + tooltip.style.visibility = "hidden"; + } + }); + +}); + +async function colorZoomed(){ + var table = $("#properties").DataTable(); + table.$("tr.selectedGroupRow").removeClass("selectedGroupRow"); + var zoomed = circles.get("zoom"); + for (var group of zoomed.groups) { + var groupName = group.label; + var tableDiv = document.querySelector(".dataTable"); + var rows = tableDiv.querySelectorAll("tbody tr"); + for (var tr of rows) { + var td = tr.querySelector("td.property_label"); + if (td.innerText == groupName) { + tr.classList.add("selectedGroupRow"); + } + } + } +} + +document.onmousemove = function (event) { + tooltip.style.top = ( + event.pageY + tooltip.clientHeight + 16 < document.body.clientHeight + ? event.pageY + 16 + "px" + : event.pageY - tooltip.clientHeight - 5 + "px" + ); + tooltip.style.left = ( + event.pageX + tooltip.clientWidth < document.body.clientWidth + ? event.pageX + "px" + : document.body.clientWidth - tooltip.clientWidth + 5 + "px" + ); +}; + +$('#properties').on('click', 'tbody tr', function (e) { + var table = $("#properties").DataTable(); + var groupName = table.row(this).data()[0]; + var zoomed = circles.get("zoom") + for (var group of zoomed.groups) { + if (group.label === groupName) { + circles.set("zoom", { groups: [groupName], zoomed: false }); + return; + } + } + if (!e.ctrlKey) { + circles.set("zoom", { all: true, zoomed: false }); + } + circles.set("zoom", { groups: [groupName], zoomed: true }); + colorZoomed(); +}); + +// Whenever the table changes, update the zoom +$('#properties').on('draw.dt', function () { + colorZoomed(); +}); \ No newline at end of file diff --git a/web/favicon.ico b/web/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8a726669f8b9dac54eb33e9c87595154fb957b1e GIT binary patch literal 4286 zcmc&&YfzL`7~ZnWpMGc>+QqQ3yjy9OsgY=5siS5(0bWv)va+;DX>7dASdNxunL4J8 zGhSB^*j<-h1Y~ysk&6fjD1sLZA+EX?U|Fs{z3*ARoo_FYPCr`b@a=cLkMlh5^StkS z4k{E6(7z!=6#P>W)mNeLQz#T8XwZ}*kOt3-4?P?DqH{PW6pA`!AKdR~P(FY=N`)A= z=~}ORP^`i8e9zD1^Y^>HulE^)*Q`-Kg2HHjoQey=F3o3HAHNl=^n3BCem_?0e#d6* zX6#olM{?|Blt%dxw?6-gi-cQ7YaLbvV_Cvcj7urQ)5a$BH+7<)3Fs>u{Y-Z;z|;YM zqZw0@^YE4KH^j$I$E_2O2_C*;>uHYTEkDs0`!p*sEjbf|j1GbIxT#BEFiuZ{xjjx? zn4iDVf)7rsp^lq@>JyKGed-BQn&)>6F%$8Aq80-QSBk4Ayx!&|orW0iV2y4UilUz- z4ho+fSnpcp!#Jp3g0U&(f`4yo-_J9rCS3?hN*A$ta$s!EuU}5B7XJ07b3f9)ud#zP z?!Xf?{JOp9M{_*izx&=7Kh|=>nG&3go8zUyZp}&zHd^WTy*=hNdHttSI}ni7hE)YE z_^qTF$F4RZ>RJ;HmNjEbu^qt|92kAJU2tH2zBq7f!wl6fe9pDlxLJ7KP)jj+WAi$U z8%k@duQ#Ep+KQGs3)<`5M;qPSYAqZ<{BrSgRmLDr_oB!T8Yb*N*h*RY!TdjabWyfA^nvr#U^5`@y=KVqV2?!{1(>bKLB60cpwDo@?{Hquxew@@N!SN4?V* z`Mb>7PJ=nHhjJY_{s`SpFWvc_7t-62RNm+l+fipjb7eiMt`wuVz<`3h({_P4Nq$P5T{n*RGLQ?pm-HrIZg7Go3lfKJ3$cgeQ$HZZ*RE#$~kOGQG>cBj?au z)c|A0VSK3xM2KoQLewJ=sve0&H2BK*i&dksUB3+FmvX&0w9$IMl-OM!EK4}%m3Lg@ zUz1L9T>kDL{W7y+MVySYNIlAls~Lk(%~;nx&oj5Jx+SQ&TI%Kn$8(9|8k5m3e0cTD zC9k|2L^-0O8t2-UIIy2JrB|>)JD*}4F0eugcW&a_csq45rll{#JH~}rsGHQygSe?i zA;$C*`DJ#k!|QP#a3A45`HZoVwC{B3&%TeO-o^bxu6=8*4aRdvuv8_*IU2K4=3q+J zY6Rv)BIx{jypi(_rk-1g1^UTC7uG0JI}6oUuefQzwe-U!yXd9zTIBES{bb6IO6qU& zTDMo-#-B-%LRa>Bp>84qGC#+x+znV;bO4)54k7&FP6QB#pwv+EW3=E9rXG)i{6tZo zl9sINA?nqh*nH34PtR^cU5yN%v~R4a!OscdLi@#pKQHkuOvzq@@PeJ#S{Q@Kcmp81dpCXvN zXZ-h02a@({ur&Wi{7`ff;oD2GZf`l}MJX{oF#^Gwaag1oj-`}~nYqzuZ=n9vU_l#= zuGic^|ADkHE7lg!f?=ciG%zNu_q26?%9gv`y-&$pGV;ZEAr2qk(FVF;fxvEqOAx%(1f9eddh$I{Np~I ze$&10wNk&!&((`wV3PYSG)Cm$^q-mD(pV}g-Zjj^AF-DU~bY3 z%ubn!1;;+aQEipL$31?y_sip#a&XT1Ry4?Skoyny)AHh6Y|<`p)dAN6=L27PEZ?^U zsoBltNv>;C;T=|vHf6U`x zx(%xiH6xe&=@UqOGLg(V#Jszr$dUuRN2@V;12b-`Nn#Dkyld5h1UTWFsd zNqg? literal 0 HcmV?d00001 diff --git a/web/piechart.php b/web/piechart.php index db0db17..a0445ce 100644 --- a/web/piechart.php +++ b/web/piechart.php @@ -17,7 +17,9 @@ - + + + - @@ -121,6 +138,10 @@ function rglob($pattern, $flags = 0) { Show labels +
+ + Show aminations +
@@ -134,8 +155,8 @@ function rglob($pattern, $flags = 0) { Element - Resource - Fraction + Resource + Fraction @@ -144,626 +165,12 @@ function rglob($pattern, $flags = 0) { - + - - -