// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
import React, { Component, PropTypes } from 'react';
import {
Bar, BarChart,
Rectangle,
Scatter, ScatterChart,
Tooltip,
XAxis, YAxis,
Dot,
ResponsiveContainer
} from 'recharts';
import Slider from 'material-ui/Slider';
import BigNumber from 'bignumber.js';
import componentStyles from './gasPriceSelector.css';
import mainStyles from '../transfer.css';
const styles = Object.assign({}, mainStyles, componentStyles);
const COLORS = {
default: 'rgba(255, 99, 132, 0.2)',
selected: 'rgba(255, 99, 132, 0.5)',
hover: 'rgba(255, 99, 132, 0.15)',
grid: 'rgba(255, 99, 132, 0.5)',
line: 'rgb(255, 99, 132)',
intersection: '#fff'
};
const countModifier = (count) => {
const val = count.toNumber ? count.toNumber() : count;
return Math.log10(val + 1) + 0.1;
};
class CustomCursor extends Component {
static propTypes = {
x: PropTypes.number,
y: PropTypes.number,
width: PropTypes.number,
height: PropTypes.number,
onClick: PropTypes.func,
getIndex: PropTypes.func,
counts: PropTypes.array,
yDomain: PropTypes.array
}
render () {
const { x, y, width, height, getIndex, counts, yDomain } = this.props;
const index = getIndex();
if (index === -1) {
return null;
}
const count = countModifier(counts[index]);
const barHeight = (count / yDomain[1]) * (y + height);
return (
);
}
onClick = () => {
const { onClick, getIndex } = this.props;
const index = getIndex();
onClick({ index });
}
}
class CustomBar extends Component {
static propTypes = {
selected: PropTypes.number,
x: PropTypes.number,
y: PropTypes.number,
width: PropTypes.number,
height: PropTypes.number,
index: PropTypes.number,
onClick: PropTypes.func
}
render () {
const { x, y, selected, index, width, height, onClick } = this.props;
const fill = selected === index
? COLORS.selected
: COLORS.default;
const borderWidth = 0.5;
const borderColor = 'rgba(255, 255, 255, 0.5)';
return (
);
}
}
class CustomizedShape extends Component {
static propTypes = {
showValue: PropTypes.number.isRequired,
cx: PropTypes.number,
cy: PropTypes.number,
payload: PropTypes.object
}
render () {
const { cx, cy, showValue, payload } = this.props;
if (showValue !== payload.y) {
return null;
}
return (
);
}
}
class CustomTooltip extends Component {
static propTypes = {
gasPriceHistogram: PropTypes.shape({
bucketBounds: PropTypes.array.isRequired,
counts: PropTypes.array.isRequired
}).isRequired,
type: PropTypes.string,
payload: PropTypes.array,
label: PropTypes.number,
active: PropTypes.bool
}
render () {
const { active, label, gasPriceHistogram } = this.props;
if (!active) {
return null;
}
const index = label;
const count = gasPriceHistogram.counts[index];
const minGasPrice = gasPriceHistogram.bucketBounds[index];
const maxGasPrice = gasPriceHistogram.bucketBounds[index + 1];
return (
{ count.toNumber() } transactions
with gas price set from
{ minGasPrice.toFormat(0) }
to
{ maxGasPrice.toFormat(0) }
);
}
}
export default class GasPriceSelector extends Component {
static propTypes = {
gasPriceHistogram: PropTypes.shape({
bucketBounds: PropTypes.array.isRequired,
counts: PropTypes.array.isRequired
}).isRequired,
onChange: PropTypes.func.isRequired,
gasPrice: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
])
}
state = {
gasPrice: null,
sliderValue: 0.5,
selectedIndex: 0,
chartData: {
values: [],
xDomain: [],
yDomain: [],
N: 0
}
}
componentWillMount () {
this.computeCharts();
this.setGasPrice();
}
componentWillReceiveProps (nextProps) {
if (nextProps.gasPrice !== this.props.gasPrice) {
this.setGasPrice(nextProps);
}
}
componentWillUpdate (nextProps, nextState) {
if (Math.floor(nextState.sliderValue) !== Math.floor(this.state.sliderValue)) {
this.updateSelectedBarChart(nextState);
}
}
render () {
return (
{ this.renderChart() }
{ this.renderSlider() }
);
}
renderSlider () {
const { sliderValue } = this.state;
return (
);
}
renderChart () {
const { gasPriceHistogram } = this.props;
const { chartData, sliderValue, selectedIndex } = this.state;
if (chartData.values.length === 0) {
return null;
}
const height = 300;
const countIndex = Math.max(0, Math.min(selectedIndex, gasPriceHistogram.counts.length - 1));
const selectedCount = countModifier(gasPriceHistogram.counts[countIndex]);
return (
}
line
isAnimationActive={ false }
/>
}
/>
}
/>
);
}
renderCustomCursor = () => {
const { gasPriceHistogram } = this.props;
const { chartData } = this.state;
return (
);
}
getBarHoverIndex = () => {
const { barChart } = this.refs;
if (!barChart || !barChart.state) {
return -1;
}
return barChart.state.activeTooltipIndex;
}
computeChartsData () {
const { gasPriceChartData } = this.state;
const { gasPriceHistogram } = this.props;
const values = gasPriceChartData
.map((value, index) => ({ value, index }));
const N = values.length - 1;
const maxGasCounts = countModifier(
gasPriceHistogram
.counts
.reduce((max, count) => count.greaterThan(max) ? count : max, 0)
);
const xDomain = [0, N];
const yDomain = [0, maxGasCounts * 1.1];
const chartData = {
values, N,
xDomain, yDomain
};
this.setState({ chartData }, () => {
this.updateSelectedBarChart();
});
}
computeCharts (props = this.props) {
const { gasPriceHistogram } = props;
const gasPriceChartData = gasPriceHistogram
.counts
.map(count => countModifier(count));
this.setState(
{ gasPriceChartData },
() => this.computeChartsData()
);
}
updateSelectedBarChart (state = this.state) {
}
setGasPrice (props = this.props) {
const { gasPrice, gasPriceHistogram } = props;
// If no gas price yet...
if (!gasPrice) {
return this.setSliderValue(0.5);
}
const bnGasPrice = (typeof gasPrice === 'string')
? new BigNumber(gasPrice)
: gasPrice;
// If gas price hasn't changed
if (this.state.gasPrice && bnGasPrice.equals(this.state.gasPrice)) {
return;
}
const gasPrices = gasPriceHistogram.bucketBounds;
const startIndex = gasPrices
.findIndex(price => price.greaterThan(bnGasPrice)) - 1;
// gasPrice Lower than the max in histogram
if (startIndex === -1) {
return this.setSliderValue(0, bnGasPrice);
}
// gasPrice Greater than the max in histogram
if (startIndex === -2) {
return this.setSliderValue(1, bnGasPrice);
}
const priceA = gasPrices[startIndex];
const priceB = gasPrices[startIndex + 1];
const sliderValueDec = bnGasPrice
.minus(priceA)
.dividedBy(priceB.minus(priceA))
.toNumber();
const sliderValue = (startIndex + sliderValueDec) / (gasPrices.length - 1);
this.setSliderValue(sliderValue, bnGasPrice);
}
setSliderValue (value, gasPrice = this.state.gasPrice) {
const { gasPriceHistogram } = this.props;
const N = gasPriceHistogram.bucketBounds.length - 1;
const sliderValue = Math.max(0, Math.min(value, 1));
const selectedIndex = Math.floor(sliderValue * N);
this.setState({ sliderValue, gasPrice, selectedIndex });
}
onBarChartMouseUp = (event) => {
console.log(event);
}
onClickGasPrice = (bar) => {
const { index } = bar;
const ratio = (index + 0.5) / (this.state.chartData.N + 1);
this.onEditGasPriceSlider(null, ratio);
}
onEditGasPriceSlider = (event, sliderValue) => {
const { gasPriceHistogram } = this.props;
const gasPrices = gasPriceHistogram.bucketBounds;
const N = gasPrices.length - 1;
const gasPriceAIdx = Math.floor(sliderValue * N);
const gasPriceBIdx = gasPriceAIdx + 1;
if (gasPriceBIdx === N + 1) {
const gasPrice = gasPrices[gasPriceAIdx].round();
this.props.onChange(event, gasPrice);
return;
}
const gasPriceA = gasPrices[gasPriceAIdx];
const gasPriceB = gasPrices[gasPriceBIdx];
const mult = Math.round((sliderValue % 1) * 100) / 100;
const gasPrice = gasPriceA
.plus(gasPriceB.minus(gasPriceA).times(mult))
.round();
this.setSliderValue(sliderValue, gasPrice);
this.props.onChange(event, gasPrice);
}
}