Advertisement
Chessington

Untitled

Jun 19th, 2019
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.96 KB | None | 0 0
  1. import React from 'react'
  2. import { View, TouchableWithoutFeedback, Text, Animated, Easing, ScrollView, StyleSheet } from 'react-native'
  3. import {initData, drawYAxis, drawGuideLine, drawYAxisLabels, numberWithCommas, drawXAxis, drawXAxisLabels} from '../common'
  4.  
  5. class LineChart extends React.Component {
  6. constructor (props) {
  7. super(props)
  8. let newState = initData(this.props.data, this.props.height, this.props.gap, this.props.numberOfYAxisGuideLine)
  9. this.state = {
  10. loading: false,
  11. sortedData: newState.sortedData,
  12. selectedIndex: null,
  13. nowHeight: 200,
  14. nowWidth: 200,
  15. scrollPosition: 0,
  16. nowX: 0,
  17. nowY: 0,
  18. max: newState.max,
  19. fadeAnim: new Animated.Value(0),
  20. guideArray: newState.guideArray
  21. }
  22.  
  23. this.drawCoordinates = this.drawCoordinates.bind(this)
  24. this.drawCoordinate = this.drawCoordinate.bind(this)
  25. this.drawSelected = this.drawSelected.bind(this)
  26. }
  27.  
  28. shouldComponentUpdate (nextProps, nextState) {
  29. if (nextState.sortedData !== this.state.sortedData ||
  30. nextState.selectedIndex !== this.state.selectedIndex ||
  31. nextState.scrollPosition !== this.state.scrollPosition) {
  32. return true
  33. } else {
  34. return false
  35. }
  36. }
  37.  
  38. componentDidMount () {
  39. Animated.timing(this.state.fadeAnim, { toValue: 1, easing: Easing.bounce, duration: 1000, useNativeDriver: true }).start()
  40. }
  41.  
  42. componentWillReceiveProps (nextProps) {
  43. if (nextProps.data !== this.props.data) {
  44. this.setState(Object.assign({
  45. fadeAnim: new Animated.Value(0)
  46. }, initData(nextProps.data, this.props.height, this.props.gap, this.props.numberOfYAxisGuideLine)), () => {
  47. Animated.timing(this.state.fadeAnim, { toValue: 1, easing: Easing.bounce, duration: 1000, useNativeDriver: true }).start()
  48. })
  49. }
  50. }
  51.  
  52. getTransform (rad, width) {
  53. let x = (0 - width / 2) * Math.cos(rad) - (0 - width / 2) * Math.sin(rad)
  54. let y = (0 - width / 2) * Math.sin(rad) + (0 - width / 2) * Math.cos(rad)
  55.  
  56. return [ {translateX: (-1 * x) - width / 2}, {translateY: (-1 * y) + width / 2}, { rotate: rad + 'rad' } ]
  57. }
  58.  
  59. drawCoordinate (index, start, end, backgroundColor, lineStyle, isBlank, lastCoordinate, seriesIndex) {
  60. let key = 'line' + index
  61. let dx = end.gap - start.gap
  62. let dy = end.ratioY - start.ratioY
  63. let size = Math.sqrt(dx * dx + dy * dy)
  64. let angleRad = -1 * Math.atan2(dy, dx)
  65. let height
  66. let top
  67. let topMargin = 20
  68.  
  69. if (start.ratioY > end.ratioY) {
  70. height = start.ratioY
  71. top = -1 * size
  72. } else {
  73. height = end.ratioY
  74. top = -1 * (size - Math.abs(dy))
  75. }
  76.  
  77. return (
  78. <View key={key} style={{
  79. height: this.props.height + topMargin,
  80. justifyContent: 'flex-end'
  81. }}>
  82.  
  83. <View style={StyleSheet.flatten([{
  84. width: dx,
  85. height: height,
  86. marginTop: topMargin
  87. }, styles.coordinateWrapper])}>
  88. <View style={StyleSheet.flatten([{
  89. top: top,
  90. width: size,
  91. height: size,
  92. borderColor: isBlank ? backgroundColor : this.props.primaryColor,
  93. borderTopWidth: 1,
  94. transform: this.getTransform(angleRad, size)
  95. }, styles.lineBox, lineStyle])} />
  96. <View style={StyleSheet.flatten([styles.absolute, {
  97. height: height - Math.abs(dy) - 2,
  98. backgroundColor: lastCoordinate ? '#FFFFFF00' : backgroundColor,
  99. marginTop: Math.abs(dy) + 2
  100. }])} />
  101. </View>
  102. {!lastCoordinate && seriesIndex === 0 ? (
  103. <View style={StyleSheet.flatten([styles.guideLine, {
  104. width: dx,
  105. borderRightColor: this.props.xAxisGridLineColor
  106. }])} />
  107. ) : null}
  108. {seriesIndex === this.state.sortedData.length - 1 && (
  109. <TouchableWithoutFeedback onPress={() => {
  110. let selectedIndex = lastCoordinate ? index - 1 : index
  111.  
  112. let emptyCount = 0
  113. this.state.sortedData.map((series) => {
  114. if (series.data[selectedIndex].isEmpty) emptyCount++
  115. })
  116. if (emptyCount === this.state.sortedData.length) {
  117. return null
  118. }
  119. // console.log('point', selectedIndex, point)
  120.  
  121. this.setState({
  122. selectedIndex: selectedIndex
  123. }, () => {
  124. if (typeof this.props.onPress === 'function') {
  125. this.props.onPress(selectedIndex)
  126. }
  127. })
  128. }}>
  129. <View style={{
  130. width: dx,
  131. height: '100%',
  132. position: 'absolute',
  133. marginLeft: -1 * dx / 2,
  134. backgroundColor: '#FFFFFF01'
  135. }} />
  136. </TouchableWithoutFeedback>
  137. )}
  138.  
  139. </View>
  140. )
  141. }
  142.  
  143. drawPoint (index, point, seriesColor) {
  144. let key = 'point' + index
  145. let size = 8
  146. let color = !seriesColor ? this.props.primaryColor : seriesColor
  147. if (this.state.selectedIndex === index) {
  148. color = this.props.selectedColor
  149. }
  150.  
  151. if (point.isEmpty || this.props.hidePoints) return null
  152.  
  153. return (
  154. <TouchableWithoutFeedback key={key} onPress={() => {
  155. this.setState({selectedIndex: index})
  156. }}>
  157.  
  158. <View style={StyleSheet.flatten([styles.pointWrapper, {
  159. width: size,
  160. height: size,
  161.  
  162. left: point.gap - size / 2,
  163. bottom: point.ratioY - size / 2,
  164.  
  165. borderColor: color,
  166. backgroundColor: color
  167.  
  168. }])} />
  169. </TouchableWithoutFeedback>
  170. )
  171. }
  172. drawValue (index, point) {
  173. let key = 'pointvalue' + index
  174. let size = 200
  175. return (
  176.  
  177. <View key={key} style={{
  178. position: 'absolute',
  179. left: index === 0 ? point.gap : point.gap - size / 2,
  180. bottom: point.ratioY + 10,
  181. backgroundColor: 'transparent',
  182. width: index !== 0 ? 200 : undefined
  183.  
  184. }} >
  185. {this.drawCustomValue(index, point)}
  186.  
  187. </View>
  188.  
  189. )
  190. }
  191.  
  192. drawCustomValue (index, point) {
  193. if (this.props.customValueRenderer) {
  194. return this.props.customValueRenderer(index, point)
  195. } else {
  196. return null
  197. }
  198. }
  199.  
  200. drawCoordinates (data, seriesColor, seriesIndex) {
  201. let result = []
  202. let lineStyle = {
  203. borderColor: !seriesColor ? this.props.primaryColor : seriesColor
  204. }
  205. let dataLength = data.length
  206.  
  207. for (let i = 0; i < dataLength - 1; i++) {
  208. result.push(this.drawCoordinate(i, data[i], data[i + 1], '#FFFFFF00', lineStyle, false, false, seriesIndex))
  209. }
  210.  
  211. if (dataLength > 0) {
  212. result.push(this.drawPoint(0, data[0], seriesColor))
  213. result.push(this.drawValue(0, data[0], seriesColor))
  214. }
  215.  
  216. for (let i = 0; i < dataLength - 1; i++) {
  217. result.push(this.drawPoint((i + 1), data[i + 1], seriesColor))
  218. result.push(this.drawValue((i + 1), data[i + 1], seriesColor))
  219. }
  220.  
  221. let lastData = Object.assign({}, data[dataLength - 1])
  222. let lastCoordinate = Object.assign({}, data[dataLength - 1])
  223. lastCoordinate.gap = lastCoordinate.gap + this.props.gap
  224. result.push(this.drawCoordinate((dataLength), lastData, lastCoordinate, '#FFFFFF', {}, true, true, seriesIndex))
  225.  
  226. return result
  227. }
  228.  
  229. getDistance (p1, p2) {
  230. return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2))
  231. }
  232.  
  233. drawSelected (index) {
  234. if (this.state.sortedData.length === 0) return null
  235. let data = this.state.sortedData[0].data
  236. let dataObject = data[index]
  237. if (typeof (this.state.selectedIndex) === 'number' && this.state.selectedIndex >= 0) {
  238. if (!dataObject) {
  239. return null
  240. }
  241. let reverse = true
  242. let bottom = dataObject.ratioY
  243.  
  244. let left = dataObject.gap
  245. let gap = 0
  246. if (index === data.length - 1 && index !== 0) {
  247. left = data[index - 1].gap
  248. gap = dataObject.gap - left
  249. }
  250. if (bottom > this.props.height * 2 / 3) {
  251. reverse = false
  252. }
  253.  
  254. return (
  255. <View style={StyleSheet.flatten([styles.selectedWrapper, {
  256. left: left,
  257. justifyContent: 'center'
  258. }])}>
  259. <View style={StyleSheet.flatten([styles.selectedLine, {
  260. backgroundColor: this.props.selectedColor,
  261. marginLeft: gap
  262. }])} />
  263.  
  264. <View style={StyleSheet.flatten([styles.selectedBox])}>
  265. {this.state.sortedData.map((series) => {
  266. let dataObject = series.data[this.state.selectedIndex]
  267. return (
  268. <View key={series.seriesName}>
  269. {dataObject.x ? (
  270. <Text style={styles.tooltipTitle}>{dataObject.x}</Text>
  271. ) : null}
  272. <View style={{flexDirection: 'row', paddingLeft: 5, alignItems: 'center'}}>
  273. <View style={{
  274. width: 10,
  275. height: 5,
  276. marginRight: 3,
  277. borderRadius: 2,
  278. backgroundColor: !series.seriesColor ? this.props.primaryColor : series.seriesColor
  279. }} />
  280. <Text style={styles.tooltipValue}>{numberWithCommas(dataObject.y, false)}</Text>
  281. </View>
  282. </View>
  283. )
  284. })}
  285.  
  286. </View>
  287.  
  288. </View>
  289. )
  290. } else {
  291. return null
  292. }
  293. }
  294.  
  295. render () {
  296. let {fadeAnim} = this.state
  297. return (
  298. this.state.sortedData.length > 0 ? (
  299. <View style={StyleSheet.flatten([styles.wrapper, {
  300. backgroundColor: this.props.backgroundColor
  301. }])}>
  302. <View style={styles.yAxisLabelsWrapper}>
  303. {drawYAxisLabels(this.state.guideArray, this.props.height + 20, this.props.minValue, this.props.labelColor)}
  304.  
  305. </View>
  306.  
  307. <View>
  308. <ScrollView horizontal showsHorizontalScrollIndicator={false}>
  309. <View>
  310.  
  311. <View ref='chartView' style={styles.chartViewWrapper}>
  312.  
  313. {drawYAxis(this.props.yAxisColor)}
  314. {drawGuideLine(this.state.guideArray, this.props.yAxisGridLineColor)}
  315. {this.state.sortedData.map((obj, index) => {
  316. return (
  317. <Animated.View key={'animated_' + index} style={{
  318. transform: [{scaleY: fadeAnim}],
  319. flexDirection: 'row',
  320. alignItems: 'flex-end',
  321. height: '100%',
  322. position: index === 0 ? 'relative' : 'absolute',
  323. minWidth: 200,
  324. marginBottom: this.props.minValue && this.state.guideArray && this.state.guideArray.length > 0 ? -1 * this.state.guideArray[0][2] * this.props.minValue : null
  325. }} >
  326. {this.drawCoordinates(obj.data, obj.seriesColor, index)}
  327. </Animated.View>
  328. )
  329. })}
  330. {this.drawSelected(this.state.selectedIndex)}
  331.  
  332. </View>
  333.  
  334. {drawXAxis(this.props.xAxisColor)}
  335. {drawXAxisLabels(this.state.sortedData[0].data, this.props.gap, this.props.labelColor, this.props.showEvenNumberXaxisLabel)}
  336. </View>
  337.  
  338. </ScrollView>
  339. </View>
  340.  
  341. </View>
  342. ) : null
  343.  
  344. )
  345. }
  346. }
  347.  
  348. LineChart.defaultProps = {
  349. data: [],
  350. primaryColor: '#297AB1',
  351. selectedColor: '#FF0000',
  352. height: 100,
  353. gap: 60,
  354. showEvenNumberXaxisLabel: true,
  355. onPointClick: (point) => {
  356.  
  357. },
  358. numberOfYAxisGuideLine: 5
  359. }
  360.  
  361. const styles = StyleSheet.create({
  362. wrapper: {
  363. flexDirection: 'row',
  364. overflow: 'hidden'
  365. },
  366. yAxisLabelsWrapper: {
  367. paddingRight: 5
  368. },
  369. chartViewWrapper: {
  370. flexDirection: 'row',
  371. alignItems: 'flex-end',
  372. margin: 0,
  373. paddingRight: 0,
  374. overflow: 'hidden'
  375. },
  376. coordinateWrapper: {
  377. overflow: 'hidden',
  378. justifyContent: 'flex-start',
  379. alignContent: 'flex-start'
  380. },
  381. lineBox: {
  382. overflow: 'hidden',
  383. justifyContent: 'flex-start'
  384. },
  385. guideLine: {
  386. position: 'absolute',
  387. height: '100%',
  388. borderRightColor: '#e0e0e050',
  389. borderRightWidth: 1
  390. },
  391. absolute: {
  392. position: 'absolute',
  393. width: '100%'
  394. },
  395. pointWrapper: {
  396. position: 'absolute',
  397. borderRadius: 10,
  398. borderWidth: 1
  399. },
  400. selectedWrapper: {
  401. position: 'absolute',
  402. height: '100%',
  403. alignItems: 'flex-start'
  404. },
  405. selectedLine: {
  406. position: 'absolute',
  407. width: 1,
  408. height: '100%'
  409. },
  410. selectedBox: {
  411. backgroundColor: '#FFFFFF',
  412. borderRadius: 5,
  413. opacity: 0.8,
  414. borderColor: '#AAAAAA',
  415. borderWidth: 1,
  416. position: 'absolute',
  417. padding: 3,
  418. marginLeft: 5,
  419. justifyContent: 'center'
  420. },
  421. tooltipTitle: {fontSize: 10},
  422. tooltipValue: {fontWeight: 'bold', fontSize: 15}
  423. })
  424.  
  425. export default LineChart
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement