aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app
diff options
context:
space:
mode:
authorDan Miller <danjm.com@gmail.com>2018-10-24 01:10:27 +0800
committerDan Miller <danjm.com@gmail.com>2018-12-04 11:36:05 +0800
commit6f0406125d9d656a4a9826b616f28ffda894ec8e (patch)
tree777819f0af71fa73d9895afb99e95c226e0e5765 /ui/app
parentaa798cc54557f0740c3e9ab3f7bc85bccdc8abc3 (diff)
downloadtangerine-wallet-browser-6f0406125d9d656a4a9826b616f28ffda894ec8e.tar
tangerine-wallet-browser-6f0406125d9d656a4a9826b616f28ffda894ec8e.tar.gz
tangerine-wallet-browser-6f0406125d9d656a4a9826b616f28ffda894ec8e.tar.bz2
tangerine-wallet-browser-6f0406125d9d656a4a9826b616f28ffda894ec8e.tar.lz
tangerine-wallet-browser-6f0406125d9d656a4a9826b616f28ffda894ec8e.tar.xz
tangerine-wallet-browser-6f0406125d9d656a4a9826b616f28ffda894ec8e.tar.zst
tangerine-wallet-browser-6f0406125d9d656a4a9826b616f28ffda894ec8e.zip
Clean up gas chart code.
Diffstat (limited to 'ui/app')
-rw-r--r--ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js11
-rw-r--r--ui/app/components/gas-customization/gas-price-chart/gas-price-chart.component.js417
-rw-r--r--ui/app/components/gas-customization/gas-price-chart/gas-price-chart.utils.js307
3 files changed, 373 insertions, 362 deletions
diff --git a/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
index 3a62d21cc..67c1ff2e3 100644
--- a/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
+++ b/ui/app/components/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
@@ -69,6 +69,10 @@ const mapStateToProps = state => {
const customGasPrice = calcCustomGasPrice(customModalGasPriceInHex)
+ const priceAndTimeEstimates = state.gas.priceAndTimeEstimates
+ const gasPrices = priceAndTimeEstimates.map(({ gasprice }) => gasprice)
+ const estimatedTimes = priceAndTimeEstimates.map(({ expectedTime }) => expectedTime)
+
return {
hideBasic,
isConfirm: isConfirm(state),
@@ -77,7 +81,7 @@ const mapStateToProps = state => {
customGasPrice,
customGasLimit: calcCustomGasLimit(customModalGasLimitInHex),
newTotalFiat,
- currentTimeEstimate: getRenderableTimeEstimate(customGasPrice, state.gas.priceAndTimeEstimates),
+ currentTimeEstimate: getRenderableTimeEstimate(customGasPrice, priceAndTimeEstimates),
gasPriceButtonGroupProps: {
buttonDataLoading,
defaultActiveButtonIndex: getDefaultActiveButtonIndex(gasButtonInfo, customModalGasPriceInHex),
@@ -85,7 +89,10 @@ const mapStateToProps = state => {
},
gasChartProps: {
currentPrice: customGasPrice,
- priceAndTimeEstimates: state.gas.priceAndTimeEstimates,
+ gasPrices,
+ estimatedTimes,
+ gasPricesMax: gasPrices[gasPrices.length - 1] + 1,
+ estimatedTimesMax: estimatedTimes[0],
},
infoRowProps: {
originalTotalFiat: addHexWEIsToRenderableFiat(value, gasTotal, currentCurrency, conversionRate),
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 69bbd12f6..69f8b3a91 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
@@ -2,75 +2,18 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import * as d3 from 'd3'
import c3 from 'c3'
-
-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')
-}
-
-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
- }
-}
+import {
+ appendOrUpdateCircle,
+ generateChart,
+ generateDataUIObj,
+ getAdjacentGasPrices,
+ getCoordinateData,
+ getNewXandTimeEstimate,
+ handleChartUpdate,
+ hideDataUI,
+ setSelectedCircle,
+ setTickPosition,
+} from './gas-price-chart.utils.js'
export default class GasPriceChart extends Component {
static contextTypes = {
@@ -78,225 +21,23 @@ export default class GasPriceChart extends Component {
}
static propTypes = {
- priceAndTimeEstimates: PropTypes.array,
+ gasPrices: PropTypes.array,
+ estimatedTimes: PropTypes.array,
+ gasPricesMax: PropTypes.number,
+ estimatedTimesMax: PropTypes.number,
currentPrice: PropTypes.number,
updateCustomGasPrice: PropTypes.func,
}
- 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: {
- 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, 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')
- }
+ renderChart ({
+ currentPrice,
+ gasPrices,
+ estimatedTimes,
+ gasPricesMax,
+ estimatedTimesMax,
+ updateCustomGasPrice,
+ }) {
+ const chart = generateChart(gasPrices, estimatedTimes, gasPricesMax, estimatedTimesMax)
setTimeout(function () {
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-chart').on('mouseout', () => {
- const overLayedCircle = d3.select('#overlayed-circle')
- if (!overLayedCircle.empty()) {
- overLayedCircle.remove()
- }
- d3.select('.c3-tooltip-container').style('display', 'none !important')
+ hideDataUI(chart, '#overlayed-circle')
})
- const chartRect = d3.select('.c3-areas-data1')
- const { x: chartXStart, width: chartWidth } = chartRect.node().getBoundingClientRect()
-
d3.select('.c3-chart').on('click', () => {
- 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,
- id: 'data1',
- index: numberOfValues,
- name: 'data1',
- }, numberOfValues)
- updateCustomGasPrice(xData)
+ const { x: newGasPrice } = d3.select('#overlayed-circle').datum()
+ updateCustomGasPrice(newGasPrice)
})
- setSelectedCircle({ chart, gasPrices, currentPrice, chartXStart, chartWidth })
+ const { x: chartXStart, width: chartWidth } = getCoordinateData('.c3-areas-data1')
- d3.select('.c3-chart').on('mousemove', function () {
- const chartMouseXPos = d3.event.clientX - chartXStart
- const posPercentile = chartMouseXPos / chartWidth
+ handleChartUpdate ({
+ chart,
+ gasPrices,
+ newPrice: currentPrice,
+ cssId: '#set-circle',
+ })
- 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
+ d3.select('.c3-chart').on('mousemove', function () {
+ const { currentPosValue, newTimeEstimate } = getNewXandTimeEstimate({
+ xMousePos: d3.event.clientX,
+ chartXStart,
+ chartWidth,
+ gasPrices,
+ estimatedTimes,
})
- const closestLowerValue = gasPrices[closestLowerValueIndex]
- const estimatedClosestLowerTimeEstimate = estimatedTimes[closestLowerValueIndex]
- const closestHigherValueIndex = gasPrices.findIndex((e, i, a) => {
- return e > currentPosValue
- })
- 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
+ if (currentPosValue === null && newTimeEstimate === null) {
+ hideDataUI(chart, '#overlayed-circle')
}
- const estimatedClosestHigherTimeEstimate = estimatedTimes[closestHigherValueIndex]
- const slope = (estimatedClosestHigherTimeEstimate - estimatedClosestLowerTimeEstimate) / (closestHigherValue - closestLowerValue)
- const newTimeEstimate = -1 * (slope * (closestHigherValue - currentPosValue) - estimatedClosestHigherTimeEstimate)
+ const indexOfNewCircle = estimatedTimes.length + 1
+ const dataUIObj = generateDataUIObj(currentPosValue, indexOfNewCircle, newTimeEstimate)
- const newEstimatedTimes = [...estimatedTimes, newTimeEstimate]
- chart.internal.overlayPoint({
- x: currentPosValue,
- 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',
- }])
+ chart.internal.overlayPoint(dataUIObj, indexOfNewCircle)
+ chart.internal.showTooltip([dataUIObj], d3.select('.c3-areas-data1')._groups[0])
+ chart.internal.showXGridFocus([dataUIObj])
})
}, 0)
@@ -395,22 +94,20 @@ export default class GasPriceChart extends Component {
}
componentDidUpdate (prevProps) {
- if (prevProps.currentPrice !== this.props.currentPrice) {
- const chartRect = d3.select('.c3-areas-data1')
- const { x: chartXStart, width: chartWidth } = chartRect.node().getBoundingClientRect()
- setSelectedCircle({
+ const { gasPrices, currentPrice: newPrice } = this.props
+
+ if (prevProps.currentPrice !== newPrice) {
+ handleChartUpdate ({
chart: this.chart,
- currentPrice: this.props.currentPrice,
- gasPrices: this.props.priceAndTimeEstimates.map(({ gasprice }) => gasprice),
- chartXStart,
- chartWidth,
+ gasPrices,
+ newPrice,
+ cssId: '#set-circle',
})
}
}
componentDidMount () {
- const { currentPrice, priceAndTimeEstimates, updateCustomGasPrice } = this.props
- this.renderChart(currentPrice, priceAndTimeEstimates, updateCustomGasPrice)
+ this.renderChart(this.props)
}
render () {
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
new file mode 100644
index 000000000..43bbfd9ab
--- /dev/null
+++ b/ui/app/components/gas-customization/gas-price-chart/gas-price-chart.utils.js
@@ -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
+}