├── utils ├── formatPrice.js └── formatDate.js ├── previews ├── 01-bitcoin.png └── 02-ethereum.png ├── components ├── ethereum │ ├── index.js │ ├── chart │ │ ├── TimeMarker.js │ │ ├── Volume.js │ │ ├── HoverMarkers.js │ │ ├── Details.js │ │ └── Chart.js │ ├── footer │ │ └── Footer.js │ ├── topcorner │ │ └── TopCorner.js │ └── banner │ │ └── Banner.js ├── background.js ├── maxprice.js ├── minprice.js ├── tooltips.js ├── hoverline.js ├── bitcoinprice.js └── chart.js ├── README.md ├── package.json ├── LICENSE ├── .gitignore └── pages ├── bitcoin.js └── ethereum.js /utils/formatPrice.js: -------------------------------------------------------------------------------- 1 | import { format } from 'd3-format'; 2 | export default format('$,.2f'); 3 | -------------------------------------------------------------------------------- /previews/01-bitcoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hshoff/viewsource/HEAD/previews/01-bitcoin.png -------------------------------------------------------------------------------- /previews/02-ethereum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hshoff/viewsource/HEAD/previews/02-ethereum.png -------------------------------------------------------------------------------- /utils/formatDate.js: -------------------------------------------------------------------------------- 1 | import { timeFormat } from 'd3-time-format'; 2 | export default timeFormat('%b %d'); 3 | -------------------------------------------------------------------------------- /components/ethereum/index.js: -------------------------------------------------------------------------------- 1 | export { default as TopCorner } from './topcorner/TopCorner'; 2 | export { default as Banner } from './banner/Banner'; 3 | export { default as Chart } from './chart/Chart'; 4 | export { default as Footer } from './footer/Footer'; 5 | -------------------------------------------------------------------------------- /components/background.js: -------------------------------------------------------------------------------- 1 | import { LinearGradient } from '@vx/gradient'; 2 | 3 | export default function Background({ width, height }) { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /components/maxprice.js: -------------------------------------------------------------------------------- 1 | import { LinePath } from '@vx/shape'; 2 | 3 | export default ({ data, label, yText, yScale, xScale, x, y }) => { 4 | return ( 5 | 6 | 17 | 18 | {label} 19 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /components/minprice.js: -------------------------------------------------------------------------------- 1 | import { LinePath } from '@vx/shape'; 2 | 3 | export default ({ data, yScale, xScale, yText, label, x, y }) => { 4 | return ( 5 | 6 | 17 | 18 | {label} 19 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /components/ethereum/chart/TimeMarker.js: -------------------------------------------------------------------------------- 1 | import { Tooltip } from '@vx/tooltip'; 2 | 3 | export default function TimeMarker({ top, time, formatTime, xScale }) { 4 | return ( 5 | 17 |
18 | {formatTime(time)} 19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # View Source 2 | a collection of examples by [@hshoff](https://twitter.com/hshoff) 3 | 4 | 5 | ## 1. Bitcoin price chart 6 | 7 | ![img](./previews/01-bitcoin.png) 8 | 9 | - demo: https://viewsource.now.sh/bitcoin 10 | - youtube: https://www.youtube.com/watch?v=oeE2tuspdHg 11 | 12 | ## 2. Ethereum candlestick chart 13 | 14 | ![img](./previews/02-ethereum.png) 15 | 16 | - demo: https://viewsource.now.sh/ethereum 17 | - youtube: coming soon 18 | 19 | ## Setup 20 | 21 | ```bash 22 | git clone https://github.com/hshoff/viewsource.git 23 | cd viewsource 24 | npm install 25 | npm run dev 26 | 27 | # => localhost:3000/bitcoin 28 | ``` 29 | -------------------------------------------------------------------------------- /components/ethereum/chart/Volume.js: -------------------------------------------------------------------------------- 1 | import { Group } from '@vx/group'; 2 | import { Bar } from '@vx/shape'; 3 | 4 | export default function Volume({ top, scale, xScale, height, data }) { 5 | return ( 6 | 7 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /components/tooltips.js: -------------------------------------------------------------------------------- 1 | import { Tooltip } from '@vx/tooltip'; 2 | 3 | export default ({ yTop, yLeft, yLabel, xTop, xLeft, xLabel }) => { 4 | return ( 5 |
6 | 13 | {xLabel} 14 | 15 | 23 | {yLabel} 24 | 25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /components/ethereum/footer/Footer.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | return ( 3 |
4 |
5 | made with vx 6 |
7 |
8 | 9 | vx-demo.now.sh/gallery 10 | 11 |
12 |
13 | 14 | github.com/hshoff/viewsource 15 | 16 |
17 | 25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /components/hoverline.js: -------------------------------------------------------------------------------- 1 | import { Line } from '@vx/shape'; 2 | 3 | export default ({ from, to, tooltipLeft, tooltipTop }) => { 4 | return ( 5 | 6 | 14 | 22 | 30 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hshoff/funcodetime", 3 | "version": "1.0.0", 4 | "description": "FunTimeCode episodes ", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "next", 8 | "build": "next build", 9 | "start": "next start" 10 | }, 11 | "author": "@hshoff", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@vx/axis": "0.0.134", 15 | "@vx/event": "0.0.127", 16 | "@vx/gradient": "0.0.129", 17 | "@vx/grid": "0.0.131", 18 | "@vx/group": "0.0.127", 19 | "@vx/mock-data": "0.0.127", 20 | "@vx/pattern": "0.0.127", 21 | "@vx/responsive": "0.0.127", 22 | "@vx/scale": "0.0.127", 23 | "@vx/shape": "0.0.131", 24 | "@vx/tooltip": "0.0.134", 25 | "d3-array": "^1.2.0", 26 | "d3-format": "^1.2.0", 27 | "d3-time-format": "^2.0.5", 28 | "isomorphic-fetch": "^2.2.1", 29 | "next": "^3.0.1-beta.20", 30 | "react": "^15.6.1", 31 | "react-dom": "^15.6.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /components/ethereum/topcorner/TopCorner.js: -------------------------------------------------------------------------------- 1 | export default function Banner({ width, height }) { 2 | return ( 3 |
4 | 5 | 6 | 11 | 17 | 23 | ETH 24 | 25 | 26 | 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Harrison Shoff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .next 61 | .DS_Store 62 | 63 | -------------------------------------------------------------------------------- /components/ethereum/chart/HoverMarkers.js: -------------------------------------------------------------------------------- 1 | import { Bar } from '@vx/shape'; 2 | 3 | export default function HoverMarker({ 4 | yScale, 5 | xScale, 6 | height, 7 | width, 8 | margin, 9 | time, 10 | yPoint, 11 | formatPrice 12 | }) { 13 | return ( 14 | 15 | 22 | 30 | 37 | 43 | 44 | {formatPrice(yScale.invert(yPoint))} 45 | 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /pages/bitcoin.js: -------------------------------------------------------------------------------- 1 | import { withScreenSize } from '@vx/responsive'; 2 | import Background from '../components/background'; 3 | import BitcoinPrice from '../components/bitcoinprice'; 4 | 5 | class App extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | data: {} 10 | }; 11 | } 12 | componentDidMount() { 13 | fetch('https://api.coindesk.com/v1/bpi/historical/close.json') 14 | .then(res => { 15 | return res.json(); 16 | }) 17 | .then(json => { 18 | this.setState({ 19 | data: json 20 | }); 21 | }); 22 | } 23 | render() { 24 | const { screenWidth, screenHeight } = this.props; 25 | const { data } = this.state; 26 | return ( 27 |
28 | 29 |
30 | 31 |

32 | {data.disclaimer} 33 |

34 |
35 | 59 |
60 | ); 61 | } 62 | } 63 | 64 | export default withScreenSize(App); 65 | -------------------------------------------------------------------------------- /components/ethereum/chart/Details.js: -------------------------------------------------------------------------------- 1 | import { Tooltip } from '@vx/tooltip'; 2 | 3 | export default function Details({ 4 | formatPrice, 5 | formatNumber, 6 | bucket, 7 | xScale, 8 | yScale 9 | }) { 10 | const left = xScale(bucket.closeTime) + xScale.bandwidth() + 5; 11 | const halfway = xScale.range()[1] / 2; 12 | 13 | return ( 14 | halfway ? 'translate(-104%)' : '' 21 | }} 22 | top={yScale(bucket.lowPrice)} 23 | left={left} 24 | > 25 |
26 |
27 |
high
28 |
29 | {formatPrice(bucket.highPrice)} 30 |
31 |
32 |
33 |
low
34 |
35 | {formatPrice(bucket.lowPrice)} 36 |
37 |
38 |
39 |
open
40 |
41 | {formatPrice(bucket.openPrice)} 42 |
43 |
44 |
45 |
close
46 |
47 | {formatPrice(bucket.closePrice)} 48 |
49 |
50 |
51 |
volume
52 |
53 | {formatNumber(bucket.volume)} 54 |
55 |
56 |
57 | 71 |
72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /components/bitcoinprice.js: -------------------------------------------------------------------------------- 1 | import Chart from './chart'; 2 | import formatPrice from '../utils/formatPrice'; 3 | 4 | export default function BitcoinPrice({ data = {}, width, height }) { 5 | if (!data.bpi) return
loading...
; 6 | 7 | const prices = Object.keys(data.bpi).map(k => ({ 8 | time: k, 9 | price: data.bpi[k] 10 | })); 11 | 12 | const currentPrice = prices[prices.length - 1].price; 13 | const firstPrice = prices[0].price; 14 | const diffPrice = currentPrice - firstPrice; 15 | const hasIncreased = diffPrice > 0; 16 | 17 | return ( 18 |
19 |
20 |
21 | Bitcoin Price
22 | last 30 days 23 |
24 |
25 |
26 |
27 | {formatPrice(currentPrice)} 28 |
29 |
30 | {hasIncreased ? '+' : '-'} 31 | {formatPrice(diffPrice)} 32 |
33 |
34 |
35 |
36 | 47 |
48 | 99 |
100 | ); 101 | } 102 | -------------------------------------------------------------------------------- /components/ethereum/banner/Banner.js: -------------------------------------------------------------------------------- 1 | import { withParentSize } from '@vx/responsive'; 2 | 3 | const Logo = withParentSize(function({ parentWidth, parentHeight }) { 4 | const margin = { 5 | top: 5, 6 | bottom: 5, 7 | right: 22, 8 | left: 22 9 | }; 10 | const width = parentWidth - margin.left - margin.right; 11 | const wCenter = parentWidth / 2; 12 | const hCenter = parentHeight / 2; 13 | const topCenter = hCenter - hCenter * 0.25; 14 | const bottomCenter = hCenter + hCenter * 0.25; 15 | return ( 16 | 17 | 18 | 22 | 27 | 31 | 36 | 42 | 48 | 49 | 50 | ); 51 | }); 52 | 53 | export default function Banner({ 54 | increaseNumItems, 55 | decreaseNumItems, 56 | numItems 57 | }) { 58 | return ( 59 |
60 |
61 | 62 |
63 |
64 | 65 | 66 | {numItems} 67 | 68 | 69 |
70 | 120 |
121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /pages/ethereum.js: -------------------------------------------------------------------------------- 1 | import 'isomorphic-fetch'; 2 | import { TopCorner, Banner, Chart, Footer } from '../components/ethereum'; 3 | 4 | class Ethereum extends React.Component { 5 | static async getInitialProps() { 6 | const res = await fetch( 7 | `https://api.cryptowat.ch/markets/gdax/ethusd/ohlc?period=180` 8 | ); 9 | const json = await res.json(); 10 | return { data: json.result['180'] }; 11 | } 12 | 13 | constructor(props) { 14 | super(props); 15 | this.state = { numItems: 180 }; 16 | this.increaseNumItems = this.increaseNumItems.bind(this); 17 | this.decreaseNumItems = this.decreaseNumItems.bind(this); 18 | } 19 | 20 | increaseNumItems() { 21 | if (this.state.numItems === 500) return; 22 | this.setState(() => ({ numItems: this.state.numItems + 20 })); 23 | } 24 | 25 | decreaseNumItems() { 26 | if (this.state.numItems === 40) return; 27 | this.setState(() => ({ numItems: this.state.numItems - 20 })); 28 | } 29 | 30 | render() { 31 | const { data } = this.props; 32 | 33 | const unix = d => new Date(d * 1000); 34 | 35 | const buckets = data 36 | .map(b => { 37 | const [ 38 | closeTime, 39 | openPrice, 40 | highPrice, 41 | lowPrice, 42 | closePrice, 43 | volume 44 | ] = b; 45 | return { 46 | closeTime: unix(closeTime), 47 | openPrice, 48 | highPrice, 49 | lowPrice, 50 | closePrice, 51 | volume, 52 | hollow: closePrice > openPrice 53 | }; 54 | }) 55 | .reverse() 56 | .slice(0, this.state.numItems); 57 | 58 | const sortedBuckets = buckets.sort((a, b) => { 59 | return a.closeTime - b.closeTime; 60 | }); 61 | 62 | const maxHighPrice = Math.max( 63 | ...buckets.map(b => Math.max(...[b.highPrice, b.openPrice, b.closePrice])) 64 | ); 65 | const minLowPrice = Math.min( 66 | ...buckets.map(b => Math.min(...[b.lowPrice, b.openPrice, b.closePrice])) 67 | ); 68 | const maxVolume = Math.max(...buckets.map(b => b.volume)); 69 | 70 | const start = sortedBuckets[0].closeTime; 71 | const end = sortedBuckets[sortedBuckets.length - 1].closeTime; 72 | 73 | return ( 74 |
75 |
76 |
77 | 87 |
88 | 89 | 94 |
95 |
96 | 129 |
130 | ); 131 | } 132 | } 133 | 134 | export default Ethereum; 135 | -------------------------------------------------------------------------------- /components/chart.js: -------------------------------------------------------------------------------- 1 | import { Group } from '@vx/group'; 2 | import { AreaClosed, LinePath, Bar } from '@vx/shape'; 3 | import { withParentSize } from '@vx/responsive'; 4 | import { scaleTime, scaleLinear } from '@vx/scale'; 5 | import { LinearGradient } from '@vx/gradient'; 6 | import { PatternLines } from '@vx/pattern'; 7 | import { AxisBottom } from '@vx/axis'; 8 | import { withTooltip } from '@vx/tooltip'; 9 | import { localPoint } from '@vx/event'; 10 | import { bisector } from 'd3-array'; 11 | 12 | import Tooltips from './tooltips'; 13 | import HoverLine from './hoverline'; 14 | import MaxPrice from './maxprice'; 15 | import MinPrice from './minprice'; 16 | import formatPrice from '../utils/formatPrice'; 17 | import formatDate from '../utils/formatDate'; 18 | 19 | class Chart extends React.Component { 20 | constructor(props) { 21 | super(props); 22 | } 23 | render() { 24 | const { 25 | data, 26 | parentWidth = 600, 27 | parentHeight = 400, 28 | margin = {}, 29 | tooltipData, 30 | tooltipLeft, 31 | tooltipTop, 32 | showTooltip, 33 | hideTooltip 34 | } = this.props; 35 | 36 | const width = parentWidth - margin.left - margin.right; 37 | const height = parentHeight - margin.top - margin.bottom; 38 | 39 | const x = d => new Date(d.time); 40 | const y = d => d.price; 41 | const bisectDate = bisector(d => x(d)).left; 42 | 43 | const firstPoint = data[0]; 44 | const currentPoint = data[data.length - 1]; 45 | const minPrice = Math.min(...data.map(y)); 46 | const maxPrice = Math.max(...data.map(y)); 47 | const firstPrice = y(firstPoint); 48 | const currentPrice = y(currentPoint); 49 | const maxData = [ 50 | { time: x(firstPoint), price: maxPrice }, 51 | { time: x(currentPoint), price: maxPrice } 52 | ]; 53 | const minData = [ 54 | { time: x(firstPoint), price: minPrice }, 55 | { time: x(currentPoint), price: minPrice } 56 | ]; 57 | 58 | const xScale = scaleTime({ 59 | range: [0, width], 60 | domain: [x(firstPoint), x(currentPoint)] 61 | }); 62 | const yScale = scaleLinear({ 63 | range: [height, 0], 64 | domain: [minPrice, maxPrice + 100] 65 | }); 66 | 67 | return ( 68 |
69 | (this.svg = s)} 71 | width={parentWidth} 72 | height={parentHeight} 73 | > 74 | 81 | 89 | 90 | 107 | } 108 | /> 109 | 118 | 127 | 136 | 146 | 155 | event => hideTooltip()} 161 | onMouseMove={data => event => { 162 | const { x: xPoint } = localPoint(this.svg, event); 163 | const x0 = xScale.invert(xPoint); 164 | const index = bisectDate(data, x0, 1); 165 | const d0 = data[index - 1]; 166 | const d1 = data[index]; 167 | const d = x0 - xScale(x(d0)) > xScale(x(d1)) - x0 ? d1 : d0; 168 | showTooltip({ 169 | tooltipData: d, 170 | tooltipLeft: xScale(x(d)), 171 | tooltipTop: yScale(y(d)) 172 | }); 173 | }} 174 | /> 175 | 176 | {tooltipData && 177 | } 189 | 190 | {tooltipData && 191 | } 199 |
200 | ); 201 | } 202 | } 203 | 204 | export default withTooltip(Chart); 205 | -------------------------------------------------------------------------------- /components/ethereum/chart/Chart.js: -------------------------------------------------------------------------------- 1 | import { Bar } from '@vx/shape'; 2 | import { Group } from '@vx/group'; 3 | import { localPoint } from '@vx/event'; 4 | import { withTooltip, Tooltip } from '@vx/tooltip'; 5 | import { LinearGradient } from '@vx/gradient'; 6 | import { withParentSize } from '@vx/responsive'; 7 | import { GridRows, GridColumns } from '@vx/grid'; 8 | import { AxisLeft, AxisBottom, AxisRight } from '@vx/axis'; 9 | import { scaleTime, scaleLinear, scaleBand } from '@vx/scale'; 10 | import { format } from 'd3-format'; 11 | import { timeFormat } from 'd3-time-format'; 12 | import Volume from './Volume'; 13 | import Details from './Details'; 14 | import TimeMarker from './TimeMarker'; 15 | import HoverMarkers from './HoverMarkers'; 16 | 17 | const formatPrice = format('$,.2f'); 18 | const formatNumber = format(',.0f'); 19 | const formatTime = timeFormat('%I:%M%p'); 20 | 21 | class Chart extends React.Component { 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | activeBucket: undefined, 26 | yPoint: undefined 27 | }; 28 | } 29 | render() { 30 | const { parentWidth, parentHeight, data } = this.props; 31 | const { 32 | buckets, 33 | start, 34 | end, 35 | maxHighPrice, 36 | minLowPrice, 37 | maxVolume, 38 | showTooltip, 39 | hideTooltip, 40 | tooltipLeft, 41 | tooltipTop, 42 | tooltipData 43 | } = data; 44 | 45 | const { activeBucket, yPoint } = this.state; 46 | 47 | const margin = { 48 | top: 0, 49 | left: 0, 50 | right: 0, 51 | bottom: 80 52 | }; 53 | 54 | const width = parentWidth; 55 | const height = parentHeight; 56 | 57 | const xScale = scaleBand({ 58 | range: [0, width - 50], 59 | domain: buckets.map(b => b.closeTime), 60 | padding: 0.3 61 | }); 62 | const timeScale = scaleTime({ 63 | range: [0, width - 50], 64 | domain: [start, end] 65 | }); 66 | const yScale = scaleLinear({ 67 | range: [height - margin.bottom, 20], 68 | domain: [minLowPrice - 3, maxHighPrice] 69 | }); 70 | 71 | const volumeHeight = (height - margin.bottom) * 0.25; 72 | const yVolumeScale = scaleLinear({ 73 | range: [volumeHeight, 0], 74 | domain: [0, maxVolume] 75 | }); 76 | 77 | return ( 78 |
79 | (this.svg = s)}> 80 | 81 | 87 | 88 | 94 | 100 | 101 | {buckets.map(b => { 102 | return ( 103 | 104 | 112 | 120 | 134 | 141 | 142 | ); 143 | })} 144 | 145 | 154 | } 155 | /> 156 | 157 | 174 | } 175 | /> 176 | {activeBucket && 177 | } 187 | event => { 193 | const { x: xPoint, y: yPoint } = localPoint(this.svg, event); 194 | const bandWidth = xScale.step(); 195 | const index = Math.floor(xPoint / bandWidth); 196 | const val = buckets[index]; 197 | const left = xScale(val.closeTime); 198 | this.setState({ 199 | activeBucket: val, 200 | yPoint 201 | }); 202 | }} 203 | onMouseLeave={data => event => 204 | this.setState({ activeBucket: undefined, yPoint: undefined })} 205 | /> 206 | 219 | } 220 | /> 221 | 222 | {activeBucket && 223 |
224 | 230 |
237 |
} 238 |
239 | ); 240 | } 241 | } 242 | 243 | export default withParentSize(withTooltip(Chart)); 244 | --------------------------------------------------------------------------------