From 45a929b254cba638179014e513b7461b3f4296a4 Mon Sep 17 00:00:00 2001 From: Joe Uhren Date: Sat, 28 Dec 2024 19:22:38 -0700 Subject: [PATCH] Tons of network chart improvements and changes -Chart.js has been updated to v4.4.7 -The chartjs-plugin-crosshair chart plugin has been updated to v2.0.5 via a forked version that has a working sync feature which is now available as a new setting option for use with the network charts -Added a new max_hours setting to display chart data for a certain number of hours instead of a fixed set of records which can help reveal holes in the sync process for the explorer and/or blockchain -Added a new timestamp field to the network history collection for use with the max_hours setting chart data -Added a number of new network chart settings to control display of the chart title, legend, a new vertical block line option, chart height, an option to force 2 charts to appear on their own row or beside each other, and an option to force a chart to take up all available space in the chart box without extra padding -Added a new dependency chartjs-plugin-annotation v3.1.0 to display block lines in new hourly charts --- README.md | 11 +- lib/database.js | 137 ++++--- lib/settings.js | 151 ++++++- models/networkhistory.js | 3 +- settings.json.template | 153 ++++++- views/address.pug | 1 - views/block.pug | 1 - views/includes/difficulty_chart.pug | 6 + views/includes/nethash_chart.pug | 6 + views/index.pug | 1 - views/layout.pug | 605 ++++++++++++++++++++-------- views/market.pug | 2 - views/masternodes.pug | 1 - views/movement.pug | 1 - views/network.pug | 1 - views/orphans.pug | 1 - views/reward.pug | 1 - views/richlist.pug | 1 - views/tx.pug | 1 - 19 files changed, 849 insertions(+), 235 deletions(-) create mode 100644 views/includes/difficulty_chart.pug create mode 100644 views/includes/nethash_chart.pug diff --git a/README.md b/README.md index ddf9f22..1f6fcef 100644 --- a/README.md +++ b/README.md @@ -86,10 +86,13 @@ Table of Contents - DataTables v1.13.6 - Font Awesome v6.4.2 - Luxon v3.4.3 - - Chart.js v4.4.0 - - chartjs-plugin-crosshair v2.0.0 ([https://github.com/abelheinsbroek/chartjs-plugin-crosshair](https://github.com/abelheinsbroek/chartjs-plugin-crosshair)) + - Chart.js v4.4.7 + - chartjs-plugin-crosshair v2.0.5 + - Forked version with working sync feature: ([https://github.com/joeuhren/chartjs-plugin-crosshair](https://github.com/joeuhren/chartjs-plugin-crosshair)) + - Original version: ([https://github.com/abelheinsbroek/chartjs-plugin-crosshair](https://github.com/abelheinsbroek/chartjs-plugin-crosshair)) - chartjs-chart-financial v0.1.1 ([https://github.com/chartjs/chartjs-chart-financial](https://github.com/chartjs/chartjs-chart-financial)) - chartjs-adapter-luxon v1.3.1 ([https://github.com/chartjs/chartjs-adapter-luxon](https://github.com/chartjs/chartjs-adapter-luxon)) + - chartjs-plugin-annotation v3.1.0 ([https://github.com/chartjs/chartjs-plugin-annotation](https://github.com/chartjs/chartjs-plugin-annotation)) - OverlayScrollbars v2.3.2 - flag-icons v6.11.1 ([https://github.com/lipis/flag-icons](https://github.com/lipis/flag-icons)) - Intl.js (uses the v4.8.0 polyfill service to only download if using a browser that doesn't already support the ECMAScript Internationalization API) @@ -179,8 +182,8 @@ Table of Contents - **USD Market Cap:** Displays the current market cap value (value measured in USD) - **Logo:** Display an image of your coin logo - Configurable network charts that can be independently displayed in the header of any page - - **Hashrate chart:** Line graph listing of the estimated network hashes per second over the last number of blocks *\*Requires a full sync before network data will start being collected* - - **Difficulty chart:** Line graph listing of the block difficulty over the last number of blocks *\*Requires a full sync before network data will start being collected* + - **Hashrate chart:** Line graph listing of the estimated network hashes per second over the last number of blocks or hours *\*Requires a full sync before network data will start being collected* + - **Difficulty chart:** Line graph listing of the block difficulty over the last number of blocks or hours *\*Requires a full sync before network data will start being collected* - Add as many custom social links to the explorer footer as desired. Useful for linking to github, twitter, coinmarketcap or any other social media or external links as necessary. - Custom rpc/api command support which increases blockchain compatibility. Supported cmds: - **getnetworkhashps:** Returns the estimated network hashes per second diff --git a/lib/database.js b/lib/database.js index d2703dc..f2625e2 100644 --- a/lib/database.js +++ b/lib/database.js @@ -1306,67 +1306,97 @@ module.exports = { lib.get_hashrate(function(hashrate) { // lookup network difficulty lib.get_difficulty(function(difficulty) { - var difficultyPOW = 0; - var difficultyPOS = 0; + // lookup the block hash + lib.get_blockhash(height, function(blockhash) { + if (blockhash) { + // lookup block data + lib.get_block(blockhash, function(block) { + if (block) { + var difficultyPOW = 0; + var difficultyPOS = 0; - if (difficulty && difficulty['proof-of-work']) { - if (settings.shared_pages.difficulty == 'Hybrid') { - difficultyPOS = difficulty['proof-of-stake']; - difficultyPOW = difficulty['proof-of-work']; - } else if (settings.shared_pages.difficulty == 'POW') - difficultyPOW = difficulty['proof-of-work']; - else - difficultyPOS = difficulty['proof-of-stake']; - } else if (settings.shared_pages.difficulty == 'POW') - difficultyPOW = difficulty; - else - difficultyPOS = difficulty; + if (difficulty && difficulty['proof-of-work']) { + if (settings.shared_pages.difficulty == 'Hybrid') { + difficultyPOS = difficulty['proof-of-stake']; + difficultyPOW = difficulty['proof-of-work']; + } else if (settings.shared_pages.difficulty == 'POW') + difficultyPOW = difficulty['proof-of-work']; + else + difficultyPOS = difficulty['proof-of-stake']; + } else if (settings.shared_pages.difficulty == 'POW') + difficultyPOW = difficulty; + else + difficultyPOS = difficulty; - // create a new network history record - var newNetworkHistory = new NetworkHistory({ - blockindex: height, - nethash: (hashrate == null || hashrate == '-' ? 0 : hashrate), - difficulty_pow: difficultyPOW, - difficulty_pos: difficultyPOS, - }); + // create a new network history record + var newNetworkHistory = new NetworkHistory({ + blockindex: height, + nethash: (hashrate == null || hashrate == '-' ? 0 : hashrate), + difficulty_pow: difficultyPOW, + difficulty_pos: difficultyPOS, + timestamp: block.time + }); - // save the new network history record - newNetworkHistory.save().then(() => { - // get the count of network history records - NetworkHistory.find({}).countDocuments().then((count) => { - // read maximum allowed records from settings - let max_records = settings.network_history.max_saved_records; + // save the new network history record + newNetworkHistory.save().then(() => { + // read maximum allowed records from settings + const max_records = settings.network_history.max_saved_records; - // check if the current count of records is greater than the maximum allowed - if (count > max_records) { - // prune network history records to keep collection small and quick to access - NetworkHistory.find().select('blockindex').sort({blockindex: 1}).limit(count - max_records).exec().then((records) => { - // create a list of the oldest network history ids that will be deleted - const ids = records.map((doc) => doc.blockindex); + // check if the max allowed records is set + if (max_records == 0) { + // read maximum hours from settings + const max_hours = settings.network_history.max_hours; - // delete old network history records - NetworkHistory.deleteMany({blockindex: {$in: ids}}).then(() => { - console.log('Network history update complete'); - return cb(); + // calculate the cutoff timestamp value + const timestampThreshold = Math.floor(Date.now() / 1000) - max_hours * 60 * 60; + + // delete all network history records that are older than the max hours setting + NetworkHistory.deleteMany({ timestamp: { $lt: timestampThreshold } }).then(delete_result => { + console.log('Network history update complete'); + return cb(); + }).catch(err => { + console.log(err); + return cb(); + }); + } else { + // prune network history records to keep collection small and quick to access + NetworkHistory.find().sort({ blockindex: -1 }).skip(max_records).select('blockindex').exec().then((records) => { + // check if any records need to be deleted + if (records.length > 0) { + // create a list of the oldest network history ids that will be deleted + const ids = records.map((doc) => doc.blockindex); + + // delete old network history records + NetworkHistory.deleteMany({blockindex: {$in: ids}}).then(() => { + console.log('Network history update complete'); + return cb(); + }).catch((err) => { + console.log(err); + return cb(); + }); + } else { + // no records need to be deleted + console.log('Network history update complete'); + return cb(); + } + }).catch((err) => { + console.log(err); + return cb(); + }); + } }).catch((err) => { - console.log(err); + console.log('Error updating network history: ' + err); return cb(); }); - }).catch((err) => { - console.log(err); + } else { + console.log(`Error updating network history: Cannot find block with hash ${blockhash}`); return cb(); - }); - } else { - console.log('Network history update complete'); - return cb(); - } - }).catch((err) => { - console.log(err); + } + }); + } else { + console.log(`Error updating network history: Cannot find block hash with height ${height}`); return cb(); - }); - }).catch((err) => { - console.log('Error updating network history: ' + err); - return cb(); + } }); }); }); @@ -1928,7 +1958,10 @@ module.exports = { check_rename_db_field(NetworkHistory, 'difficulty', 'difficulty_pow', function(renamed) { // determine if difficulty_pos field exists check_add_db_field(NetworkHistory, 'difficulty_pos', 0, function(exists) { - return cb(true); + // determine if timestamp field exists + check_add_db_field(NetworkHistory, 'timestamp', 0, function(timestamp_exists) { + return cb(true); + }); }); }); } else diff --git a/lib/settings.js b/lib/settings.js index 6b6b71b..da53710 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -86,7 +86,9 @@ exports.network_history = { // If set to false, historical data such as network hashrate and difficulty values will not be saved or available for network charts "enabled": true, // max_saved_records: The maximum # of blocks to save historical data for - "max_saved_records": 120 + "max_saved_records": 120, + // max_hours: The maximum # of hours to save historical data for. The max_saved_records value will supercede this value so be sure to set max_saved_records to 0 if wanting to display data for a certain number of hours + "max_hours": 24 }; /* Shared page settings */ @@ -343,6 +345,51 @@ exports.shared_pages = { // If set to false, the network hashrate chart will be completely inaccessible // NOTE: The `shared_pages.show_hashrate` option must be set to true or else the network hashrate chart will be completely inaccessible "enabled": true, + // chart_title: A collection of settings that pertain to the chart title + "chart_title": { + // enabled: Enable/disable the chart title (true/false) + // If set to false, the chart title will be completely absent from the chart + "enabled": false, + // title_text: The text to display in the chart title + // NOTE: You can add a "/n" character for a carriage return + // The following keywords can be used in the chart title: + // {coin_name}: Display the name of the coin from the `coin.name` setting + // {max_saved_records}: Display the number of records the chart is showing data for from the `network_history.max_saved_records` setting + // {max_hours}: Display the number of hours the chart is showing data for from the `network_history.max_hours` setting + // {current_nethash}: Display the most recent nethash value from the network chart data + // {highest_nethash}: Display the highest nethash value from the network chart data + // {lowest_nethash}: Display the lowest nethash value from the network chart data + // {highest_block}: Display the highest block height value from the network chart data + // {lowest_block}: Display the lowest block height value from the network chart data + "title_text": "{coin_name} Network Hashrate History for the last {max_hours} hours\nHigh: {highest_nethash}\nCurrent: {current_nethash}\nLow: {lowest_nethash}\nBlock: {highest_block}", + // alignment: Determine how to align the chart title text horizontally + // Valid options are left, center and right + "alignment": "center", + // color: Change the chart title text color + // Set this to any valid html color + // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" + "color": "#666666", + // font: A collection of settings that pertain to the chart title font + "font": { + // family: The font to use for the chart title + // Valid options are: Arial, Verdana, Times New Roman, Georgia, Courier New, Tahoma + "family": "Arial", + // size: Determine the size of the font text in pixels + "size": 20, + // weight: Determine the thickness of the font text + // Valid options are: normal, bold, bolder, lighter, 100, 200, 300, 400, 500, 600, 700, 800, 900 + "weight": "bold" + } + }, + // legend: A collection of settings that pertain to the chart legend + "legend": { + // enabled: Enable/disable the chart legend (true/false) + // If set to false, the legend will be completely absent from the chart + "enabled": true, + // position: Determine where to position the chart legend + // Valid options are top, left, bottom and right + "position": "bottom" + }, // bgcolor: Change the background color of the network hashrate chart // Set this to any valid html color // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" @@ -359,15 +406,85 @@ exports.shared_pages = { // Set this to any valid html color // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" "crosshair_color": "#000000", + // block_line: A collection of settings that pertain to the vertical block line on the network hashrate chart + "block_line": { + // enabled: Enable/disable the vertical block line (true/false) + // If set to false, the vertical block line will be completely hidden from the chart + // NOTE: The `network_history.max_saved_records` option must also be set to 0 or else the vertical block line will be completely hidden from the chart + "enabled": true, + // block_line_color: Change the line color of the block data for the network hashrate chart + // Set this to any valid html color + // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" + "block_line_color": "rgba(0, 128, 0, 0.2)" + }, // round_decimals: Set how many decimal places the hash rates are rounded to (Max 20) // NOTE: Set to a value of -1 to output the raw value without rounding - "round_decimals": 3 + "round_decimals": 3, + // chart_height: Height of chart in px + "chart_height": 300, + // full_row: Determine whether the chart will appear in its own row or can have another chart beside it in the same row + // If set to true, the chart will take up the full width of the page + // If set to false, and another chart is configured to display, the 2nd chart will appear beside this chart, both taking up 50% of the width of the screen + // NOTE: On smaller screens such as mobile phones and some tablets, each chart will take up it's own row regardless of what the full_row setting is set to due to limited screen real estate + "full_row": false, + // stretch_to_fit: Determine if the chart should be fit inside the surrounding chart box with or without padding + // If set to true, the chart will take up the full available space inside the chart box + // If set to false, there will be some padding on all 4 sides of the chart box + "stretch_to_fit": false }, // difficulty_chart: A collection of settings that pertain to the network difficulty chart "difficulty_chart": { // enabled: Enable/disable the network difficulty chart (true/false) // If set to false, the network difficulty chart will be completely inaccessible "enabled": true, + // chart_title: A collection of settings that pertain to the chart title + "chart_title": { + // enabled: Enable/disable the chart title (true/false) + // If set to false, the chart title will be completely absent from the chart + "enabled": false, + // title_text: The text to display in the chart title + // NOTE: You can add a "/n" character for a carriage return + // The following keywords can be used in the chart title: + // {coin_name}: Display the name of the coin from the `coin.name` setting + // {max_saved_records}: Display the number of records the chart is showing data for from the `network_history.max_saved_records` setting + // {max_hours}: Display the number of hours the chart is showing data for from the `network_history.max_hours` setting + // {current_pow_difficulty}: Display the most recent POW difficulty value from the network chart data + // {highest_pow_difficulty}: Display the highest POW difficulty value from the network chart data + // {lowest_pow_difficulty}: Display the lowest POW difficulty value from the network chart data + // {current_pos_difficulty}: Display the most recent POS difficulty value from the network chart data + // {highest_pos_difficulty}: Display the highest POS difficulty value from the network chart data + // {lowest_pos_difficulty}: Display the lowest POS difficulty value from the network chart data + // {highest_block}: Display the highest block height value from the network chart data + // {lowest_block}: Display the lowest block height value from the network chart data + "title_text": "{coin_name} Difficulty Rate History for the last {max_hours} hours\nHigh: {highest_pos_difficulty}\nCurrent: {current_pos_difficulty}\nLow: {lowest_pos_difficulty}\nBlock: {highest_block}", + // alignment: Determine how to align the chart title text horizontally + // Valid options are left, center and right + "alignment": "center", + // color: Change the chart title text color + // Set this to any valid html color + // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" + "color": "#666666", + // font: A collection of settings that pertain to the chart title font + "font": { + // family: The font to use for the chart title + // Valid options are: Arial, Verdana, Times New Roman, Georgia, Courier New, Tahoma + "family": "Arial", + // size: Determine the size of the font text in pixels + "size": 20, + // weight: Determine the thickness of the font text + // Valid options are: normal, bold, bolder, lighter, 100, 200, 300, 400, 500, 600, 700, 800, 900 + "weight": "bold" + } + }, + // legend: A collection of settings that pertain to the chart legend + "legend": { + // enabled: Enable/disable the chart legend (true/false) + // If set to false, the legend will be completely absent from the chart + "enabled": true, + // position: Determine where to position the chart legend + // Valid options are top, left, bottom and right + "position": "bottom" + }, // bgcolor: Change the background color of the network difficulty chart // Set this to any valid html color // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" @@ -392,13 +509,39 @@ exports.shared_pages = { // Set this to any valid html color // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" "crosshair_color": "#000000", + // block_line: A collection of settings that pertain to the vertical block line on the network difficulty chart + "block_line": { + // enabled: Enable/disable the vertical block line (true/false) + // If set to false, the vertical block line will be completely hidden from the chart + // NOTE: The `network_history.max_saved_records` option must also be set to 0 or else the vertical block line will be completely hidden from the chart + "enabled": true, + // block_line_color: Change the line color of the block data for the network difficulty chart + // Set this to any valid html color + // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" + "block_line_color": "rgba(0, 128, 0, 0.2)" + }, // round_decimals: Set how many decimal places the difficulty values are rounded to (Max 20) // NOTE: Set to a value of -1 to output the raw value without rounding - "round_decimals": 3 + "round_decimals": 3, + // chart_height: Height of chart in px + "chart_height": 300, + // full_row: Determine whether the chart will appear in its own row or can have another chart beside it in the same row + // If set to true, the chart will take up the full width of the page + // If set to false, and another chart is configured to display, the 2nd chart will appear beside this chart, both taking up 50% of the width of the screen + // NOTE: On smaller screens such as mobile phones and some tablets, each chart will take up it's own row regardless of what the full_row setting is set to due to limited screen real estate + "full_row": false, + // stretch_to_fit: Determine if the chart should be fit inside the surrounding chart box with or without padding + // If set to true, the chart will take up the full available space inside the chart box + // If set to false, there will be some padding on all 4 sides of the chart box + "stretch_to_fit": false }, // reload_chart_seconds: The time in seconds to automatically reload the network chart data from the server // Set to 0 to disable automatic reloading of chart data - "reload_chart_seconds": 60 + "reload_chart_seconds": 60, + // sync_charts: Determine if multiple network charts should be synced while cycling through different data points + // If set to true, all network charts will be synced so that when clicking on or moving the mouse cursor over a particular chart, all other network charts will also have their crosshairs set to the same data point for easy comparison + // If set to false, only the chart that is currently being manipulated will display a crosshair for the data point being viewed + "sync_charts": false } }, // page_footer: A collection of settings that pertain to the page footer that is displayed at the bottom of all pages diff --git a/models/networkhistory.js b/models/networkhistory.js index 889491a..9b67e6a 100644 --- a/models/networkhistory.js +++ b/models/networkhistory.js @@ -5,7 +5,8 @@ var NetworkHistorySchema = new Schema({ blockindex: {type: Number, default: 0, index: true}, nethash: { type: Number, default: 0 }, difficulty_pow: { type: Number, default: 0 }, - difficulty_pos: { type: Number, default: 0 } + difficulty_pos: { type: Number, default: 0 }, + timestamp: { type: Number, default: 0 } }, {id: false}); module.exports = mongoose.model('NetworkHistory', NetworkHistorySchema); \ No newline at end of file diff --git a/settings.json.template b/settings.json.template index 30b43c6..8ccaa6a 100644 --- a/settings.json.template +++ b/settings.json.template @@ -87,8 +87,10 @@ // enabled: Enable/disable the saving of additional network history data (true/false) // If set to false, historical data such as network hashrate and difficulty values will not be saved or available for network charts "enabled": true, - // max_saved_records: The maximum # of blocks to save historical data for - "max_saved_records": 120 + // max_saved_records: The maximum # of blocks to save historical data for. Set this value to 0 if using the max_hours option + "max_saved_records": 120, + // max_hours: The maximum # of hours to save historical data for. The max_saved_records value will supercede this value so be sure to set max_saved_records to 0 if wanting to display data for a certain number of hours + "max_hours": 24 }, /* Shared page settings */ @@ -345,6 +347,51 @@ // If set to false, the network hashrate chart will be completely inaccessible // NOTE: The `shared_pages.show_hashrate` option must be set to true or else the network hashrate chart will be completely inaccessible "enabled": true, + // chart_title: A collection of settings that pertain to the chart title + "chart_title": { + // enabled: Enable/disable the chart title (true/false) + // If set to false, the chart title will be completely absent from the chart + "enabled": false, + // title_text: The text to display in the chart title + // NOTE: You can add a "/n" character for a carriage return + // The following keywords can be used in the chart title: + // {coin_name}: Display the name of the coin from the `coin.name` setting + // {max_saved_records}: Display the number of records the chart is showing data for from the `network_history.max_saved_records` setting + // {max_hours}: Display the number of hours the chart is showing data for from the `network_history.max_hours` setting + // {current_nethash}: Display the most recent nethash value from the network chart data + // {highest_nethash}: Display the highest nethash value from the network chart data + // {lowest_nethash}: Display the lowest nethash value from the network chart data + // {highest_block}: Display the highest block height value from the network chart data + // {lowest_block}: Display the lowest block height value from the network chart data + "title_text": "{coin_name} Network Hashrate History for the last {max_hours} hours\nHigh: {highest_nethash}\nCurrent: {current_nethash}\nLow: {lowest_nethash}\nBlock: {highest_block}", + // alignment: Determine how to align the chart title text horizontally + // Valid options are left, center and right + "alignment": "center", + // color: Change the chart title text color + // Set this to any valid html color + // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" + "color": "#666666", + // font: A collection of settings that pertain to the chart title font + "font": { + // family: The font to use for the chart title + // Valid options are: Arial, Verdana, Times New Roman, Georgia, Courier New, Tahoma + "family": "Arial", + // size: Determine the size of the font text in pixels + "size": 20, + // weight: Determine the thickness of the font text + // Valid options are: normal, bold, bolder, lighter, 100, 200, 300, 400, 500, 600, 700, 800, 900 + "weight": "bold" + } + }, + // legend: A collection of settings that pertain to the chart legend + "legend": { + // enabled: Enable/disable the chart legend (true/false) + // If set to false, the legend will be completely absent from the chart + "enabled": true, + // position: Determine where to position the chart legend + // Valid options are top, left, bottom and right + "position": "bottom" + }, // bgcolor: Change the background color of the network hashrate chart // Set this to any valid html color // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" @@ -361,15 +408,85 @@ // Set this to any valid html color // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" "crosshair_color": "#000000", + // block_line: A collection of settings that pertain to the vertical block line on the network hashrate chart + "block_line": { + // enabled: Enable/disable the vertical block line (true/false) + // If set to false, the vertical block line will be completely hidden from the chart + // NOTE: The `network_history.max_saved_records` option must also be set to 0 or else the vertical block line will be completely hidden from the chart + "enabled": true, + // block_line_color: Change the line color of the block data for the network hashrate chart + // Set this to any valid html color + // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" + "block_line_color": "rgba(0, 128, 0, 0.2)" + }, // round_decimals: Set how many decimal places the hash rates are rounded to (Max 20) // NOTE: Set to a value of -1 to output the raw value without rounding - "round_decimals": 3 + "round_decimals": 3, + // chart_height: Height of chart in px + "chart_height": 300, + // full_row: Determine whether the chart will appear in its own row or can have another chart beside it in the same row + // If set to true, the chart will take up the full width of the page + // If set to false, and another chart is configured to display, the 2nd chart will appear beside this chart, both taking up 50% of the width of the screen + // NOTE: On smaller screens such as mobile phones and some tablets, each chart will take up it's own row regardless of what the full_row setting is set to due to limited screen real estate + "full_row": false, + // stretch_to_fit: Determine if the chart should be fit inside the surrounding chart box with or without padding + // If set to true, the chart will take up the full available space inside the chart box + // If set to false, there will be some padding on all 4 sides of the chart box + "stretch_to_fit": false }, // difficulty_chart: A collection of settings that pertain to the network difficulty chart "difficulty_chart": { // enabled: Enable/disable the network difficulty chart (true/false) // If set to false, the network difficulty chart will be completely inaccessible "enabled": true, + // chart_title: A collection of settings that pertain to the chart title + "chart_title": { + // enabled: Enable/disable the chart title (true/false) + // If set to false, the chart title will be completely absent from the chart + "enabled": false, + // title_text: The text to display in the chart title + // NOTE: You can add a "/n" character for a carriage return + // The following keywords can be used in the chart title: + // {coin_name}: Display the name of the coin from the `coin.name` setting + // {max_saved_records}: Display the number of records the chart is showing data for from the `network_history.max_saved_records` setting + // {max_hours}: Display the number of hours the chart is showing data for from the `network_history.max_hours` setting + // {current_pow_difficulty}: Display the most recent POW difficulty value from the network chart data + // {highest_pow_difficulty}: Display the highest POW difficulty value from the network chart data + // {lowest_pow_difficulty}: Display the lowest POW difficulty value from the network chart data + // {current_pos_difficulty}: Display the most recent POS difficulty value from the network chart data + // {highest_pos_difficulty}: Display the highest POS difficulty value from the network chart data + // {lowest_pos_difficulty}: Display the lowest POS difficulty value from the network chart data + // {highest_block}: Display the highest block height value from the network chart data + // {lowest_block}: Display the lowest block height value from the network chart data + "title_text": "{coin_name} Difficulty Rate History for the last {max_hours} hours\nHigh: {highest_pos_difficulty}\nCurrent: {current_pos_difficulty}\nLow: {lowest_pos_difficulty}\nBlock: {highest_block}", + // alignment: Determine how to align the chart title text horizontally + // Valid options are left, center and right + "alignment": "center", + // color: Change the chart title text color + // Set this to any valid html color + // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" + "color": "#666666", + // font: A collection of settings that pertain to the chart title font + "font": { + // family: The font to use for the chart title + // Valid options are: Arial, Verdana, Times New Roman, Georgia, Courier New, Tahoma + "family": "Arial", + // size: Determine the size of the font text in pixels + "size": 20, + // weight: Determine the thickness of the font text + // Valid options are: normal, bold, bolder, lighter, 100, 200, 300, 400, 500, 600, 700, 800, 900 + "weight": "bold" + } + }, + // legend: A collection of settings that pertain to the chart legend + "legend": { + // enabled: Enable/disable the chart legend (true/false) + // If set to false, the legend will be completely absent from the chart + "enabled": true, + // position: Determine where to position the chart legend + // Valid options are top, left, bottom and right + "position": "bottom" + }, // bgcolor: Change the background color of the network difficulty chart // Set this to any valid html color // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" @@ -394,13 +511,39 @@ // Set this to any valid html color // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" "crosshair_color": "#000000", + // block_line: A collection of settings that pertain to the vertical block line on the network difficulty chart + "block_line": { + // enabled: Enable/disable the vertical block line (true/false) + // If set to false, the vertical block line will be completely hidden from the chart + // NOTE: The `network_history.max_saved_records` option must also be set to 0 or else the vertical block line will be completely hidden from the chart + "enabled": true, + // block_line_color: Change the line color of the block data for the network difficulty chart + // Set this to any valid html color + // Ex: "#ffffff" or "rgba(255, 255, 255, 1)" or "white" + "block_line_color": "rgba(0, 128, 0, 0.2)" + }, // round_decimals: Set how many decimal places the difficulty values are rounded to (Max 20) // NOTE: Set to a value of -1 to output the raw value without rounding - "round_decimals": 3 + "round_decimals": 3, + // chart_height: Height of chart in px + "chart_height": 300, + // full_row: Determine whether the chart will appear in its own row or can have another chart beside it in the same row + // If set to true, the chart will take up the full width of the page + // If set to false, and another chart is configured to display, the 2nd chart will appear beside this chart, both taking up 50% of the width of the screen + // NOTE: On smaller screens such as mobile phones and some tablets, each chart will take up it's own row regardless of what the full_row setting is set to due to limited screen real estate + "full_row": false, + // stretch_to_fit: Determine if the chart should be fit inside the surrounding chart box with or without padding + // If set to true, the chart will take up the full available space inside the chart box + // If set to false, there will be some padding on all 4 sides of the chart box + "stretch_to_fit": false }, // reload_chart_seconds: The time in seconds to automatically reload the network chart data from the server // Set to 0 to disable automatic reloading of chart data - "reload_chart_seconds": 60 + "reload_chart_seconds": 60, + // sync_charts: Determine if multiple network charts should be synced while cycling through different data points + // If set to true, all network charts will be synced so that when clicking on or moving the mouse cursor over a particular chart, all other network charts will also have their crosshairs set to the same data point for easy comparison + // If set to false, only the chart that is currently being manipulated will display a crosshair for the data point being viewed + "sync_charts": true } }, // page_footer: A collection of settings that pertain to the page footer that is displayed at the bottom of all pages diff --git a/views/address.pug b/views/address.pug index e9e7bd4..937ed06 100644 --- a/views/address.pug +++ b/views/address.pug @@ -1,7 +1,6 @@ extends layout block content - include ./includes/common.pug script. var hashAddress = "#{address.a_id}"; var setting_maxTxCount = parseInt("#{settings.api_page.public_apis.ext.getaddresstxs.max_items_per_query}"); diff --git a/views/block.pug b/views/block.pug index fe4dcce..a04ea6b 100644 --- a/views/block.pug +++ b/views/block.pug @@ -1,7 +1,6 @@ extends layout block content - include ./includes/common.pug - var block_difficulty = parseFloat(block.difficulty).toFixed(4); - var theadClasses = []; if settings.shared_pages.table_header_bgcolor != null && settings.shared_pages.table_header_bgcolor != '' diff --git a/views/includes/difficulty_chart.pug b/views/includes/difficulty_chart.pug new file mode 100644 index 0000000..471186b --- /dev/null +++ b/views/includes/difficulty_chart.pug @@ -0,0 +1,6 @@ +div#difficultyChartParent(class=chartColumnClass, style='display:none;margin:10px 0;') + .card.card-default.border-0 + .card-header + strong Network Difficulty + .card-body(style=(settings.shared_pages.page_header.network_charts.difficulty_chart.stretch_to_fit == true ? 'padding:0!important;' : '')) + canvas#difficultyChart(style='min-height:'+settings.shared_pages.page_header.network_charts.difficulty_chart.chart_height+'px;max-height:'+settings.shared_pages.page_header.network_charts.difficulty_chart.chart_height+'px;background-color:'+settings.shared_pages.page_header.network_charts.difficulty_chart.bgcolor+';') \ No newline at end of file diff --git a/views/includes/nethash_chart.pug b/views/includes/nethash_chart.pug new file mode 100644 index 0000000..43cec6b --- /dev/null +++ b/views/includes/nethash_chart.pug @@ -0,0 +1,6 @@ +div#nethashChartParent(class=chartColumnClass, style='display:none;margin:10px 0;') + .card.card-default.border-0 + .card-header + strong Network Hashrate + .card-body(style=(settings.shared_pages.page_header.network_charts.nethash_chart.stretch_to_fit == true ? 'padding:0!important;' : '')) + canvas#nethashChart(style='min-height:'+settings.shared_pages.page_header.network_charts.nethash_chart.chart_height+'px;max-height:'+settings.shared_pages.page_header.network_charts.nethash_chart.chart_height+'px;background-color:'+settings.shared_pages.page_header.network_charts.nethash_chart.bgcolor+';') \ No newline at end of file diff --git a/views/index.pug b/views/index.pug index eaeca41..11c3dab 100644 --- a/views/index.pug +++ b/views/index.pug @@ -1,7 +1,6 @@ extends layout block content - include ./includes/common.pug script. var setting_maxTxCount = parseInt("#{settings.api_page.public_apis.ext.getlasttxs.max_items_per_query}"); var setting_txPerPage = parseInt("#{settings.index_page.transaction_table.items_per_page}"); diff --git a/views/layout.pug b/views/layout.pug index 76f55b8..8487000 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -157,12 +157,25 @@ html(lang='en') - showNethashChart = true if settings.error_page.show_difficulty_chart == true - showDifficultyChart = true - if active == 'markets' || active == 'richlist' || active == 'reward' || (settings.network_history.enabled == true && ((showNethashChart == true && settings.shared_pages.page_header.network_charts.nethash_chart.enabled == true && settings.shared_pages.show_hashrate == true) || (showDifficultyChart == true && settings.shared_pages.page_header.network_charts.difficulty_chart.enabled == true))) - script(type='text/javascript', src='https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js') + if settings.network_history.enabled == true + if !(showNethashChart == true && settings.shared_pages.page_header.network_charts.nethash_chart.enabled == true && settings.shared_pages.show_hashrate == true) + - showNethashChart = false + if !(showDifficultyChart == true && settings.shared_pages.page_header.network_charts.difficulty_chart.enabled == true) + - showDifficultyChart = false + else + - showNethashChart = false + - showDifficultyChart = false + if active == 'home' || active == 'address' || active == 'block' || active == 'markets' || active == 'masternodes' || active == 'movement' || active == 'network' || active == 'orphans' || active == 'reward' || active == 'richlist' || active == 'tx' + include ./includes/common.pug + if active == 'markets' || active == 'richlist' || active == 'reward' || showNethashChart == true || showDifficultyChart == true + script(type='text/javascript', src='https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js') if active == 'markets' script(type='text/javascript', src='https://cdn.jsdelivr.net/npm/chartjs-chart-financial@0.1.1/dist/chartjs-chart-financial.min.js') - if active == 'markets' || (settings.network_history.enabled == true && ((showNethashChart == true && settings.shared_pages.page_header.network_charts.nethash_chart.enabled == true && settings.shared_pages.show_hashrate == true) || (showDifficultyChart == true && settings.shared_pages.page_header.network_charts.difficulty_chart.enabled == true))) - script(type='text/javascript', src='https://cdn.jsdelivr.net/npm/chartjs-plugin-crosshair@2.0.0') + if active == 'markets' || showNethashChart == true || showDifficultyChart == true + script(type='text/javascript', src='https://cdn.jsdelivr.net/gh/joeuhren/chartjs-plugin-crosshair@v2.0.5/build/chartjs-plugin-crosshair.min.js') + script(type='text/javascript', src='https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.3.1') + if showNethashChart == true || showDifficultyChart == true + script(type='text/javascript', src='https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@3.1.0/dist/chartjs-plugin-annotation.min.js') - var sideBarClasses = []; if settings.shared_pages.page_header.bgcolor != null && settings.shared_pages.page_header.bgcolor != '' - sideBarClasses.push('bg-' + settings.shared_pages.page_header.bgcolor); @@ -497,8 +510,9 @@ html(lang='en') update_stats(); }); - if settings.network_history.enabled == true && ((showNethashChart == true && settings.shared_pages.page_header.network_charts.nethash_chart.enabled == true && settings.shared_pages.show_hashrate == true) || (showDifficultyChart == true && settings.shared_pages.page_header.network_charts.difficulty_chart.enabled == true)) + if showNethashChart == true || showDifficultyChart == true script. + const maxSavedRecords = #{settings.network_history.max_saved_records}; const countDecimals = function(value) { const text = value.toString(); @@ -521,6 +535,223 @@ html(lang='en') } }); } + function roundToNearestMinute(timestamp) { + return Math.floor(timestamp * 1000 / (60 * 1000)) * (60 * 1000); + } + function formatNetworkChartValue(val, roundDecimals, lbl, returnNum) { + let max = 20; + + if (roundDecimals != -1) + max = roundDecimals; + else { + const decimalCount = countDecimals(val || 0); + + if (decimalCount < max) + max = decimalCount; + } + + if (returnNum) + return (val || 0); + else + return Number((val || 0)).toLocaleString('en',{'minimumFractionDigits':0,'maximumFractionDigits':max,'useGrouping':true}) + (lbl == 'Hashrate' ? ' ' + getNetHashUnits() : ''); + } + function populateHourChartData(result) { + const roundedDataMap = new Map(); + + result.forEach((entry) => { + const roundedTimestamp = roundToNearestMinute(entry.timestamp); + + if (roundedTimestamp != 0 && !roundedDataMap.has(roundedTimestamp)) + roundedDataMap.set(roundedTimestamp, { ...entry, tick: roundedTimestamp }); + }); + + let normalizedData = Array.from(roundedDataMap.values()).sort((a, b) => a.tick - b.tick); + let lastKnownValue = { blockindex: 0, difficulty_pow: 0, difficulty_pos: 0, nethash: 0 }; + let maxTimestamp = new Date().getTime(); + let hourData = []; + + if (normalizedData.length > 0) + maxTimestamp = normalizedData[normalizedData.length - 1].tick; + + const minTimestamp = maxTimestamp - ((#{settings.network_history.max_hours} * 60 * 60 * 1000) - (60 * 1000)); // x hours before maxTimestamp minus 1 minute to encompass the full timeframe + + if (normalizedData.length > 0 && normalizedData[0].tick > minTimestamp) { + // use the first real data point values but set blockindex to 0 + lastKnownValue = { ...normalizedData[0], blockindex: 0 }; + + // backfill from minTimestamp up to the first real data point + for (let ts = minTimestamp; ts < normalizedData[0].tick; ts += 60 * 1000) + hourData.push({ ...lastKnownValue, tick: ts }); + } + + let currentTimestamp = Math.max(minTimestamp, normalizedData[0]?.tick || minTimestamp); + + for (let i = 0; i < normalizedData.length; i++) { + const dataPoint = normalizedData[i]; + + // fill any gap between currentTimestamp and the current dataPoint.tick + while (currentTimestamp < dataPoint.tick) { + hourData.push({ ...lastKnownValue, tick: currentTimestamp, blockindex: 0 }); + currentTimestamp += 60 * 1000; // move to the next minute + } + + // add the real data point to hourData + lastKnownValue = dataPoint; + hourData.push({ ...dataPoint, tick: dataPoint.tick }); + + // move currentTimestamp to the next minute after the current data point + currentTimestamp = dataPoint.tick + 60 * 1000; + } + + // fill remaining range up to maxTimestamp with last known values + while (currentTimestamp < maxTimestamp) { + hourData.push({ ...lastKnownValue, tick: currentTimestamp, blockindex: 0 }); + currentTimestamp += 60 * 1000; + } + + xScale = { + type: 'time', + time: { + unit: 'minute', + stepSize: 60, + displayFormats: { + minute: '#{settings.shared_pages.date_time.display_format}' + } + }, + min: minTimestamp, // Set x-axis to start at the first data point + max: maxTimestamp, // Set x-axis to end at the last data point + ticks: { + maxTicksLimit: #{settings.network_history.max_hours}, + callback: function(value) { + return format_unixtime(Math.floor(value / 1000)); + } + } + }; + + return { + hourData: hourData, + minTimestamp: minTimestamp, + maxTimestamp: maxTimestamp, + xScale: xScale + }; + } + function populatePluginDataTitle(chartData, chartTitleSettings, roundDecimals, netHashOnly, difficultyOnly) { + let showPOW = false; + let showPOS = false; + let highestNethash = 0; + let lowestNethash = -1; + let highestPowDifficulty = 0; + let lowestPowDifficulty = -1; + let highestPosDifficulty = 0; + let lowestPosDifficulty = -1; + let highestBlock = 0; + let lowestBlock = -1; + let pluginTitle = {}; + const currentNethash = formatNetworkChartValue((chartData.length > 0 && chartData[0].nethash != null ? chartData[chartData.length - 1].nethash : 0), roundDecimals, 'Hashrate', true); + const currentPowDifficulty = formatNetworkChartValue((chartData.length > 0 && chartData[0].difficulty_pow != null ? chartData[chartData.length - 1].difficulty_pow : 0), roundDecimals, 'Difficulty', true); + const currentPosDifficulty = formatNetworkChartValue((chartData.length > 0 && chartData[0].difficulty_pos != null ? chartData[chartData.length - 1].difficulty_pos : 0), roundDecimals, 'Difficulty', true); + + for (let i = 0; i < chartData.length; i++) { + if (chartData[i].difficulty_pow != null && chartData[i].difficulty_pow != 0) + showPOW = true; + if (chartData[i].difficulty_pos != null && chartData[i].difficulty_pos != 0) + showPOS = true; + if (chartData[i].nethash != null && chartData[i].nethash > highestNethash) + highestNethash = formatNetworkChartValue(chartData[i].nethash, roundDecimals, 'Hashrate', true); + if (chartData[i].nethash != null && (lowestNethash == -1 || chartData[i].nethash < lowestNethash)) + lowestNethash = formatNetworkChartValue(chartData[i].nethash, roundDecimals, 'Hashrate', true); + if (chartData[i].difficulty_pow != null && chartData[i].difficulty_pow > highestPowDifficulty) + highestPowDifficulty = formatNetworkChartValue(chartData[i].difficulty_pow, roundDecimals, 'Difficulty', true); + if (chartData[i].difficulty_pow != null && (lowestPowDifficulty == -1 || chartData[i].difficulty_pow < lowestPowDifficulty)) + lowestPowDifficulty = formatNetworkChartValue(chartData[i].difficulty_pow, roundDecimals, 'Difficulty', true); + if (chartData[i].difficulty_pos != null && chartData[i].difficulty_pos > highestPosDifficulty) + highestPosDifficulty = formatNetworkChartValue(chartData[i].difficulty_pos, roundDecimals, 'Difficulty', true); + if (chartData[i].difficulty_pos != null && (lowestPosDifficulty == -1 || chartData[i].difficulty_pos < lowestPosDifficulty)) + lowestPosDifficulty = formatNetworkChartValue(chartData[i].difficulty_pos, roundDecimals, 'Difficulty', true); + if (chartData[i].blockindex > highestBlock) + highestBlock = chartData[i].blockindex; + if (chartData[i].blockindex > 0 && (lowestBlock == -1 || chartData[i].blockindex < lowestBlock)) + lowestBlock = chartData[i].blockindex; + } + + if (chartTitleSettings.enabled == true) { + let chart_title = chartTitleSettings.title_text.replace(/\n/g, '\\n') + .replace(/{coin_name}/g, '#{settings.coin.name}') + .replace(/{max_saved_records}/g, maxSavedRecords) + .replace(/{max_hours}/g, '#{settings.network_history.max_hours}') + .replace(/{highest_block}/g, highestBlock) + .replace(/{lowest_block}/g, lowestBlock); + + if (netHashOnly || (!netHashOnly && !difficultyOnly)) { + chart_title = chart_title + .replace(/{current_nethash}/g, formatNetworkChartValue(currentNethash, roundDecimals, 'Hashrate', false)) + .replace(/{highest_nethash}/g, formatNetworkChartValue(highestNethash, roundDecimals, 'Hashrate', false)) + .replace(/{lowest_nethash}/g, formatNetworkChartValue(lowestNethash, roundDecimals, 'Hashrate', false)); + } + + if (difficultyOnly || (!netHashOnly && !difficultyOnly)) { + chart_title = chart_title + .replace(/{current_pow_difficulty}/g, formatNetworkChartValue(currentPowDifficulty, roundDecimals, 'Difficulty', false)) + .replace(/{highest_pow_difficulty}/g, formatNetworkChartValue(highestPowDifficulty, roundDecimals, 'Difficulty', false)) + .replace(/{lowest_pow_difficulty}/g, formatNetworkChartValue(lowestPowDifficulty, roundDecimals, 'Difficulty', false)) + .replace(/{current_pos_difficulty}/g, formatNetworkChartValue(currentPosDifficulty, roundDecimals, 'Difficulty', false)) + .replace(/{highest_pos_difficulty}/g, formatNetworkChartValue(highestPosDifficulty, roundDecimals, 'Difficulty', false)) + .replace(/{lowest_pos_difficulty}/g, formatNetworkChartValue(lowestPosDifficulty, roundDecimals, 'Difficulty', false)) + } + + pluginTitle = { + display: true, + text: chart_title.split('\\n'), + position: 'top', + align: chartTitleSettings.alignment.replace('left', 'start').replace('right', 'end'), + color: chartTitleSettings.color, + font: { + family: chartTitleSettings.font.family, + size: chartTitleSettings.font.size, + weight: chartTitleSettings.font.weight + }, + padding: { + top: 10, + bottom: 10 + } + }; + } else { + pluginTitle = { + display: false + }; + } + + return { + showPOW: showPOW, + showPOS: showPOS, + pluginTitle: pluginTitle + }; + } + function populateBlockIndexAnnotations(hourData, blockLineSettings) { + let blockIndexAnnotations = null; + + if (blockLineSettings.enabled && maxSavedRecords == 0) { + blockIndexAnnotations = { + annotations: ( + hourData + .filter(d => d.blockindex) + .map(d => ({ + type: 'line', + mode: 'vertical', + scaleID: 'x', + value: d.tick, + borderColor: blockLineSettings.block_line_color, + borderWidth: 1, + label: { + enabled: false + } + })) + ) + }; + } + + return blockIndexAnnotations; + } $(document).ready(function() { const setting_reload_chart_seconds = #{settings.shared_pages.page_header.network_charts.reload_chart_seconds}; @@ -532,27 +763,101 @@ html(lang='en') update_network_charts(); }); - if settings.network_history.enabled == true && showNethashChart == true && settings.shared_pages.page_header.network_charts.nethash_chart.enabled == true && settings.shared_pages.show_hashrate == true + if showNethashChart == true script. let nethashChart; function populateNethashChart(result) { const ctxNethash = document.getElementById('nethashChart').getContext('2d'); + let timeData = {}; + + if (maxSavedRecords == 0) + timeData = populateHourChartData(result); + + const extraData = populatePluginDataTitle((maxSavedRecords == 0 ? timeData.hourData : result), !{JSON.stringify(settings.shared_pages.page_header.network_charts.nethash_chart.chart_title)}, #{settings.shared_pages.page_header.network_charts.nethash_chart.round_decimals}, true, false); + + const netHashDataSet = [ + { + label: 'Hashrate', + data: (maxSavedRecords == 0 ? + timeData.hourData.map(a => ({ + x: a.tick, + y: a.nethash, + blockindex: a.blockindex + })) : + result.map(function(a) {return a.nethash;}) + ), + backgroundColor: ['#{settings.shared_pages.page_header.network_charts.nethash_chart.fill_color}'], + borderColor: ['#{settings.shared_pages.page_header.network_charts.nethash_chart.line_color}'], + fill: 'start' + } + ]; + + const pluginData = { + title: extraData.pluginTitle, + legend: { + display: #{settings.shared_pages.page_header.network_charts.nethash_chart.legend.enabled}, + position: '#{settings.shared_pages.page_header.network_charts.nethash_chart.legend.position}' + }, + tooltip: { + mode: 'index', + intersect: false, + displayColors: true, + callbacks: { + title: function(context) { + if (maxSavedRecords == 0) + return format_unixtime(Math.floor(context[0].parsed.x / 1000)); + else + return 'Block ' + context[0].label + ' Hashrate'; + }, + label: function(context) { + if (maxSavedRecords == 0) + return formatNetworkChartValue(context.raw.y, #{settings.shared_pages.page_header.network_charts.nethash_chart.round_decimals}, context.dataset.label, false); + else + return formatNetworkChartValue(context.raw, #{settings.shared_pages.page_header.network_charts.nethash_chart.round_decimals}, context.dataset.label, false); + }, + afterLabel: function(context) { + if (maxSavedRecords == 0) { + const tooltipItems = context.chart.tooltip.dataPoints; + const isLastVisibleItem = tooltipItems[tooltipItems.length - 1] === context; + + // only display blockindex if this is the last visible tooltip item + if (isLastVisibleItem) { + const blockindex = context.raw.blockindex; + + if (blockindex && blockindex !== 0) + return `Block: ${blockindex}`; + } + } + + return ''; + } + } + }, + crosshair: { + line: { + color: '#{settings.shared_pages.page_header.network_charts.nethash_chart.crosshair_color}', + width: 1 + }, + sync: { + enabled: #{settings.shared_pages.page_header.network_charts.sync_charts}, + group: 1, + suppressTooltips: false, + axis: 'x' + }, + zoom: { + enabled: false + } + }, + annotation: populateBlockIndexAnnotations(timeData.hourData, !{JSON.stringify(settings.shared_pages.page_header.network_charts.nethash_chart.block_line)}) + }; if (nethashChart == null) { nethashChart = new Chart(ctxNethash, { type: 'line', data: { - labels: result.map(function(a) {return a.blockindex;}), - datasets: [ - { - label: 'Hashrate', - data: result.map(function(a) {return a.nethash;}), - backgroundColor: ['#{settings.shared_pages.page_header.network_charts.nethash_chart.fill_color}'], - borderColor: ['#{settings.shared_pages.page_header.network_charts.nethash_chart.line_color}'], - fill: 'start' - } - ] + labels: (maxSavedRecords == 0 ? timeData.hourData.map(function(a) {return a.tick;}) : result.map(function(a) {return a.blockindex;})), + datasets: netHashDataSet }, options: { maintainAspectRatio: false, @@ -568,79 +873,50 @@ html(lang='en') y: { title: { display: true, - text: 'Network ' + getNetHashUnits(), + text: 'Hashrate ' + getNetHashUnits(), font: { weight: 'bold' } } } }, - plugins: { - legend: { - display: true, - position: 'bottom' - }, - title: { - display: false - }, - tooltip: { - mode: 'index', - intersect: false, - displayColors: true, - callbacks: { - title: function(context) { - return 'Block ' + context[0].label + ' Hashrate'; - }, - label: function(context) { - const val = #{settings.shared_pages.page_header.network_charts.nethash_chart.round_decimals}; - let max = 20; - - if (val != -1) - max = val; - else { - const decimalCount = countDecimals(context.raw || 0); - - if (decimalCount < max) - max = decimalCount; - } - - return Number((context.raw || 0)).toLocaleString('en',{'minimumFractionDigits':0,'maximumFractionDigits':max,'useGrouping':true}) + ' ' + getNetHashUnits(); - } - } - }, - crosshair: { - line: { - color: '#{settings.shared_pages.page_header.network_charts.nethash_chart.crosshair_color}', - width: 1 - }, - sync: { - enabled: false - }, - zoom: { - enabled: false - } - } - } + plugins: pluginData } }); + + if (maxSavedRecords == 0) + nethashChart.options.scales.x = timeData.xScale; + $('#nethashChartParent').fadeIn(); } else { - nethashChart.data.labels = result.map(function(a) {return a.blockindex;}); - nethashChart.data.datasets[0].data = result.map(function(a) {return a.nethash;}); - nethashChart.update(); + nethashChart.data.labels = (maxSavedRecords == 0 ? timeData.hourData.map(function(a) {return a.tick;}) : result.map(function(a) {return a.blockindex;})); + nethashChart.data.datasets = netHashDataSet; + nethashChart.options.plugins = pluginData; + + if (maxSavedRecords == 0) { + nethashChart.options.scales.x.min = timeData.minTimestamp; + nethashChart.options.scales.x.max = timeData.maxTimestamp; + } + + nethashChart.update('none'); } } - if settings.network_history.enabled == true && showDifficultyChart == true && settings.shared_pages.page_header.network_charts.difficulty_chart.enabled == true + if showDifficultyChart == true script. let difficultyChart; function populateDifficultyChart(result) { const ctxDifficulty = document.getElementById('difficultyChart').getContext('2d'); - + let timeData = {}; let diffDataSets = []; let showPOW = false; let showPOS = false; + if (maxSavedRecords == 0) + timeData = populateHourChartData(result); + + const extraData = populatePluginDataTitle((maxSavedRecords == 0 ? timeData.hourData : result), !{JSON.stringify(settings.shared_pages.page_header.network_charts.difficulty_chart.chart_title)}, #{settings.shared_pages.page_header.network_charts.difficulty_chart.round_decimals}, false, true); + for (let i = 0; i < result.length; i++) { if (result[i].difficulty_pow != 0) showPOW = true; @@ -651,7 +927,14 @@ html(lang='en') if (showPOS) { diffDataSets.push({ label: 'POS Difficulty', - data: result.map(function(a) {return a.difficulty_pos;}), + data: (maxSavedRecords == 0 ? + timeData.hourData.map(a => ({ + x: a.tick, + y: a.difficulty_pos, + blockindex: a.blockindex + })) : + result.map(function(a) {return a.difficulty_pos;}) + ), backgroundColor: ['#{settings.shared_pages.page_header.network_charts.difficulty_chart.pos_fill_color}'], borderColor: ['#{settings.shared_pages.page_header.network_charts.difficulty_chart.pos_line_color}'], fill: 'start' @@ -661,18 +944,84 @@ html(lang='en') if (showPOW || !showPOS) { diffDataSets.push({ label: 'POW Difficulty', - data: result.map(function(a) {return a.difficulty_pow;}), + data: (maxSavedRecords == 0 ? + timeData.hourData.map(a => ({ + x: a.tick, + y: a.difficulty_pow, + blockindex: a.blockindex + })) : + result.map(function(a) {return a.difficulty_pow;}) + ), backgroundColor: ['#{settings.shared_pages.page_header.network_charts.difficulty_chart.pow_fill_color}'], borderColor: ['#{settings.shared_pages.page_header.network_charts.difficulty_chart.pow_line_color}'], fill: 'start' }); } + const pluginData = { + title: extraData.pluginTitle, + legend: { + display: #{settings.shared_pages.page_header.network_charts.difficulty_chart.legend.enabled}, + position: '#{settings.shared_pages.page_header.network_charts.difficulty_chart.legend.position}' + }, + tooltip: { + mode: 'index', + intersect: false, + displayColors: true, + callbacks: { + title: function(context) { + if (maxSavedRecords == 0) + return format_unixtime(Math.floor(context[0].parsed.x / 1000)); + else + return 'Block ' + context[0].label + ' Difficulty'; + }, + label: function(context) { + if (maxSavedRecords == 0) + return formatNetworkChartValue(context.raw.y, #{settings.shared_pages.page_header.network_charts.difficulty_chart.round_decimals}, context.dataset.label, false); + else + return formatNetworkChartValue(context.raw, #{settings.shared_pages.page_header.network_charts.difficulty_chart.round_decimals}, context.dataset.label, false); + }, + afterLabel: function(context) { + if (maxSavedRecords == 0) { + const tooltipItems = context.chart.tooltip.dataPoints; + const isLastVisibleItem = tooltipItems[tooltipItems.length - 1] === context; + + // only display blockindex if this is the last visible tooltip item + if (isLastVisibleItem) { + const blockindex = context.raw.blockindex; + + if (blockindex && blockindex !== 0) + return `Block: ${blockindex}`; + } + } + + return ''; + } + } + }, + crosshair: { + line: { + color: '#{settings.shared_pages.page_header.network_charts.difficulty_chart.crosshair_color}', + width: 1 + }, + sync: { + enabled: #{settings.shared_pages.page_header.network_charts.sync_charts}, + group: 1, + suppressTooltips: false, + axis: 'x' + }, + zoom: { + enabled: false + } + }, + annotation: populateBlockIndexAnnotations(timeData.hourData, !{JSON.stringify(settings.shared_pages.page_header.network_charts.difficulty_chart.block_line)}) + }; + if (difficultyChart == null) { difficultyChart = new Chart(ctxDifficulty, { type: 'line', data: { - labels: result.map(function(a) {return a.blockindex;}), + labels: (maxSavedRecords == 0 ? timeData.hourData.map(function(a) {return a.tick;}) : result.map(function(a) {return a.blockindex;})), datasets: diffDataSets }, options: { @@ -696,82 +1045,28 @@ html(lang='en') } } }, - plugins: { - legend: { - display: true, - position: 'bottom' - }, - title: { - display: false - }, - tooltip: { - mode: 'index', - intersect: false, - displayColors: true, - callbacks: { - title: function(context) { - return 'Block ' + context[0].label + ' Difficulty'; - }, - label: function(context) { - const val = #{settings.shared_pages.page_header.network_charts.difficulty_chart.round_decimals}; - let max = 20; - - if (val != -1) - max = val; - else { - const decimalCount = countDecimals(context.raw || 0); - - if (decimalCount < max) - max = decimalCount; - } - - return Number((context.raw || 0)).toLocaleString('en',{'minimumFractionDigits':0,'maximumFractionDigits':max,'useGrouping':true}); - } - } - }, - crosshair: { - line: { - color: '#{settings.shared_pages.page_header.network_charts.difficulty_chart.crosshair_color}', - width: 1 - }, - sync: { - enabled: false - }, - zoom: { - enabled: false - } - } - } + plugins: pluginData } }); + + if (maxSavedRecords == 0) + difficultyChart.options.scales.x = timeData.xScale; + $('#difficultyChartParent').fadeIn(); } else { - difficultyChart.data.labels = result.map(function(a) {return a.blockindex;}); + difficultyChart.data.labels = (maxSavedRecords == 0 ? timeData.hourData.map(function(a) {return a.tick;}) : result.map(function(a) {return a.blockindex;})); + difficultyChart.data.datasets = diffDataSets; + difficultyChart.options.plugins = pluginData; - if (difficultyChart.data.datasets.length != diffDataSets.length) - difficultyChart.data.datasets = diffDataSets; - else if ( - ( - difficultyChart.data.datasets[0].label.indexOf('POW') > -1 && - diffDataSets[0].label.indexOf('POW') > -1 - ) - || - ( - difficultyChart.data.datasets[0].label.indexOf('POS') > -1 && - diffDataSets[0].label.indexOf('POS') > -1 - ) - ) { - difficultyChart.data.datasets[0].data = diffDataSets[0].data; + if (maxSavedRecords == 0) { + difficultyChart.options.scales.x.min = timeData.minTimestamp; + difficultyChart.options.scales.x.max = timeData.maxTimestamp; + } - if (difficultyChart.data.datasets.length == 2) - difficultyChart.data.datasets[1].data = diffDataSets[1].data; - } else - difficultyChart.data.datasets = diffDataSets; - - difficultyChart.update(); + difficultyChart.update('none'); } } - if (showPanels == true && showNetworkPanel == true) || (settings.network_history.enabled == true && showNethashChart == true && settings.shared_pages.page_header.network_charts.nethash_chart.enabled == true && settings.shared_pages.show_hashrate == true) + if (showPanels == true && showNetworkPanel == true) || showNethashChart == true || showDifficultyChart == true script. function getNetHashUnits() { let networkSuffix = ''; @@ -1409,26 +1704,22 @@ html(lang='en') #index-search.form-group.d-flex.justify-content-center input.form-control(type='text', name='search', placeholder=settings.localization.ex_search_message, style='min-width:80%;margin-right:5px;') button.btn.btn-success(type='submit') #{settings.localization.ex_search_button} - if settings.network_history.enabled == true && ((showNethashChart == true && settings.shared_pages.page_header.network_charts.nethash_chart.enabled == true && settings.shared_pages.show_hashrate == true) || (showDifficultyChart == true && settings.shared_pages.page_header.network_charts.difficulty_chart.enabled == true)) - .container + if showNethashChart == true || showDifficultyChart == true + - var chartColumnClass = 'col-lg-12'; + .container(style='padding-left:0;padding-right:0;') .row.align-items-start - - var chartColumnClass = 'col-lg-12'; - if showNethashChart == true && settings.shared_pages.page_header.network_charts.nethash_chart.enabled == true && settings.shared_pages.show_hashrate == true && showDifficultyChart == true && settings.shared_pages.page_header.network_charts.difficulty_chart.enabled == true + if showNethashChart == true && showDifficultyChart == true && settings.shared_pages.page_header.network_charts.nethash_chart.full_row == false && settings.shared_pages.page_header.network_charts.difficulty_chart.full_row == false - chartColumnClass = 'col-lg-6'; - if showNethashChart == true && settings.shared_pages.page_header.network_charts.nethash_chart.enabled == true && settings.shared_pages.show_hashrate == true - div#nethashChartParent(class=chartColumnClass, style='display:none;margin:10px 0;') - .card.card-default.border-0 - .card-header - strong Network Hashrate - .card-body - canvas#nethashChart(style='max-height:300px;background-color:'+settings.shared_pages.page_header.network_charts.nethash_chart.bgcolor+';') - if showDifficultyChart == true && settings.shared_pages.page_header.network_charts.difficulty_chart.enabled == true - div#difficultyChartParent(class=chartColumnClass, style='display:none;margin:10px 0;') - .card.card-default.border-0 - .card-header - strong Network Difficulty - .card-body - canvas#difficultyChart(style='max-height:300px;background-color:'+settings.shared_pages.page_header.network_charts.difficulty_chart.bgcolor+';') + include ./includes/nethash_chart.pug + include ./includes/difficulty_chart.pug + else if showNethashChart == true + include ./includes/nethash_chart.pug + else if showDifficultyChart == true + include ./includes/difficulty_chart.pug + if showNethashChart == true && showDifficultyChart == true && (settings.shared_pages.page_header.network_charts.nethash_chart.full_row == true || settings.shared_pages.page_header.network_charts.difficulty_chart.full_row == true) + .container(style='padding-left:0;padding-right:0;') + .row.align-items-start + include ./includes/difficulty_chart.pug block content div#footer-container(class=footerClasses, role='navigation') .col-4.navbar-nav diff --git a/views/market.pug b/views/market.pug index 22b3c2b..bd57e6c 100644 --- a/views/market.pug +++ b/views/market.pug @@ -1,8 +1,6 @@ extends layout block content - include ./includes/common.pug - script(type='text/javascript', src='https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.3.1') script. $(document).ready(function() { if ('#{settings.markets_page.page_header.show_last_updated}' == 'true') { diff --git a/views/masternodes.pug b/views/masternodes.pug index 4a4607d..a889bff 100644 --- a/views/masternodes.pug +++ b/views/masternodes.pug @@ -1,7 +1,6 @@ extends layout block content - include ./includes/common.pug script. var setting_txPerPage = parseInt("#{settings.masternodes_page.masternode_table.items_per_page}"); var lengthMenuOpts = !{JSON.stringify(settings.masternodes_page.masternode_table.page_length_options)}; diff --git a/views/movement.pug b/views/movement.pug index 37d2bce..763caa1 100644 --- a/views/movement.pug +++ b/views/movement.pug @@ -1,7 +1,6 @@ extends layout block content - include ./includes/common.pug script. var setting_maxTxCount = parseInt("#{settings.api_page.public_apis.ext.getlasttxs.max_items_per_query}"); var setting_txPerPage = parseInt("#{settings.movement_page.movement_table.items_per_page}"); diff --git a/views/network.pug b/views/network.pug index 167f7a7..a098729 100644 --- a/views/network.pug +++ b/views/network.pug @@ -1,7 +1,6 @@ extends layout block content - include ./includes/common.pug script. function generateLengthMenu(setting_txPerPage, lengthMenuOpts) { var addedLength = false; diff --git a/views/orphans.pug b/views/orphans.pug index b2643a0..763c93d 100644 --- a/views/orphans.pug +++ b/views/orphans.pug @@ -1,7 +1,6 @@ extends layout block content - include ./includes/common.pug script. var setting_txPerPage = parseInt("#{settings.orphans_page.orphans_table.items_per_page}"); var lengthMenuOpts = !{JSON.stringify(settings.orphans_page.orphans_table.page_length_options)}; diff --git a/views/reward.pug b/views/reward.pug index 67b2083..b11b0d0 100644 --- a/views/reward.pug +++ b/views/reward.pug @@ -1,7 +1,6 @@ extends layout block content - include ./includes/common.pug script. $(document).ready(function() { $('.summary-table').dataTable({ diff --git a/views/richlist.pug b/views/richlist.pug index 91fd11c..ec99114 100644 --- a/views/richlist.pug +++ b/views/richlist.pug @@ -1,7 +1,6 @@ extends layout block content - include ./includes/common.pug script. $(document).ready(function() { if ('#{settings.richlist_page.page_header.show_last_updated}' == 'true') { diff --git a/views/tx.pug b/views/tx.pug index dc24476..17eb08d 100644 --- a/views/tx.pug +++ b/views/tx.pug @@ -1,7 +1,6 @@ extends layout block content - include ./includes/common.pug - var theadClasses = []; if settings.shared_pages.table_header_bgcolor != null && settings.shared_pages.table_header_bgcolor != '' - theadClasses.push('table-' + settings.shared_pages.table_header_bgcolor);