parent
aa798cc545
commit
6f0406125d
@ -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