Complete integration of gas chart with redux.

feature/default_network_editable
Dan Miller 6 years ago
parent a2bbf504b8
commit cd32c58fb4
  1. 2
      ui/app/components/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js
  2. 3
      ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js
  3. 31
      ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
  4. 2
      ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js
  5. 16
      ui/app/components/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
  6. 94
      ui/app/components/gas-customization/gas-price-chart/gas-price-chart.component.js
  7. 11
      ui/app/components/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js
  8. 2
      ui/app/ducks/gas.duck.js
  9. 1
      ui/app/selectors/custom-gas.js

@ -97,7 +97,7 @@ export default class AdvancedTabContent extends Component {
updateCustomGasLimit
) }
<div className="advanced-tab__fee-chart__title">Live Gas Price Predictions</div>
<GasPriceChart {...gasChartProps} />
<GasPriceChart {...gasChartProps} updateCustomGasPrice={updateCustomGasPrice} />
<div className="advanced-tab__fee-chart__speed-buttons">
<span>Slower</span>
<span>Faster</span>

@ -47,6 +47,7 @@ export default class GasModalPageContainer extends Component {
customGasLimit,
newTotalFiat,
gasChartProps,
currentTimeEstimate,
}) {
const { transactionFee } = this.props
return (
@ -55,7 +56,7 @@ export default class GasModalPageContainer extends Component {
updateCustomGasLimit={convertThenUpdateCustomGasLimit}
customGasPrice={customGasPrice}
customGasLimit={customGasLimit}
timeRemaining="1 min 31 sec"
timeRemaining={currentTimeEstimate}
transactionFee={transactionFee}
totalFee={newTotalFiat}
gasChartProps={gasChartProps}

@ -10,6 +10,7 @@ import {
setCustomGasPrice,
setCustomGasLimit,
resetCustomData,
setCustomTimeEstimate,
} from '../../../ducks/gas.duck'
import {
hideGasButtonGroup,
@ -29,6 +30,7 @@ import {
getBasicGasEstimateLoadingStatus,
getAveragePriceEstimateInHexWEI,
getDefaultActiveButtonIndex,
formatTimeEstimate,
} from '../../../selectors/custom-gas'
import {
formatCurrency,
@ -65,20 +67,24 @@ const mapStateToProps = state => {
const hideBasic = state.appState.modal.modalState.props.hideBasic
const customGasPrice = calcCustomGasPrice(customModalGasPriceInHex)
return {
hideBasic,
isConfirm: isConfirm(state),
customModalGasPriceInHex,
customModalGasLimitInHex,
customGasPrice: calcCustomGasPrice(customModalGasPriceInHex),
customGasPrice,
customGasLimit: calcCustomGasLimit(customModalGasLimitInHex),
newTotalFiat,
currentTimeEstimate: getRenderableTimeEstimate(customGasPrice, state.gas.priceAndTimeEstimates),
gasPriceButtonGroupProps: {
buttonDataLoading,
defaultActiveButtonIndex: getDefaultActiveButtonIndex(gasButtonInfo, customModalGasPriceInHex),
gasButtonInfo,
},
gasChartProps: {
currentPrice: customGasPrice,
priceAndTimeEstimates: state.gas.priceAndTimeEstimates,
},
infoRowProps: {
@ -111,6 +117,7 @@ const mapDispatchToProps = dispatch => {
return dispatch(updateGasAndCalculate({ gasLimit, gasPrice }))
},
hideGasButtonGroup: () => dispatch(hideGasButtonGroup()),
setCustomTimeEstimate: (timeEstimateInSeconds) => dispatch(setCustomTimeEstimate(timeEstimateInSeconds)),
}
}
@ -181,3 +188,25 @@ function addHexWEIsToRenderableFiat (aHexWEI, bHexWEI, convertedCurrency, conver
partialRight(formatCurrency, [convertedCurrency]),
)(aHexWEI, bHexWEI)
}
function getRenderableTimeEstimate (currentGasPrice, priceAndTimeEstimates) {
const gasPrices = priceAndTimeEstimates.map(({ gasprice }) => gasprice)
const estimatedTimes = priceAndTimeEstimates.map(({ expectedTime }) => expectedTime)
const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => {
return e <= currentGasPrice && a[i + 1] >= currentGasPrice
})
const closestHigherValueIndex = gasPrices.findIndex((e, i, a) => {
return e > currentGasPrice
})
const closestLowerValue = gasPrices[closestLowerValueIndex]
const closestHigherValue = gasPrices[closestHigherValueIndex]
const estimatedClosestLowerTimeEstimate = estimatedTimes[closestLowerValueIndex]
const estimatedClosestHigherTimeEstimate = estimatedTimes[closestHigherValueIndex]
const slope = (estimatedClosestHigherTimeEstimate - estimatedClosestLowerTimeEstimate) / (closestHigherValue - closestLowerValue)
const newTimeEstimate = -1 * (slope * (closestHigherValue - currentGasPrice) - estimatedClosestHigherTimeEstimate)
return formatTimeEstimate(newTimeEstimate)
}

@ -65,6 +65,7 @@ describe('GasModalPageContainer Component', function () {
customGasLimit={54321}
gasPriceButtonGroupProps={mockGasPriceButtonGroupProps}
infoRowProps={mockInfoRowProps}
currentTimeEstimate={'1 min 31 sec'}
customGasPriceInHex={'mockCustomGasPriceInHex'}
customGasLimitInHex={'mockCustomGasLimitInHex'}
/>, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } })
@ -199,6 +200,7 @@ describe('GasModalPageContainer Component', function () {
customGasPrice: 123,
customGasLimit: 456,
newTotalFiat: '$0.30',
currentTimeEstimate: '1 min 31 sec',
})
const advancedTabContentProps = renderAdvancedTabContentResult.props
assert.equal(advancedTabContentProps.updateCustomGasPrice(), 'mockConvertThenUpdateCustomGasPrice')

@ -75,7 +75,12 @@ describe('gas-modal-page-container container', () => {
limit: 'aaaaaaaa',
price: 'ffffffff',
},
priceAndTimeEstimates: 'mockPriceAndTimeEstimates',
priceAndTimeEstimates: [
{ gasprice: 3, expectedTime: '31' },
{ gasprice: 4, expectedTime: '62' },
{ gasprice: 5, expectedTime: '93' },
{ gasprice: 6, expectedTime: '124' },
],
},
confirmTransaction: {
txData: {
@ -93,11 +98,18 @@ describe('gas-modal-page-container container', () => {
isConfirm: true,
customGasPrice: 4.294967295,
customGasLimit: 2863311530,
currentTimeEstimate: '~1 min 11 sec',
newTotalFiat: '637.41',
customModalGasLimitInHex: 'aaaaaaaa',
customModalGasPriceInHex: 'ffffffff',
gasChartProps: {
priceAndTimeEstimates: 'mockPriceAndTimeEstimates',
'currentPrice': 4.294967295,
priceAndTimeEstimates: [
{ gasprice: 3, expectedTime: '31' },
{ gasprice: 4, expectedTime: '62' },
{ gasprice: 5, expectedTime: '93' },
{ gasprice: 6, expectedTime: '124' },
],
},
gasPriceButtonGroupProps: {
buttonDataLoading: 'mockBasicGasEstimateLoadingStatus:4',

@ -35,6 +35,43 @@ function appendOrUpdateCircle ({ circle, data, itemIndex, cx, cy, cssId, appendO
}
}
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 {
static contextTypes = {
t: PropTypes.func,
@ -42,12 +79,15 @@ export default class GasPriceChart extends Component {
static propTypes = {
priceAndTimeEstimates: PropTypes.array,
currentPrice: PropTypes.number,
updateCustomGasPrice: PropTypes.func,
}
renderChart (priceAndTimeEstimates) {
renderChart (currentPrice, priceAndTimeEstimates, updateCustomGasPrice) {
const gasPrices = priceAndTimeEstimates.map(({ gasprice }) => gasprice)
const gasPricesMax = gasPrices[gasPrices.length - 1] + 1
const estimatedTimes = priceAndTimeEstimates.map(({ expectedTime }) => expectedTime)
const estimatedTimesMax = estimatedTimes[0]
const chart = c3.generate({
size: {
@ -187,6 +227,28 @@ export default class GasPriceChart extends Component {
})
}
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))
@ -238,13 +300,13 @@ export default class GasPriceChart extends Component {
setTimeout(function () {
setTickPosition('y', 0, -5, 8)
setTickPosition('y', 1, -3)
setTickPosition('x', 0, 3, 20)
setTickPosition('x', 1, 3, -10)
setTickPosition('y', 1, -3, -5)
setTickPosition('x', 0, 3, 15)
setTickPosition('x', 1, 3, -8)
// TODO: Confirm the below constants work with all data sets and screen sizes
d3.select('.c3-axis-x-label').attr('transform', 'translate(0,-15)')
d3.select('.c3-axis-y-label').attr('transform', 'translate(32, 2) rotate(-90)')
d3.select('.c3-axis-y-label').attr('transform', 'translate(52, 2) rotate(-90)')
d3.select('.c3-xgrid-focus line').attr('y2', 98)
d3.select('.c3-chart').on('mouseout', () => {
@ -262,6 +324,7 @@ export default class GasPriceChart extends Component {
const overlayedCircle = d3.select('#overlayed-circle')
const numberOfValues = chart.internal.data.xs.data1.length
const { x: circleX, y: circleY } = overlayedCircle.node().getBoundingClientRect()
const { x: xData } = overlayedCircle.datum()
chart.internal.selectPoint({
x: circleX - chartXStart,
value: circleY - 1.5,
@ -269,13 +332,15 @@ export default class GasPriceChart extends Component {
index: numberOfValues,
name: 'data1',
}, numberOfValues)
updateCustomGasPrice(xData)
})
setSelectedCircle({ chart, gasPrices, currentPrice, chartXStart, chartWidth })
d3.select('.c3-chart').on('mousemove', function () {
const chartMouseXPos = d3.event.clientX - chartXStart
const posPercentile = chartMouseXPos / chartWidth
const currentPosValue = (gasPrices[gasPrices.length - 1] - gasPrices[0]) * posPercentile + gasPrices[0]
const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => {
return e <= currentPosValue && a[i + 1] >= currentPosValue
@ -326,11 +391,26 @@ export default class GasPriceChart extends Component {
})
}, 0)
this.chart = chart
}
componentDidUpdate (prevProps) {
if (prevProps.currentPrice !== this.props.currentPrice) {
const chartRect = d3.select('.c3-areas-data1')
const { x: chartXStart, width: chartWidth } = chartRect.node().getBoundingClientRect()
setSelectedCircle({
chart: this.chart,
currentPrice: this.props.currentPrice,
gasPrices: this.props.priceAndTimeEstimates.map(({ gasprice }) => gasprice),
chartXStart,
chartWidth,
})
}
}
componentDidMount () {
this.renderChart(this.props.priceAndTimeEstimates)
const { currentPrice, priceAndTimeEstimates, updateCustomGasPrice } = this.props
this.renderChart(currentPrice, priceAndTimeEstimates, updateCustomGasPrice)
}
render () {

@ -10,6 +10,9 @@ const mockSelectReturn = {
node: () => ({
getBoundingClientRect: () => ({ x: 123, y: 321, width: 400 }),
}),
empty: sinon.spy(),
remove: sinon.spy(),
style: sinon.spy(),
select: d3.select,
attr: sinon.spy(),
on: sinon.spy(),
@ -17,11 +20,17 @@ const mockSelectReturn = {
const GasPriceChart = proxyquire('../gas-price-chart.component.js', {
'c3': {
generate: function () {
generate: function ({ data: { columns } }) {
return {
internal: {
showTooltip: () => {},
showXGridFocus: () => {},
hideXGridFocus: () => {},
data: {
xs: {
[columns[1][0]]: columns[1].slice(1),
},
},
},
}
},

@ -212,7 +212,7 @@ export function fetchGasEstimates (blockTime) {
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).div(60, 10).toString(10)
const expectedTime = (new BigNumber(expectedWait)).times(Number(blockTime), 10).toString(10)
return {
expectedTime,
expectedWait,

@ -31,6 +31,7 @@ const selectors = {
getAveragePriceEstimateInHexWEI,
getDefaultActiveButtonIndex,
priceEstimateToWei,
formatTimeEstimate,
}
module.exports = selectors

Loading…
Cancel
Save