diff --git a/handlers/common.go b/handlers/common.go
index a6cac68097..9a167b4866 100644
--- a/handlers/common.go
+++ b/handlers/common.go
@@ -15,6 +15,7 @@ import (
"math"
"math/big"
"net/http"
+ "sort"
"strconv"
"strings"
"syscall"
@@ -727,3 +728,60 @@ func GetWithdrawableCountFromCursor(epoch uint64, validatorindex uint64, cursor
return 0, nil
}
}
+
+func getExecutionChartData(indices []uint64, currency string, lowerBoundDay uint64) ([]*types.ChartDataPoint, error) {
+ var limit uint64 = 300
+ blockList, consMap, err := findExecBlockNumbersByProposerIndex(indices, 0, limit, false, true, lowerBoundDay)
+ if err != nil {
+ return nil, err
+ }
+
+ blocks, err := db.BigtableClient.GetBlocksIndexedMultiple(blockList, limit)
+ if err != nil {
+ return nil, err
+ }
+ relaysData, err := db.GetRelayDataForIndexedBlocks(blocks)
+ if err != nil {
+ return nil, err
+ }
+
+ var chartData = []*types.ChartDataPoint{}
+ epochsPerDay := utils.EpochsPerDay()
+ color := "#90ed7d"
+
+ // Map to keep track of the cumulative reward for each day
+ dayRewardMap := make(map[int64]float64)
+
+ for _, block := range blocks {
+ consData := consMap[block.Number]
+ day := int64(consData.Epoch / epochsPerDay)
+
+ var totalReward float64
+ if relayData, ok := relaysData[common.BytesToHash(block.Hash)]; ok {
+ totalReward = utils.WeiToEther(relayData.MevBribe.BigInt()).InexactFloat64()
+ } else {
+ totalReward = utils.WeiToEther(utils.Eth1TotalReward(block)).InexactFloat64()
+ }
+
+ // Add the reward to the existing reward for the day or set it if not previously set
+ dayRewardMap[day] += totalReward
+ }
+
+ // Now populate the chartData array using the dayRewardMap
+ exchangeRate := utils.ExchangeRateForCurrency(currency)
+ for day, reward := range dayRewardMap {
+ ts := float64(utils.DayToTime(day).Unix() * 1000)
+ chartData = append(chartData, &types.ChartDataPoint{
+ X: ts,
+ Y: exchangeRate * reward,
+ Color: color,
+ })
+ }
+
+ // If needed, sort chartData based on X values
+ sort.Slice(chartData, func(i, j int) bool {
+ return chartData[i].X < chartData[j].X
+ })
+
+ return chartData, nil
+}
diff --git a/handlers/dashboard.go b/handlers/dashboard.go
index 0244232eea..046190cef5 100644
--- a/handlers/dashboard.go
+++ b/handlers/dashboard.go
@@ -509,57 +509,6 @@ func DashboardDataBalanceCombined(w http.ResponseWriter, r *http.Request) {
}
}
-func getExecutionChartData(indices []uint64, currency string, lowerBoundDay uint64) ([]*types.ChartDataPoint, error) {
- var limit uint64 = 300
- blockList, consMap, err := findExecBlockNumbersByProposerIndex(indices, 0, limit, false, true, lowerBoundDay)
- if err != nil {
- return nil, err
- }
-
- blocks, err := db.BigtableClient.GetBlocksIndexedMultiple(blockList, limit)
- if err != nil {
- return nil, err
- }
- relaysData, err := db.GetRelayDataForIndexedBlocks(blocks)
- if err != nil {
- return nil, err
- }
-
- var chartData = make([]*types.ChartDataPoint, len(blocks))
- epochsPerDay := utils.EpochsPerDay()
- lastFinalizedEpoch := services.LatestFinalizedEpoch()
- color := "#90ed7d"
-
- for i := len(blocks) - 1; i >= 0; i-- {
- blockEpoch := utils.TimeToEpoch(blocks[i].Time.AsTime())
- consData := consMap[blocks[i].Number]
- day := int64(consData.Epoch / epochsPerDay)
- ts := float64(utils.DayToTime(day).Unix() * 1000)
- if blockEpoch > int64(lastFinalizedEpoch) {
- // we need to fill also the first items in the list, otherwise the charts break
- chartData[len(blocks)-1-i] = &types.ChartDataPoint{
- X: ts,
- Y: 0,
- Color: color,
- }
- continue
- }
- var totalReward float64
- if relayData, ok := relaysData[common.BytesToHash(blocks[i].Hash)]; ok {
- totalReward = utils.WeiToEther(relayData.MevBribe.BigInt()).InexactFloat64()
- } else {
- totalReward = utils.WeiToEther(utils.Eth1TotalReward(blocks[i])).InexactFloat64()
- }
-
- chartData[len(blocks)-1-i] = &types.ChartDataPoint{
- X: ts,
- Y: utils.ExchangeRateForCurrency(currency) * totalReward,
- Color: color,
- }
- }
- return chartData, nil
-}
-
// DashboardDataBalance retrieves the income history of a set of validators
func DashboardDataBalance(w http.ResponseWriter, r *http.Request) {
currency := GetCurrency(r)
diff --git a/static/js/dashboard.js b/static/js/dashboard.js
index c9e681da46..1600caba0c 100644
--- a/static/js/dashboard.js
+++ b/static/js/dashboard.js
@@ -1381,136 +1381,8 @@ $(document).ready(function () {
function createIncomeChart(income, executionIncomeHistory) {
executionIncomeHistory = executionIncomeHistory || []
- incomeChart = Highcharts.stockChart("balance-chart", {
- colors: ["#90ed7d", "#7cb5ec"],
- exporting: {
- scale: 1,
- },
- rangeSelector: {
- enabled: false,
- },
- chart: {
- type: "column",
- height: "627px",
- pointInterval: 24 * 3600 * 1000,
- events: {
- load: function () {
- $("#load-income-btn").removeClass("d-none")
- },
- },
- },
- credits: {
- enabled: false,
- },
- legend: {
- enabled: true,
- },
- title: {
- text: "Daily Income for all Validators",
- },
- navigator: {
- series: {
- data: income,
- color: "#7cb5ec",
- },
- },
- plotOptions: {
- column: {
- stacking: "stacked",
- dataLabels: {
- enabled: false,
- },
- pointInterval: 24 * 3600 * 1000,
- },
- series: {
- turboThreshold: 10000,
- },
- },
- xAxis: {
- type: "datetime",
- range: 31 * 24 * 60 * 60 * 1000,
- labels: {
- formatter: function () {
- var epoch = timeToEpoch(this.value)
- var orig = this.axis.defaultLabelFormatter.call(this)
- return `${orig}
Epoch ${epoch}`
- },
- },
- },
- tooltip: {
- split: false,
- shared: true,
- formatter: (tooltip) => {
- var text = ``
- var total = 0
-
- // date and epochs
- const startEpoch = timeToEpoch(tooltip.chart.hoverPoint.x)
- const timeForOneDay = 24 * 60 * 60 * 1000
- const endEpoch = timeToEpoch(tooltip.chart.hoverPoint.x + timeForOneDay) - 1
- const startDate = luxon.DateTime.fromMillis(tooltip.chart.hoverPoints[0].x)
- const endDate = luxon.DateTime.fromMillis(epochToTime(endEpoch + 1))
- text += `${startDate.toFormat("MMM-dd-yyyy HH:mm:ss")} - ${endDate.toFormat("MMM-dd-yyyy HH:mm:ss")}
Epochs ${startEpoch} - ${endEpoch}
`
-
- // income
- for (var i = 0; i < tooltip.chart.hoverPoints.length; i++) {
- const value = tooltip.chart.hoverPoints[i].y
- text += `\u25CF ${tooltip.chart.hoverPoints[i].series.name}: ${getIncomeChartValueString(value, currency, 1)}
`
- total += value
- }
-
- // add total if hovered point contains rewards for both EL and CL
- if (tooltip.chart.hoverPoints.length > 1) {
- text += `Total: ${getIncomeChartValueString(total, currency, 1)}`
- }
-
- return text
- },
- },
- yAxis: [
- {
- title: {
- text: "Income [" + currency + "]",
- },
- opposite: false,
- labels: {
- formatter: function () {
- if (currency !== "ETH") {
- return this.value.toFixed(2)
- }
- return this.value.toFixed(5)
- },
- },
- },
- ],
- series: [
- {
- name: "Daily Execution Income",
- data: executionIncomeHistory,
- },
- {
- name: "Daily Consensus Income",
- data: income,
- },
- ],
- responsive: {
- rules: [
- {
- condition: {
- callback: function () {
- return window.innerWidth >= 820
- },
- },
- chartOptions: {
- legend: {
- itemMarginTop: 7,
- itemMarginBottom: -7,
- },
- },
- },
- ],
- },
- })
+ const incomeChartOptions = getIncomeChartOptions(income, executionIncomeHistory, "Daily Income for all Validators", 627, currency)
+ incomeChart = Highcharts.stockChart("balance-chart", incomeChartOptions)
}
function createProposedChart(data) {
diff --git a/static/js/income_chart_options.js b/static/js/income_chart_options.js
new file mode 100644
index 0000000000..f12c2e7151
--- /dev/null
+++ b/static/js/income_chart_options.js
@@ -0,0 +1,132 @@
+function getIncomeChartOptions(incomeHistory, executionIncomeHistory, title, height, currency) {
+ return {
+ colors: ["#90ed7d", "#7cb5ec"],
+ exporting: {
+ scale: 1,
+ },
+ rangeSelector: {
+ enabled: false,
+ },
+ chart: {
+ type: "column",
+ height: `${height}px"`,
+ pointInterval: 24 * 3600 * 1000,
+ events: {
+ load: function () {
+ $("#load-income-btn").removeClass("d-none")
+ },
+ },
+ },
+ title: {
+ text: title,
+ },
+ credits: {
+ enabled: false,
+ },
+ legend: {
+ enabled: true,
+ },
+ plotOptions: {
+ column: {
+ stacking: "stacked",
+ dataLabels: {
+ enabled: false,
+ },
+ pointInterval: 24 * 3600 * 1000,
+ },
+ series: {
+ turboThreshold: 10000,
+ },
+ },
+ xAxis: {
+ type: "datetime",
+ range: 31 * 24 * 60 * 60 * 1000,
+ labels: {
+ formatter: function () {
+ var epoch = timeToEpoch(this.value)
+ var orig = this.axis.defaultLabelFormatter.call(this)
+ return `${orig}
Epoch ${epoch}`
+ },
+ },
+ },
+ yAxis: [
+ {
+ title: {
+ text: `Income [${currency}]`,
+ },
+ opposite: false,
+ labels: {
+ formatter: function () {
+ return currency === "ETH" ? trimToken(this.value) : trimCurrency(this.value)
+ },
+ },
+ },
+ ],
+ series: [
+ {
+ name: "Execution Income",
+ data: executionIncomeHistory,
+ showInNavigator: false,
+ dataGrouping: {
+ enabled: false,
+ },
+ },
+ {
+ name: "Consensus Income",
+ data: incomeHistory,
+ showInNavigator: true,
+ dataGrouping: {
+ enabled: false,
+ },
+ },
+ ],
+ tooltip: {
+ split: false,
+ shared: true,
+ formatter: (tooltip) => {
+ var text = ``
+ var total = 0
+
+ // time range for hovered point
+ const ts = tooltip.chart.hoverPoints[0].x
+ const startEpoch = timeToEpoch(ts)
+ const timeForOneDay = 24 * 60 * 60 * 1000
+ const endEpoch = timeToEpoch(ts + timeForOneDay) - 1
+ const startDate = luxon.DateTime.fromMillis(ts)
+ const endDate = luxon.DateTime.fromMillis(epochToTime(endEpoch + 1))
+ text += `${startDate.toFormat("MMM-dd-yyyy HH:mm:ss")} - ${endDate.toFormat("MMM-dd-yyyy HH:mm:ss")}
Epochs ${startEpoch} - ${endEpoch}
`
+
+ // income
+ for (var i = 0; i < tooltip.chart.hoverPoints.length; i++) {
+ const value = tooltip.chart.hoverPoints[i].y
+ text += `\u25CF ${tooltip.chart.hoverPoints[i].series.name}: ${getIncomeChartValueString(value, currency)}
`
+ total += value
+ }
+
+ // add total if hovered point contains rewards for both EL and CL
+ if (tooltip.chart.hoverPoints.length > 1) {
+ text += `Total: ${getIncomeChartValueString(total, currency)}`
+ }
+
+ return text
+ },
+ },
+ responsive: {
+ rules: [
+ {
+ condition: {
+ callback: function () {
+ return window.innerWidth >= 820
+ },
+ },
+ chartOptions: {
+ legend: {
+ itemMarginTop: 7,
+ itemMarginBottom: -7,
+ },
+ },
+ },
+ ],
+ },
+ }
+}
diff --git a/static/js/layout.js b/static/js/layout.js
index 8390ecf1cc..068bb978df 100644
--- a/static/js/layout.js
+++ b/static/js/layout.js
@@ -817,12 +817,12 @@ function trimCurrency(value) {
return trimPrice(value, 2)
}
-function getIncomeChartValueString(value, currency, ethPrice) {
+function getIncomeChartValueString(value, currency) {
if (this.currency === "ETH") {
return `${trimToken(value)} ETH`
}
- return `${trimToken(value / ethPrice)} ETH (${trimCurrency(value)} ${currency})`
+ return `${trimCurrency(value)} ${currency}`
}
$("[data-tooltip-date=true]").each(function (item) {
diff --git a/templates/dashboard.html b/templates/dashboard.html
index 6804ae109a..f1e52424ee 100644
--- a/templates/dashboard.html
+++ b/templates/dashboard.html
@@ -7,6 +7,7 @@
+