mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Modify results of API data to better fit gas chart: remove outliers, pad data
This commit is contained in:
parent
7ffea926f2
commit
f8ffdaedc9
@ -33,16 +33,19 @@ export default class GasPriceChart extends Component {
|
|||||||
updateCustomGasPrice,
|
updateCustomGasPrice,
|
||||||
}) {
|
}) {
|
||||||
const chart = generateChart(gasPrices, estimatedTimes, gasPricesMax, estimatedTimesMax)
|
const chart = generateChart(gasPrices, estimatedTimes, gasPricesMax, estimatedTimesMax)
|
||||||
|
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
setTickPosition('y', 0, -5, 8)
|
setTickPosition('y', 0, -5, 8)
|
||||||
setTickPosition('y', 1, -3, -5)
|
setTickPosition('y', 1, -3, -5)
|
||||||
setTickPosition('x', 0, 3, 15)
|
setTickPosition('x', 0, 3)
|
||||||
setTickPosition('x', 1, 3, -8)
|
setTickPosition('x', 1, 3, -8)
|
||||||
|
|
||||||
// TODO: Confirm the below constants work with all data sets and screen sizes
|
const { x: domainX } = getCoordinateData('.domain')
|
||||||
|
const { x: yAxisX } = getCoordinateData('.c3-axis-y-label')
|
||||||
|
const { x: tickX } = getCoordinateData('.c3-axis-x .tick')
|
||||||
|
|
||||||
|
d3.select('.c3-axis-x .tick').attr('transform', 'translate(' + (domainX - tickX) / 2 + ', 0)')
|
||||||
d3.select('.c3-axis-x-label').attr('transform', 'translate(0,-15)')
|
d3.select('.c3-axis-x-label').attr('transform', 'translate(0,-15)')
|
||||||
d3.select('.c3-axis-y-label').attr('transform', 'translate(52, 2) rotate(-90)')
|
d3.select('.c3-axis-y-label').attr('transform', 'translate(' + (domainX - yAxisX - 12) + ', 2) rotate(-90)')
|
||||||
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', () => {
|
||||||
|
@ -12,6 +12,7 @@ export function handleMouseMove ({ xMousePos, chartXStart, chartWidth, gasPrices
|
|||||||
|
|
||||||
if (currentPosValue === null && newTimeEstimate === null) {
|
if (currentPosValue === null && newTimeEstimate === null) {
|
||||||
hideDataUI(chart, '#overlayed-circle')
|
hideDataUI(chart, '#overlayed-circle')
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexOfNewCircle = estimatedTimes.length + 1
|
const indexOfNewCircle = estimatedTimes.length + 1
|
||||||
@ -162,7 +163,13 @@ export function setSelectedCircle ({
|
|||||||
}) {
|
}) {
|
||||||
const numberOfValues = chart.internal.data.xs.data1.length
|
const numberOfValues = chart.internal.data.xs.data1.length
|
||||||
const { x: lowerX, y: lowerY } = getCoordinateData(`.c3-circle-${closestLowerValueIndex}`)
|
const { x: lowerX, y: lowerY } = getCoordinateData(`.c3-circle-${closestLowerValueIndex}`)
|
||||||
const { x: higherX, y: higherY } = getCoordinateData(`.c3-circle-${closestHigherValueIndex}`)
|
let { x: higherX, y: higherY } = getCoordinateData(`.c3-circle-${closestHigherValueIndex}`)
|
||||||
|
|
||||||
|
if (lowerX === higherX) {
|
||||||
|
const { x: higherXAdjusted, y: higherYAdjusted } = getCoordinateData(`.c3-circle-${closestHigherValueIndex + 1}`)
|
||||||
|
higherY = higherYAdjusted
|
||||||
|
higherX = higherXAdjusted
|
||||||
|
}
|
||||||
|
|
||||||
const currentX = lowerX + (higherX - lowerX) * (newPrice - closestLowerValue) / (closestHigherValue - closestLowerValue)
|
const currentX = lowerX + (higherX - lowerX) * (newPrice - closestLowerValue) / (closestHigherValue - closestLowerValue)
|
||||||
const newTimeEstimate = extrapolateY({ higherY, lowerY, higherX, lowerX, xForExtrapolation: currentX })
|
const newTimeEstimate = extrapolateY({ higherY, lowerY, higherX, lowerX, xForExtrapolation: currentX })
|
||||||
@ -203,13 +210,13 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate
|
|||||||
axis: {
|
axis: {
|
||||||
x: {
|
x: {
|
||||||
min: gasPrices[0],
|
min: gasPrices[0],
|
||||||
max: gasPricesMaxPadded,
|
max: gasPricesMax,
|
||||||
tick: {
|
tick: {
|
||||||
values: [Math.floor(gasPrices[0]), Math.ceil(gasPricesMaxPadded)],
|
values: [Math.floor(gasPrices[0]), Math.ceil(gasPricesMax)],
|
||||||
outer: false,
|
outer: false,
|
||||||
format: function (val) { return val + ' GWEI' },
|
format: function (val) { return val + ' GWEI' },
|
||||||
},
|
},
|
||||||
padding: {left: gasPricesMaxPadded / 50, right: gasPricesMaxPadded / 50},
|
padding: {left: gasPricesMax / 50, right: gasPricesMax / 50},
|
||||||
label: {
|
label: {
|
||||||
text: 'Gas Price ($)',
|
text: 'Gas Price ($)',
|
||||||
position: 'outer-center',
|
position: 'outer-center',
|
||||||
|
@ -136,7 +136,7 @@ describe('GasPriceChart Component', function () {
|
|||||||
assert.equal(gasPriceChartUtilsSpies.setTickPosition.callCount, 4)
|
assert.equal(gasPriceChartUtilsSpies.setTickPosition.callCount, 4)
|
||||||
assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(0).args, ['y', 0, -5, 8])
|
assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(0).args, ['y', 0, -5, 8])
|
||||||
assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(1).args, ['y', 1, -3, -5])
|
assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(1).args, ['y', 1, -3, -5])
|
||||||
assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(2).args, ['x', 0, 3, 15])
|
assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(2).args, ['x', 0, 3])
|
||||||
assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(3).args, ['x', 1, 3, -8])
|
assert.deepEqual(gasPriceChartUtilsSpies.setTickPosition.getCall(3).args, ['x', 1, 3, -8])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { clone, uniqBy } from 'ramda'
|
import { clone, uniqBy, flatten } from 'ramda'
|
||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
import {
|
import {
|
||||||
loadLocalStorageData,
|
loadLocalStorageData,
|
||||||
@ -266,6 +266,56 @@ export function fetchBasicGasAndTimeEstimates () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extrapolateY ({ higherY, lowerY, higherX, lowerX, xForExtrapolation }) {
|
||||||
|
higherY = new BigNumber(higherY, 10)
|
||||||
|
lowerY = new BigNumber(lowerY, 10)
|
||||||
|
higherX = new BigNumber(higherX, 10)
|
||||||
|
lowerX = new BigNumber(lowerX, 10)
|
||||||
|
xForExtrapolation = new BigNumber(xForExtrapolation, 10)
|
||||||
|
const slope = (higherY.minus(lowerY)).div(higherX.minus(lowerX))
|
||||||
|
const newTimeEstimate = slope.times(higherX.minus(xForExtrapolation)).minus(higherY).negated()
|
||||||
|
|
||||||
|
return Number(newTimeEstimate.toPrecision(10))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomArbitrary (min, max) {
|
||||||
|
min = new BigNumber(min, 10)
|
||||||
|
max = new BigNumber(max, 10)
|
||||||
|
const random = new BigNumber(String(Math.random()), 10)
|
||||||
|
return new BigNumber(random.times(max.minus(min)).plus(min)).toPrecision(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcMedian (list) {
|
||||||
|
const medianPos = (Math.floor(list.length / 2) + Math.ceil(list.length / 2)) / 2
|
||||||
|
return medianPos === Math.floor(medianPos)
|
||||||
|
? (list[medianPos - 1] + list[medianPos]) / 2
|
||||||
|
: list[Math.floor(medianPos)]
|
||||||
|
}
|
||||||
|
|
||||||
|
function quartiles (data) {
|
||||||
|
const lowerHalf = data.slice(0, Math.floor(data.length / 2))
|
||||||
|
const upperHalf = data.slice(Math.floor(data.length / 2) + (data.length % 2 === 0 ? 0 : 1))
|
||||||
|
const median = calcMedian(data)
|
||||||
|
const lowerQuartile = calcMedian(lowerHalf)
|
||||||
|
const upperQuartile = calcMedian(upperHalf)
|
||||||
|
return {
|
||||||
|
median,
|
||||||
|
lowerQuartile,
|
||||||
|
upperQuartile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function inliersByIQR (data, prop) {
|
||||||
|
const { lowerQuartile, upperQuartile } = quartiles(data.map(d => prop ? d[prop] : d))
|
||||||
|
const IQR = upperQuartile - lowerQuartile
|
||||||
|
const lowerBound = lowerQuartile - 1.5 * IQR
|
||||||
|
const upperBound = upperQuartile + 1.5 * IQR
|
||||||
|
return data.filter(d => {
|
||||||
|
const value = prop ? d[prop] : d
|
||||||
|
return value >= lowerBound && value <= upperBound
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function fetchGasEstimates (blockTime) {
|
export function fetchGasEstimates (blockTime) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const {
|
const {
|
||||||
@ -289,21 +339,50 @@ export function fetchGasEstimates (blockTime) {
|
|||||||
.then(r => {
|
.then(r => {
|
||||||
const estimatedPricesAndTimes = r.map(({ expectedTime, expectedWait, gasprice }) => ({ expectedTime, expectedWait, gasprice }))
|
const estimatedPricesAndTimes = r.map(({ expectedTime, expectedWait, gasprice }) => ({ expectedTime, expectedWait, gasprice }))
|
||||||
const estimatedTimeWithUniquePrices = uniqBy(({ expectedTime }) => expectedTime, estimatedPricesAndTimes)
|
const estimatedTimeWithUniquePrices = uniqBy(({ expectedTime }) => expectedTime, estimatedPricesAndTimes)
|
||||||
const timeMappedToSeconds = estimatedTimeWithUniquePrices.map(({ expectedWait, gasprice }) => {
|
|
||||||
const expectedTime = (new BigNumber(expectedWait)).times(Number(blockTime), 10).toString(10)
|
const withSupplementalTimeEstimates = flatten(estimatedTimeWithUniquePrices.map(({ expectedWait, gasprice }, i, arr) => {
|
||||||
|
const next = arr[i + 1]
|
||||||
|
if (!next) {
|
||||||
|
return [{ expectedWait, gasprice }]
|
||||||
|
} else {
|
||||||
|
const supplementalPrice = getRandomArbitrary(gasprice, next.gasprice)
|
||||||
|
const supplementalTime = extrapolateY({
|
||||||
|
higherY: next.expectedWait,
|
||||||
|
lowerY: expectedWait,
|
||||||
|
higherX: next.gasprice,
|
||||||
|
lowerX: gasprice,
|
||||||
|
xForExtrapolation: supplementalPrice,
|
||||||
|
})
|
||||||
|
const supplementalPrice2 = getRandomArbitrary(supplementalPrice, next.gasprice)
|
||||||
|
const supplementalTime2 = extrapolateY({
|
||||||
|
higherY: next.expectedWait,
|
||||||
|
lowerY: supplementalTime,
|
||||||
|
higherX: next.gasprice,
|
||||||
|
lowerX: supplementalPrice,
|
||||||
|
xForExtrapolation: supplementalPrice2,
|
||||||
|
})
|
||||||
|
return [
|
||||||
|
{ expectedWait, gasprice },
|
||||||
|
{ expectedWait: supplementalTime, gasprice: supplementalPrice },
|
||||||
|
{ expectedWait: supplementalTime2, gasprice: supplementalPrice2 },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
const withOutliersRemoved = inliersByIQR(withSupplementalTimeEstimates.slice(0).reverse(), 'expectedWait').reverse()
|
||||||
|
const timeMappedToSeconds = withOutliersRemoved.map(({ expectedWait, gasprice }) => {
|
||||||
|
const expectedTime = (new BigNumber(expectedWait)).times(Number(blockTime), 10).toNumber()
|
||||||
return {
|
return {
|
||||||
expectedTime,
|
expectedTime,
|
||||||
expectedWait,
|
gasprice: (new BigNumber(gasprice, 10).toNumber()),
|
||||||
gasprice,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const timeRetrieved = Date.now()
|
const timeRetrieved = Date.now()
|
||||||
dispatch(setApiEstimatesLastRetrieved(timeRetrieved))
|
dispatch(setApiEstimatesLastRetrieved(timeRetrieved))
|
||||||
saveLocalStorageData(timeRetrieved, 'GAS_API_ESTIMATES_LAST_RETRIEVED')
|
saveLocalStorageData(timeRetrieved, 'GAS_API_ESTIMATES_LAST_RETRIEVED')
|
||||||
saveLocalStorageData(timeMappedToSeconds.slice(1), 'GAS_API_ESTIMATES')
|
saveLocalStorageData(timeMappedToSeconds, 'GAS_API_ESTIMATES')
|
||||||
|
|
||||||
return timeMappedToSeconds.slice(1)
|
return timeMappedToSeconds
|
||||||
})
|
})
|
||||||
: Promise.resolve(priceAndTimeEstimates.length
|
: Promise.resolve(priceAndTimeEstimates.length
|
||||||
? priceAndTimeEstimates
|
? priceAndTimeEstimates
|
||||||
|
@ -45,10 +45,25 @@ describe('Gas Duck', () => {
|
|||||||
speed: 'mockSpeed',
|
speed: 'mockSpeed',
|
||||||
}
|
}
|
||||||
const mockPredictTableResponse = [
|
const mockPredictTableResponse = [
|
||||||
|
{ expectedTime: 400, expectedWait: 40, gasprice: 0.25, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 200, expectedWait: 20, gasprice: 0.5, somethingElse: 'foobar' },
|
||||||
{ expectedTime: 100, expectedWait: 10, gasprice: 1, somethingElse: 'foobar' },
|
{ expectedTime: 100, expectedWait: 10, gasprice: 1, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 75, expectedWait: 7.5, gasprice: 1.5, somethingElse: 'foobar' },
|
||||||
{ expectedTime: 50, expectedWait: 5, gasprice: 2, somethingElse: 'foobar' },
|
{ expectedTime: 50, expectedWait: 5, gasprice: 2, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 35, expectedWait: 4.5, gasprice: 3, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 34, expectedWait: 4.4, gasprice: 3.1, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 25, expectedWait: 4.2, gasprice: 3.5, somethingElse: 'foobar' },
|
||||||
{ expectedTime: 20, expectedWait: 4, gasprice: 4, somethingElse: 'foobar' },
|
{ expectedTime: 20, expectedWait: 4, gasprice: 4, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 19, expectedWait: 3.9, gasprice: 4.1, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 15, expectedWait: 3, gasprice: 7, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 14, expectedWait: 2.9, gasprice: 7.1, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 12, expectedWait: 2.5, gasprice: 8, somethingElse: 'foobar' },
|
||||||
{ expectedTime: 10, expectedWait: 2, gasprice: 10, somethingElse: 'foobar' },
|
{ expectedTime: 10, expectedWait: 2, gasprice: 10, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 9, expectedWait: 1.9, gasprice: 10.1, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 5, expectedWait: 1, gasprice: 15, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 4, expectedWait: 0.9, gasprice: 15.1, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 2, expectedWait: 0.8, gasprice: 17, somethingElse: 'foobar' },
|
||||||
|
{ expectedTime: 1.1, expectedWait: 0.6, gasprice: 19.9, somethingElse: 'foobar' },
|
||||||
{ expectedTime: 1, expectedWait: 0.5, gasprice: 20, somethingElse: 'foobar' },
|
{ expectedTime: 1, expectedWait: 0.5, gasprice: 20, somethingElse: 'foobar' },
|
||||||
]
|
]
|
||||||
const fetchStub = sinon.stub().callsFake((url) => new Promise(resolve => {
|
const fetchStub = sinon.stub().callsFake((url) => new Promise(resolve => {
|
||||||
@ -382,35 +397,13 @@ describe('Gas Duck', () => {
|
|||||||
[{ type: SET_API_ESTIMATES_LAST_RETRIEVED, value: 2000000 }]
|
[{ type: SET_API_ESTIMATES_LAST_RETRIEVED, value: 2000000 }]
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.deepEqual(
|
const { type: thirdDispatchCallType, value: priceAndTimeEstimateResult } = mockDistpatch.getCall(2).args[0]
|
||||||
mockDistpatch.getCall(2).args,
|
assert.equal(thirdDispatchCallType, SET_PRICE_AND_TIME_ESTIMATES)
|
||||||
[{
|
assert(priceAndTimeEstimateResult.length < mockPredictTableResponse.length * 3 - 2)
|
||||||
type: SET_PRICE_AND_TIME_ESTIMATES,
|
assert(!priceAndTimeEstimateResult.find(d => d.expectedTime > 100))
|
||||||
value: [
|
assert(!priceAndTimeEstimateResult.find((d, i, a) => a[a + 1] && d.expectedTime > a[a + 1].expectedTime))
|
||||||
{
|
assert(!priceAndTimeEstimateResult.find((d, i, a) => a[a + 1] && d.gasprice > a[a + 1].gasprice))
|
||||||
expectedTime: '25',
|
|
||||||
expectedWait: 5,
|
|
||||||
gasprice: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expectedTime: '20',
|
|
||||||
expectedWait: 4,
|
|
||||||
gasprice: 4,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expectedTime: '10',
|
|
||||||
expectedWait: 2,
|
|
||||||
gasprice: 10,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expectedTime: '2.5',
|
|
||||||
expectedWait: 0.5,
|
|
||||||
gasprice: 20,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
}]
|
|
||||||
)
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
mockDistpatch.getCall(3).args,
|
mockDistpatch.getCall(3).args,
|
||||||
[{ type: GAS_ESTIMATE_LOADING_FINISHED }]
|
[{ type: GAS_ESTIMATE_LOADING_FINISHED }]
|
||||||
|
Loading…
Reference in New Issue
Block a user