mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Clean up gas chart code.
This commit is contained in:
parent
aa798cc545
commit
6f0406125d
@ -69,6 +69,10 @@ const mapStateToProps = state => {
|
|||||||
|
|
||||||
const customGasPrice = calcCustomGasPrice(customModalGasPriceInHex)
|
const customGasPrice = calcCustomGasPrice(customModalGasPriceInHex)
|
||||||
|
|
||||||
|
const priceAndTimeEstimates = state.gas.priceAndTimeEstimates
|
||||||
|
const gasPrices = priceAndTimeEstimates.map(({ gasprice }) => gasprice)
|
||||||
|
const estimatedTimes = priceAndTimeEstimates.map(({ expectedTime }) => expectedTime)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hideBasic,
|
hideBasic,
|
||||||
isConfirm: isConfirm(state),
|
isConfirm: isConfirm(state),
|
||||||
@ -77,7 +81,7 @@ const mapStateToProps = state => {
|
|||||||
customGasPrice,
|
customGasPrice,
|
||||||
customGasLimit: calcCustomGasLimit(customModalGasLimitInHex),
|
customGasLimit: calcCustomGasLimit(customModalGasLimitInHex),
|
||||||
newTotalFiat,
|
newTotalFiat,
|
||||||
currentTimeEstimate: getRenderableTimeEstimate(customGasPrice, state.gas.priceAndTimeEstimates),
|
currentTimeEstimate: getRenderableTimeEstimate(customGasPrice, priceAndTimeEstimates),
|
||||||
gasPriceButtonGroupProps: {
|
gasPriceButtonGroupProps: {
|
||||||
buttonDataLoading,
|
buttonDataLoading,
|
||||||
defaultActiveButtonIndex: getDefaultActiveButtonIndex(gasButtonInfo, customModalGasPriceInHex),
|
defaultActiveButtonIndex: getDefaultActiveButtonIndex(gasButtonInfo, customModalGasPriceInHex),
|
||||||
@ -85,7 +89,10 @@ const mapStateToProps = state => {
|
|||||||
},
|
},
|
||||||
gasChartProps: {
|
gasChartProps: {
|
||||||
currentPrice: customGasPrice,
|
currentPrice: customGasPrice,
|
||||||
priceAndTimeEstimates: state.gas.priceAndTimeEstimates,
|
gasPrices,
|
||||||
|
estimatedTimes,
|
||||||
|
gasPricesMax: gasPrices[gasPrices.length - 1] + 1,
|
||||||
|
estimatedTimesMax: estimatedTimes[0],
|
||||||
},
|
},
|
||||||
infoRowProps: {
|
infoRowProps: {
|
||||||
originalTotalFiat: addHexWEIsToRenderableFiat(value, gasTotal, currentCurrency, conversionRate),
|
originalTotalFiat: addHexWEIsToRenderableFiat(value, gasTotal, currentCurrency, conversionRate),
|
||||||
|
@ -2,75 +2,18 @@ import React, { Component } from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import * as d3 from 'd3'
|
import * as d3 from 'd3'
|
||||||
import c3 from 'c3'
|
import c3 from 'c3'
|
||||||
|
import {
|
||||||
function setTickPosition (axis, n, newPosition, secondNewPosition) {
|
appendOrUpdateCircle,
|
||||||
const positionToShift = axis === 'y' ? 'x' : 'y'
|
generateChart,
|
||||||
const secondPositionToShift = axis === 'y' ? 'y' : 'x'
|
generateDataUIObj,
|
||||||
d3.select('#chart')
|
getAdjacentGasPrices,
|
||||||
.select(`.c3-axis-${axis}`)
|
getCoordinateData,
|
||||||
.selectAll('.tick')
|
getNewXandTimeEstimate,
|
||||||
.filter((d, i) => i === n)
|
handleChartUpdate,
|
||||||
.select('text')
|
hideDataUI,
|
||||||
.attr(positionToShift, 0)
|
setSelectedCircle,
|
||||||
.select('tspan')
|
setTickPosition,
|
||||||
.attr(positionToShift, newPosition)
|
} from './gas-price-chart.utils.js'
|
||||||
.attr(secondPositionToShift, secondNewPosition || 0)
|
|
||||||
.style('visibility', 'visible')
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendOrUpdateCircle ({ circle, data, itemIndex, cx, cy, cssId, appendOnly }) {
|
|
||||||
if (appendOnly || circle.empty()) {
|
|
||||||
circle.data([data])
|
|
||||||
.enter().append('circle')
|
|
||||||
.attr('class', () => this.generateClass('c3-selected-circle', itemIndex))
|
|
||||||
.attr('id', cssId)
|
|
||||||
.attr('cx', cx)
|
|
||||||
.attr('cy', cy)
|
|
||||||
.attr('stroke', () => this.color(data))
|
|
||||||
.attr('r', 6)
|
|
||||||
} else {
|
|
||||||
circle.data([data])
|
|
||||||
.attr('cx', cx)
|
|
||||||
.attr('cy', cy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSelectedCircle ({ chart, gasPrices, currentPrice, chartXStart, chartWidth }) {
|
|
||||||
const numberOfValues = chart.internal.data.xs.data1.length
|
|
||||||
const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => {
|
|
||||||
return e <= currentPrice && a[i + 1] >= currentPrice
|
|
||||||
})
|
|
||||||
const closestHigherValueIndex = gasPrices.findIndex((e, i, a) => {
|
|
||||||
return e > currentPrice
|
|
||||||
})
|
|
||||||
const closestHigherValue = gasPrices[closestHigherValueIndex]
|
|
||||||
const closestLowerValue = gasPrices[closestLowerValueIndex]
|
|
||||||
|
|
||||||
if (closestHigherValue && closestLowerValue) {
|
|
||||||
const closestLowerCircle = d3.select(`.c3-circle-${closestLowerValueIndex}`)
|
|
||||||
const closestHigherCircle = d3.select(`.c3-circle-${closestHigherValueIndex}`)
|
|
||||||
const { x: lowerX, y: lowerY } = closestLowerCircle.node().getBoundingClientRect()
|
|
||||||
const { x: higherX, y: higherY } = closestHigherCircle.node().getBoundingClientRect()
|
|
||||||
const currentX = lowerX + (higherX - lowerX) * (currentPrice - closestLowerValue) / (closestHigherValue - closestLowerValue)
|
|
||||||
const slope = (higherY - lowerY) / (higherX - lowerX)
|
|
||||||
const newTimeEstimate = -1 * (slope * (higherX - currentX) - higherY)
|
|
||||||
chart.internal.selectPointB({
|
|
||||||
x: currentX,
|
|
||||||
value: newTimeEstimate,
|
|
||||||
id: 'data1',
|
|
||||||
index: numberOfValues,
|
|
||||||
name: 'data1',
|
|
||||||
}, numberOfValues)
|
|
||||||
} else {
|
|
||||||
const setCircle = d3.select('#set-circle')
|
|
||||||
if (!setCircle.empty()) {
|
|
||||||
setCircle.remove()
|
|
||||||
}
|
|
||||||
d3.select('.c3-tooltip-container').style('display', 'none !important')
|
|
||||||
chart.internal.hideXGridFocus()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class GasPriceChart extends Component {
|
export default class GasPriceChart extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -78,225 +21,23 @@ export default class GasPriceChart extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
priceAndTimeEstimates: PropTypes.array,
|
gasPrices: PropTypes.array,
|
||||||
|
estimatedTimes: PropTypes.array,
|
||||||
|
gasPricesMax: PropTypes.number,
|
||||||
|
estimatedTimesMax: PropTypes.number,
|
||||||
currentPrice: PropTypes.number,
|
currentPrice: PropTypes.number,
|
||||||
updateCustomGasPrice: PropTypes.func,
|
updateCustomGasPrice: PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
renderChart (currentPrice, priceAndTimeEstimates, updateCustomGasPrice) {
|
renderChart ({
|
||||||
const gasPrices = priceAndTimeEstimates.map(({ gasprice }) => gasprice)
|
currentPrice,
|
||||||
const gasPricesMax = gasPrices[gasPrices.length - 1] + 1
|
gasPrices,
|
||||||
const estimatedTimes = priceAndTimeEstimates.map(({ expectedTime }) => expectedTime)
|
estimatedTimes,
|
||||||
|
gasPricesMax,
|
||||||
const estimatedTimesMax = estimatedTimes[0]
|
estimatedTimesMax,
|
||||||
const chart = c3.generate({
|
updateCustomGasPrice,
|
||||||
size: {
|
}) {
|
||||||
height: 165,
|
const chart = generateChart(gasPrices, estimatedTimes, gasPricesMax, estimatedTimesMax)
|
||||||
},
|
|
||||||
transition: {
|
|
||||||
duration: 0,
|
|
||||||
},
|
|
||||||
padding: {left: 20, right: 15, top: 6, bottom: 10},
|
|
||||||
data: {
|
|
||||||
x: 'x',
|
|
||||||
columns: [
|
|
||||||
['x', ...gasPrices],
|
|
||||||
['data1', ...estimatedTimes],
|
|
||||||
],
|
|
||||||
types: {
|
|
||||||
data1: 'area',
|
|
||||||
},
|
|
||||||
selection: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
data1: '#259de5',
|
|
||||||
},
|
|
||||||
axis: {
|
|
||||||
x: {
|
|
||||||
min: gasPrices[0],
|
|
||||||
max: gasPricesMax,
|
|
||||||
tick: {
|
|
||||||
values: [Math.floor(gasPrices[0]), Math.ceil(gasPricesMax)],
|
|
||||||
outer: false,
|
|
||||||
format: function (val) { return val + ' GWEI' },
|
|
||||||
},
|
|
||||||
padding: {left: gasPricesMax / 50, right: gasPricesMax / 50},
|
|
||||||
label: {
|
|
||||||
text: 'Gas Price ($)',
|
|
||||||
position: 'outer-center',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
padding: {top: 7, bottom: 7},
|
|
||||||
tick: {
|
|
||||||
values: [Math.floor(estimatedTimesMax * 0.05), Math.ceil(estimatedTimesMax * 0.97)],
|
|
||||||
outer: false,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
text: 'Confirmation time (sec)',
|
|
||||||
position: 'outer-middle',
|
|
||||||
},
|
|
||||||
min: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
x: {},
|
|
||||||
lines: {
|
|
||||||
front: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
point: {
|
|
||||||
focus: {
|
|
||||||
expand: {
|
|
||||||
enabled: false,
|
|
||||||
r: 3.5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
format: {
|
|
||||||
title: (v) => v.toPrecision(4),
|
|
||||||
},
|
|
||||||
contents: function (d, defaultTitleFormat, defaultValueFormat, color) {
|
|
||||||
const config = this.config
|
|
||||||
const titleFormat = config.tooltip_format_title || defaultTitleFormat
|
|
||||||
let text
|
|
||||||
let title
|
|
||||||
d.forEach(el => {
|
|
||||||
if (el && (el.value || el.value === 0) && !text) {
|
|
||||||
title = titleFormat ? titleFormat(el.x) : el.x
|
|
||||||
text = "<table class='" + 'custom-tooltip' + "'>" + (title || title === 0 ? "<tr><th colspan='2'>" + title + '</th></tr>' : '')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return text + '</table>' + "<div class='tooltip-arrow'></div>"
|
|
||||||
},
|
|
||||||
position: function (data, width, height, element) {
|
|
||||||
const overlayedCircle = d3.select('#overlayed-circle')
|
|
||||||
if (overlayedCircle.empty()) {
|
|
||||||
return { top: -100, left: -100 }
|
|
||||||
}
|
|
||||||
|
|
||||||
const { x: circleX, y: circleY, width: circleWidth } = overlayedCircle.node().getBoundingClientRect()
|
|
||||||
const { x: chartXStart, y: chartYStart } = d3.select('.c3-chart').node().getBoundingClientRect()
|
|
||||||
|
|
||||||
// TODO: Confirm the below constants work with all data sets and screen sizes
|
|
||||||
// TODO: simplify l149-l159
|
|
||||||
let y = circleY - chartYStart - 19
|
|
||||||
if (circleY - circleWidth < chartYStart + 5) {
|
|
||||||
y = y + circleWidth + 38
|
|
||||||
d3.select('.tooltip-arrow').style('margin-top', '-16px')
|
|
||||||
} else {
|
|
||||||
d3.select('.tooltip-arrow').style('margin-top', '4px')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
top: y,
|
|
||||||
left: circleX - chartXStart + circleWidth - (gasPricesMax / 50),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
show: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
chart.internal.selectPoint = function (data, itemIndex = (data.index || 0)) {
|
|
||||||
const { x: circleX, y: circleY, width: circleWidth } = d3.select('#overlayed-circle')
|
|
||||||
.node()
|
|
||||||
.getBoundingClientRect()
|
|
||||||
const { x: chartXStart, y: chartYStart } = d3.select('.c3-areas-data1')
|
|
||||||
.node()
|
|
||||||
.getBoundingClientRect()
|
|
||||||
|
|
||||||
d3.select('#set-circle').remove()
|
|
||||||
|
|
||||||
const circle = this.main
|
|
||||||
.select('.' + 'c3-selected-circles' + this.getTargetSelectorSuffix(data.id))
|
|
||||||
.selectAll('.' + 'c3-selected-circle' + '-' + itemIndex)
|
|
||||||
|
|
||||||
appendOrUpdateCircle.bind(this)({
|
|
||||||
circle,
|
|
||||||
data,
|
|
||||||
itemIndex,
|
|
||||||
cx: () => circleX - chartXStart + circleWidth + 2,
|
|
||||||
cy: () => circleY - chartYStart + circleWidth + 1,
|
|
||||||
cssId: 'set-circle',
|
|
||||||
appendOnly: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
chart.internal.selectPointB = function (data, itemIndex = (data.index || 0)) {
|
|
||||||
const { x: chartXStart, y: chartYStart } = d3.select('.c3-areas-data1')
|
|
||||||
.node()
|
|
||||||
.getBoundingClientRect()
|
|
||||||
|
|
||||||
d3.select('#set-circle').remove()
|
|
||||||
|
|
||||||
const circle = this.main
|
|
||||||
.select('.' + 'c3-selected-circles' + this.getTargetSelectorSuffix(data.id))
|
|
||||||
.selectAll('.' + 'c3-selected-circle' + '-' + itemIndex)
|
|
||||||
|
|
||||||
appendOrUpdateCircle.bind(this)({
|
|
||||||
circle,
|
|
||||||
data,
|
|
||||||
itemIndex,
|
|
||||||
cx: () => data.x - chartXStart + 11,
|
|
||||||
cy: () => data.value - chartYStart + 10,
|
|
||||||
cssId: 'set-circle',
|
|
||||||
appendOnly: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
chart.internal.overlayPoint = function (data, itemIndex) {
|
|
||||||
const circle = this.main
|
|
||||||
.select('.' + 'c3-selected-circles' + this.getTargetSelectorSuffix(data.id))
|
|
||||||
.selectAll('.' + 'c3-selected-circle' + '-' + itemIndex)
|
|
||||||
|
|
||||||
appendOrUpdateCircle.bind(this)({
|
|
||||||
circle,
|
|
||||||
data,
|
|
||||||
itemIndex,
|
|
||||||
cx: this.circleX.bind(this),
|
|
||||||
cy: this.circleY.bind(this),
|
|
||||||
cssId: 'overlayed-circle',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
chart.internal.setCurrentCircle = function (data, itemIndex) {
|
|
||||||
const circle = this.main
|
|
||||||
.select('.' + 'c3-selected-circles' + this.getTargetSelectorSuffix(data.id))
|
|
||||||
.selectAll('#current-circle')
|
|
||||||
|
|
||||||
appendOrUpdateCircle.bind(this)({
|
|
||||||
circle,
|
|
||||||
data,
|
|
||||||
itemIndex,
|
|
||||||
cx: this.circleX.bind(this),
|
|
||||||
cy: this.circleY.bind(this),
|
|
||||||
cssId: 'current-circle',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
chart.internal.showTooltip = function (selectedData, element) {
|
|
||||||
const $$ = this
|
|
||||||
const config = $$.config
|
|
||||||
const forArc = $$.hasArcType()
|
|
||||||
const dataToShow = selectedData.filter((d) => d && (d.value || d.value === 0))
|
|
||||||
const positionFunction = config.tooltip_position || chart.internal.prototype.tooltipPosition
|
|
||||||
if (dataToShow.length === 0 || !config.tooltip_show) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
$$.tooltip.html(config.tooltip_contents.call($$, selectedData, $$.axis.getXAxisTickFormat(), $$.getYFormat(forArc), $$.color)).style('display', 'flex')
|
|
||||||
|
|
||||||
// Get tooltip dimensions
|
|
||||||
const tWidth = $$.tooltip.property('offsetWidth')
|
|
||||||
const tHeight = $$.tooltip.property('offsetHeight')
|
|
||||||
const position = positionFunction.call(this, dataToShow, tWidth, tHeight, element)
|
|
||||||
// Set tooltip
|
|
||||||
$$.tooltip.style('top', position.top + 'px').style('left', position.left + 'px')
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
setTickPosition('y', 0, -5, 8)
|
setTickPosition('y', 0, -5, 8)
|
||||||
@ -310,84 +51,42 @@ export default class GasPriceChart extends Component {
|
|||||||
d3.select('.c3-xgrid-focus line').attr('y2', 98)
|
d3.select('.c3-xgrid-focus line').attr('y2', 98)
|
||||||
|
|
||||||
d3.select('.c3-chart').on('mouseout', () => {
|
d3.select('.c3-chart').on('mouseout', () => {
|
||||||
const overLayedCircle = d3.select('#overlayed-circle')
|
hideDataUI(chart, '#overlayed-circle')
|
||||||
if (!overLayedCircle.empty()) {
|
|
||||||
overLayedCircle.remove()
|
|
||||||
}
|
|
||||||
d3.select('.c3-tooltip-container').style('display', 'none !important')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const chartRect = d3.select('.c3-areas-data1')
|
|
||||||
const { x: chartXStart, width: chartWidth } = chartRect.node().getBoundingClientRect()
|
|
||||||
|
|
||||||
d3.select('.c3-chart').on('click', () => {
|
d3.select('.c3-chart').on('click', () => {
|
||||||
const overlayedCircle = d3.select('#overlayed-circle')
|
const { x: newGasPrice } = d3.select('#overlayed-circle').datum()
|
||||||
const numberOfValues = chart.internal.data.xs.data1.length
|
updateCustomGasPrice(newGasPrice)
|
||||||
const { x: circleX, y: circleY } = overlayedCircle.node().getBoundingClientRect()
|
|
||||||
const { x: xData } = overlayedCircle.datum()
|
|
||||||
chart.internal.selectPoint({
|
|
||||||
x: circleX - chartXStart,
|
|
||||||
value: circleY - 1.5,
|
|
||||||
id: 'data1',
|
|
||||||
index: numberOfValues,
|
|
||||||
name: 'data1',
|
|
||||||
}, numberOfValues)
|
|
||||||
updateCustomGasPrice(xData)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
setSelectedCircle({ chart, gasPrices, currentPrice, chartXStart, chartWidth })
|
const { x: chartXStart, width: chartWidth } = getCoordinateData('.c3-areas-data1')
|
||||||
|
|
||||||
|
handleChartUpdate ({
|
||||||
|
chart,
|
||||||
|
gasPrices,
|
||||||
|
newPrice: currentPrice,
|
||||||
|
cssId: '#set-circle',
|
||||||
|
})
|
||||||
|
|
||||||
d3.select('.c3-chart').on('mousemove', function () {
|
d3.select('.c3-chart').on('mousemove', function () {
|
||||||
const chartMouseXPos = d3.event.clientX - chartXStart
|
const { currentPosValue, newTimeEstimate } = getNewXandTimeEstimate({
|
||||||
const posPercentile = chartMouseXPos / chartWidth
|
xMousePos: d3.event.clientX,
|
||||||
|
chartXStart,
|
||||||
const currentPosValue = (gasPrices[gasPrices.length - 1] - gasPrices[0]) * posPercentile + gasPrices[0]
|
chartWidth,
|
||||||
const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => {
|
gasPrices,
|
||||||
return e <= currentPosValue && a[i + 1] >= currentPosValue
|
estimatedTimes,
|
||||||
})
|
})
|
||||||
const closestLowerValue = gasPrices[closestLowerValueIndex]
|
|
||||||
const estimatedClosestLowerTimeEstimate = estimatedTimes[closestLowerValueIndex]
|
|
||||||
|
|
||||||
const closestHigherValueIndex = gasPrices.findIndex((e, i, a) => {
|
if (currentPosValue === null && newTimeEstimate === null) {
|
||||||
return e > currentPosValue
|
hideDataUI(chart, '#overlayed-circle')
|
||||||
})
|
|
||||||
const closestHigherValue = gasPrices[closestHigherValueIndex]
|
|
||||||
if (!closestHigherValue || !closestLowerValue) {
|
|
||||||
const overLayedCircle = d3.select('#overlayed-circle')
|
|
||||||
if (!overLayedCircle.empty()) {
|
|
||||||
overLayedCircle.remove()
|
|
||||||
}
|
|
||||||
d3.select('.c3-tooltip-container').style('display', 'none !important')
|
|
||||||
chart.internal.hideXGridFocus()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
const estimatedClosestHigherTimeEstimate = estimatedTimes[closestHigherValueIndex]
|
|
||||||
|
|
||||||
const slope = (estimatedClosestHigherTimeEstimate - estimatedClosestLowerTimeEstimate) / (closestHigherValue - closestLowerValue)
|
const indexOfNewCircle = estimatedTimes.length + 1
|
||||||
const newTimeEstimate = -1 * (slope * (closestHigherValue - currentPosValue) - estimatedClosestHigherTimeEstimate)
|
const dataUIObj = generateDataUIObj(currentPosValue, indexOfNewCircle, newTimeEstimate)
|
||||||
|
|
||||||
const newEstimatedTimes = [...estimatedTimes, newTimeEstimate]
|
chart.internal.overlayPoint(dataUIObj, indexOfNewCircle)
|
||||||
chart.internal.overlayPoint({
|
chart.internal.showTooltip([dataUIObj], d3.select('.c3-areas-data1')._groups[0])
|
||||||
x: currentPosValue,
|
chart.internal.showXGridFocus([dataUIObj])
|
||||||
value: newTimeEstimate,
|
|
||||||
id: 'data1',
|
|
||||||
index: newEstimatedTimes.length,
|
|
||||||
name: 'data1',
|
|
||||||
}, newEstimatedTimes.length)
|
|
||||||
chart.internal.showTooltip([{
|
|
||||||
x: currentPosValue,
|
|
||||||
value: newTimeEstimate,
|
|
||||||
id: 'data1',
|
|
||||||
index: newEstimatedTimes.length,
|
|
||||||
name: 'data1',
|
|
||||||
}], chartRect._groups[0])
|
|
||||||
chart.internal.showXGridFocus([{
|
|
||||||
x: currentPosValue,
|
|
||||||
value: newTimeEstimate,
|
|
||||||
id: 'data1',
|
|
||||||
index: newEstimatedTimes.length,
|
|
||||||
name: 'data1',
|
|
||||||
}])
|
|
||||||
})
|
})
|
||||||
}, 0)
|
}, 0)
|
||||||
|
|
||||||
@ -395,22 +94,20 @@ export default class GasPriceChart extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.currentPrice !== this.props.currentPrice) {
|
const { gasPrices, currentPrice: newPrice } = this.props
|
||||||
const chartRect = d3.select('.c3-areas-data1')
|
|
||||||
const { x: chartXStart, width: chartWidth } = chartRect.node().getBoundingClientRect()
|
if (prevProps.currentPrice !== newPrice) {
|
||||||
setSelectedCircle({
|
handleChartUpdate ({
|
||||||
chart: this.chart,
|
chart: this.chart,
|
||||||
currentPrice: this.props.currentPrice,
|
gasPrices,
|
||||||
gasPrices: this.props.priceAndTimeEstimates.map(({ gasprice }) => gasprice),
|
newPrice,
|
||||||
chartXStart,
|
cssId: '#set-circle',
|
||||||
chartWidth,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { currentPrice, priceAndTimeEstimates, updateCustomGasPrice } = this.props
|
this.renderChart(this.props)
|
||||||
this.renderChart(currentPrice, priceAndTimeEstimates, updateCustomGasPrice)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
@ -0,0 +1,307 @@
|
|||||||
|
import * as d3 from 'd3'
|
||||||
|
import c3 from 'c3'
|
||||||
|
|
||||||
|
export function getCoordinateData (selector) {
|
||||||
|
return d3.select(selector).node().getBoundingClientRect()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateDataUIObj (x, index, value) {
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
value,
|
||||||
|
index,
|
||||||
|
id: 'data1',
|
||||||
|
name: 'data1',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleChartUpdate ({ chart, gasPrices, newPrice, cssId }) {
|
||||||
|
const {
|
||||||
|
closestLowerValueIndex,
|
||||||
|
closestLowerValue,
|
||||||
|
closestHigherValueIndex,
|
||||||
|
closestHigherValue,
|
||||||
|
} = getAdjacentGasPrices({ gasPrices, priceToPosition: newPrice })
|
||||||
|
|
||||||
|
if (closestLowerValue && closestHigherValue) {
|
||||||
|
setSelectedCircle({
|
||||||
|
chart,
|
||||||
|
newPrice,
|
||||||
|
closestLowerValueIndex,
|
||||||
|
closestLowerValue,
|
||||||
|
closestHigherValueIndex,
|
||||||
|
closestHigherValue,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
hideDataUI(chart, cssId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAdjacentGasPrices({ gasPrices, priceToPosition }) {
|
||||||
|
const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => e <= priceToPosition && a[i + 1] >= priceToPosition)
|
||||||
|
const closestHigherValueIndex = gasPrices.findIndex((e, i, a) => e > priceToPosition)
|
||||||
|
return {
|
||||||
|
closestLowerValueIndex,
|
||||||
|
closestHigherValueIndex,
|
||||||
|
closestHigherValue: gasPrices[closestHigherValueIndex],
|
||||||
|
closestLowerValue: gasPrices[closestLowerValueIndex],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function extrapolateY ({ higherY, lowerY, higherX, lowerX, xForExtrapolation }) {
|
||||||
|
const slope = (higherY - lowerY) / (higherX - lowerX)
|
||||||
|
const newTimeEstimate = -1 * (slope * (higherX - xForExtrapolation) - higherY)
|
||||||
|
|
||||||
|
return newTimeEstimate
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function getNewXandTimeEstimate ({ xMousePos, chartXStart, chartWidth, gasPrices, estimatedTimes }) {
|
||||||
|
const chartMouseXPos = xMousePos - chartXStart
|
||||||
|
const posPercentile = chartMouseXPos / chartWidth
|
||||||
|
|
||||||
|
const currentPosValue = (gasPrices[gasPrices.length - 1] - gasPrices[0]) * posPercentile + gasPrices[0]
|
||||||
|
|
||||||
|
const {
|
||||||
|
closestLowerValueIndex,
|
||||||
|
closestLowerValue,
|
||||||
|
closestHigherValueIndex,
|
||||||
|
closestHigherValue,
|
||||||
|
} = getAdjacentGasPrices({ gasPrices, priceToPosition: currentPosValue })
|
||||||
|
|
||||||
|
return !closestHigherValue || !closestLowerValue
|
||||||
|
? {
|
||||||
|
currentPosValue: null,
|
||||||
|
newTimeEstimate: null,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
currentPosValue,
|
||||||
|
newTimeEstimate: extrapolateY ({
|
||||||
|
higherY: estimatedTimes[closestHigherValueIndex],
|
||||||
|
lowerY: estimatedTimes[closestLowerValueIndex],
|
||||||
|
higherX: closestHigherValue,
|
||||||
|
lowerX: closestLowerValue,
|
||||||
|
xForExtrapolation: currentPosValue,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideDataUI (chart, dataNodeId) {
|
||||||
|
const overLayedCircle = d3.select(dataNodeId)
|
||||||
|
if (!overLayedCircle.empty()) {
|
||||||
|
overLayedCircle.remove()
|
||||||
|
}
|
||||||
|
d3.select('.c3-tooltip-container').style('display', 'none !important')
|
||||||
|
chart.internal.hideXGridFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTickPosition (axis, n, newPosition, secondNewPosition) {
|
||||||
|
const positionToShift = axis === 'y' ? 'x' : 'y'
|
||||||
|
const secondPositionToShift = axis === 'y' ? 'y' : 'x'
|
||||||
|
d3.select('#chart')
|
||||||
|
.select(`.c3-axis-${axis}`)
|
||||||
|
.selectAll('.tick')
|
||||||
|
.filter((d, i) => i === n)
|
||||||
|
.select('text')
|
||||||
|
.attr(positionToShift, 0)
|
||||||
|
.select('tspan')
|
||||||
|
.attr(positionToShift, newPosition)
|
||||||
|
.attr(secondPositionToShift, secondNewPosition || 0)
|
||||||
|
.style('visibility', 'visible')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function appendOrUpdateCircle ({ data, itemIndex, cx, cy, cssId, appendOnly }) {
|
||||||
|
const circle = this.main
|
||||||
|
.select('.' + 'c3-selected-circles' + this.getTargetSelectorSuffix(data.id))
|
||||||
|
.selectAll('.' + 'c3-selected-circle' + '-' + itemIndex)
|
||||||
|
|
||||||
|
if (appendOnly || circle.empty()) {
|
||||||
|
circle.data([data])
|
||||||
|
.enter().append('circle')
|
||||||
|
.attr('class', () => this.generateClass('c3-selected-circle', itemIndex))
|
||||||
|
.attr('id', cssId)
|
||||||
|
.attr('cx', cx)
|
||||||
|
.attr('cy', cy)
|
||||||
|
.attr('stroke', () => this.color(data))
|
||||||
|
.attr('r', 6)
|
||||||
|
} else {
|
||||||
|
circle.data([data])
|
||||||
|
.attr('cx', cx)
|
||||||
|
.attr('cy', cy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setSelectedCircle ({
|
||||||
|
chart,
|
||||||
|
newPrice,
|
||||||
|
closestLowerValueIndex,
|
||||||
|
closestLowerValue,
|
||||||
|
closestHigherValueIndex,
|
||||||
|
closestHigherValue,
|
||||||
|
}) {
|
||||||
|
const numberOfValues = chart.internal.data.xs.data1.length
|
||||||
|
const { x: lowerX, y: lowerY } = getCoordinateData(`.c3-circle-${closestLowerValueIndex}`)
|
||||||
|
const { x: higherX, y: higherY } = getCoordinateData(`.c3-circle-${closestHigherValueIndex}`)
|
||||||
|
|
||||||
|
const currentX = lowerX + (higherX - lowerX) * (newPrice - closestLowerValue) / (closestHigherValue - closestLowerValue)
|
||||||
|
const newTimeEstimate = extrapolateY ({ higherY, lowerY, higherX, lowerX, xForExtrapolation: currentX })
|
||||||
|
|
||||||
|
chart.internal.selectPoint(
|
||||||
|
generateDataUIObj(currentX, numberOfValues, newTimeEstimate),
|
||||||
|
numberOfValues
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimatedTimesMax) {
|
||||||
|
const chart = c3.generate({
|
||||||
|
size: {
|
||||||
|
height: 165,
|
||||||
|
},
|
||||||
|
transition: {
|
||||||
|
duration: 0,
|
||||||
|
},
|
||||||
|
padding: {left: 20, right: 15, top: 6, bottom: 10},
|
||||||
|
data: {
|
||||||
|
x: 'x',
|
||||||
|
columns: [
|
||||||
|
['x', ...gasPrices],
|
||||||
|
['data1', ...estimatedTimes],
|
||||||
|
],
|
||||||
|
types: {
|
||||||
|
data1: 'area',
|
||||||
|
},
|
||||||
|
selection: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
data1: '#259de5',
|
||||||
|
},
|
||||||
|
axis: {
|
||||||
|
x: {
|
||||||
|
min: gasPrices[0],
|
||||||
|
max: gasPricesMax,
|
||||||
|
tick: {
|
||||||
|
values: [Math.floor(gasPrices[0]), Math.ceil(gasPricesMax)],
|
||||||
|
outer: false,
|
||||||
|
format: function (val) { return val + ' GWEI' },
|
||||||
|
},
|
||||||
|
padding: {left: gasPricesMax / 50, right: gasPricesMax / 50},
|
||||||
|
label: {
|
||||||
|
text: 'Gas Price ($)',
|
||||||
|
position: 'outer-center',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
padding: {top: 7, bottom: 7},
|
||||||
|
tick: {
|
||||||
|
values: [Math.floor(estimatedTimesMax * 0.05), Math.ceil(estimatedTimesMax * 0.97)],
|
||||||
|
outer: false,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
text: 'Confirmation time (sec)',
|
||||||
|
position: 'outer-middle',
|
||||||
|
},
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
x: {},
|
||||||
|
lines: {
|
||||||
|
front: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
point: {
|
||||||
|
focus: {
|
||||||
|
expand: {
|
||||||
|
enabled: false,
|
||||||
|
r: 3.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
format: {
|
||||||
|
title: (v) => v.toPrecision(4),
|
||||||
|
},
|
||||||
|
contents: function (d) {
|
||||||
|
const titleFormat = this.config.tooltip_format_title
|
||||||
|
let text
|
||||||
|
d.forEach(el => {
|
||||||
|
if (el && (el.value || el.value === 0) && !text) {
|
||||||
|
text = "<table class='" + 'custom-tooltip' + "'>" + "<tr><th colspan='2'>" + titleFormat(el.x) + '</th></tr>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return text + '</table>' + "<div class='tooltip-arrow'></div>"
|
||||||
|
},
|
||||||
|
position: function (data) {
|
||||||
|
if (d3.select('#overlayed-circle').empty()) {
|
||||||
|
return { top: -100, left: -100 }
|
||||||
|
}
|
||||||
|
|
||||||
|
const { x: circleX, y: circleY, width: circleWidth } = getCoordinateData('#overlayed-circle')
|
||||||
|
const { x: chartXStart, y: chartYStart } = getCoordinateData('.c3-chart')
|
||||||
|
|
||||||
|
// TODO: Confirm the below constants work with all data sets and screen sizes
|
||||||
|
const flipTooltip = circleY - circleWidth < chartYStart + 5
|
||||||
|
|
||||||
|
d3
|
||||||
|
.select('.tooltip-arrow')
|
||||||
|
.style('margin-top', flipTooltip ? '-16px' : '4px')
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: circleY - chartYStart - 19 + (flipTooltip ? circleWidth + 38 : 0),
|
||||||
|
left: circleX - chartXStart + circleWidth - (gasPricesMax / 50),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
chart.internal.selectPoint = function (data, itemIndex = (data.index || 0)) {
|
||||||
|
const { x: chartXStart, y: chartYStart } = getCoordinateData('.c3-areas-data1')
|
||||||
|
|
||||||
|
d3.select('#set-circle').remove()
|
||||||
|
|
||||||
|
appendOrUpdateCircle.bind(this)({
|
||||||
|
data,
|
||||||
|
itemIndex,
|
||||||
|
cx: () => data.x - chartXStart + 11,
|
||||||
|
cy: () => data.value - chartYStart + 10,
|
||||||
|
cssId: 'set-circle',
|
||||||
|
appendOnly: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
chart.internal.overlayPoint = function (data, itemIndex) {
|
||||||
|
appendOrUpdateCircle.bind(this)({
|
||||||
|
data,
|
||||||
|
itemIndex,
|
||||||
|
cx: this.circleX.bind(this),
|
||||||
|
cy: this.circleY.bind(this),
|
||||||
|
cssId: 'overlayed-circle',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
chart.internal.showTooltip = function (selectedData, element) {
|
||||||
|
const dataToShow = selectedData.filter((d) => d && (d.value || d.value === 0))
|
||||||
|
|
||||||
|
if (dataToShow.length) {
|
||||||
|
this.tooltip.html(
|
||||||
|
this.config.tooltip_contents.call(this, selectedData, this.axis.getXAxisTickFormat(), this.getYFormat(), this.color)
|
||||||
|
).style('display', 'flex')
|
||||||
|
|
||||||
|
// Get tooltip dimensions
|
||||||
|
const tWidth = this.tooltip.property('offsetWidth')
|
||||||
|
const tHeight = this.tooltip.property('offsetHeight')
|
||||||
|
const position = this.config.tooltip_position.call(this, dataToShow, tWidth, tHeight, element)
|
||||||
|
// Set tooltip
|
||||||
|
this.tooltip.style('top', position.top + 'px').style('left', position.left + 'px')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chart
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user