diff options
author | Dan Miller <danjm.com@gmail.com> | 2018-11-14 01:36:35 +0800 |
---|---|---|
committer | Dan Miller <danjm.com@gmail.com> | 2018-12-04 11:36:22 +0800 |
commit | f8ffdaedc9a8fac631deafbc1d377430c980af94 (patch) | |
tree | 3cd5cf6a2d056cbe17ea0f5ce5619ccfe88fd8f2 | |
parent | 7ffea926f23b2542c5182df7958defcdd9398b04 (diff) | |
download | tangerine-wallet-browser-f8ffdaedc9a8fac631deafbc1d377430c980af94.tar.gz tangerine-wallet-browser-f8ffdaedc9a8fac631deafbc1d377430c980af94.tar.zst tangerine-wallet-browser-f8ffdaedc9a8fac631deafbc1d377430c980af94.zip |
Modify results of API data to better fit gas chart: remove outliers, pad data
5 files changed, 126 insertions, 44 deletions
diff --git a/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.component.js b/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.component.js index 5ca465ba9..3f382e5b2 100644 --- a/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.component.js +++ b/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.component.js @@ -33,16 +33,19 @@ export default class GasPriceChart extends Component { updateCustomGasPrice, }) { const chart = generateChart(gasPrices, estimatedTimes, gasPricesMax, estimatedTimesMax) - setTimeout(function () { setTickPosition('y', 0, -5, 8) setTickPosition('y', 1, -3, -5) - setTickPosition('x', 0, 3, 15) + setTickPosition('x', 0, 3) 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-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-chart').on('mouseout', () => { diff --git a/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.utils.js b/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.utils.js index 67508703f..0b9dfbd11 100644 --- a/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.utils.js +++ b/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.utils.js @@ -12,6 +12,7 @@ export function handleMouseMove ({ xMousePos, chartXStart, chartWidth, gasPrices if (currentPosValue === null && newTimeEstimate === null) { hideDataUI(chart, '#overlayed-circle') + return } const indexOfNewCircle = estimatedTimes.length + 1 @@ -162,7 +163,13 @@ export function setSelectedCircle ({ }) { 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}`) + 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 newTimeEstimate = extrapolateY({ higherY, lowerY, higherX, lowerX, xForExtrapolation: currentX }) @@ -203,13 +210,13 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate axis: { x: { min: gasPrices[0], - max: gasPricesMaxPadded, + max: gasPricesMax, tick: { - values: [Math.floor(gasPrices[0]), Math.ceil(gasPricesMaxPadded)], + values: [Math.floor(gasPrices[0]), Math.ceil(gasPricesMax)], outer: false, format: function (val) { return val + ' GWEI' }, }, - padding: {left: gasPricesMaxPadded / 50, right: gasPricesMaxPadded / 50}, + padding: {left: gasPricesMax / 50, right: gasPricesMax / 50}, label: { text: 'Gas Price ($)', position: 'outer-center', diff --git a/ui/app/components/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js b/ui/app/components/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js index 46341195b..74eddae42 100644 --- a/ui/app/components/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js +++ b/ui/app/components/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js @@ -136,7 +136,7 @@ describe('GasPriceChart Component', function () { assert.equal(gasPriceChartUtilsSpies.setTickPosition.callCount, 4) 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(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]) }) diff --git a/ui/app/ducks/gas.duck.js b/ui/app/ducks/gas.duck.js index ffd1e773f..20a125dbb 100644 --- a/ui/app/ducks/gas.duck.js +++ b/ui/app/ducks/gas.duck.js @@ -1,4 +1,4 @@ -import { clone, uniqBy } from 'ramda' +import { clone, uniqBy, flatten } from 'ramda' import BigNumber from 'bignumber.js' import { 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) { return (dispatch, getState) => { const { @@ -289,21 +339,50 @@ export function fetchGasEstimates (blockTime) { .then(r => { const estimatedPricesAndTimes = r.map(({ expectedTime, expectedWait, gasprice }) => ({ expectedTime, expectedWait, gasprice })) 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 { expectedTime, - expectedWait, - gasprice, + gasprice: (new BigNumber(gasprice, 10).toNumber()), } }) const timeRetrieved = Date.now() dispatch(setApiEstimatesLastRetrieved(timeRetrieved)) 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 ? priceAndTimeEstimates diff --git a/ui/app/ducks/tests/gas-duck.test.js b/ui/app/ducks/tests/gas-duck.test.js index dff154dea..4783db30c 100644 --- a/ui/app/ducks/tests/gas-duck.test.js +++ b/ui/app/ducks/tests/gas-duck.test.js @@ -45,10 +45,25 @@ describe('Gas Duck', () => { speed: 'mockSpeed', } 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: 75, expectedWait: 7.5, gasprice: 1.5, 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: 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: 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' }, ] const fetchStub = sinon.stub().callsFake((url) => new Promise(resolve => { @@ -382,35 +397,13 @@ describe('Gas Duck', () => { [{ type: SET_API_ESTIMATES_LAST_RETRIEVED, value: 2000000 }] ) - assert.deepEqual( - mockDistpatch.getCall(2).args, - [{ - type: SET_PRICE_AND_TIME_ESTIMATES, - value: [ - { - 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, - }, - ], + const { type: thirdDispatchCallType, value: priceAndTimeEstimateResult } = mockDistpatch.getCall(2).args[0] + assert.equal(thirdDispatchCallType, SET_PRICE_AND_TIME_ESTIMATES) + assert(priceAndTimeEstimateResult.length < mockPredictTableResponse.length * 3 - 2) + assert(!priceAndTimeEstimateResult.find(d => d.expectedTime > 100)) + 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)) - }] - ) assert.deepEqual( mockDistpatch.getCall(3).args, [{ type: GAS_ESTIMATE_LOADING_FINISHED }] |