├── .buckconfig ├── .editorconfig ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .watchmanconfig ├── LICENSE ├── README.md ├── android ├── app │ ├── BUCK │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── navexpredux │ │ │ ├── MainActivity.java │ │ │ └── MainApplication.java │ │ └── res │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystores │ ├── BUCK │ └── debug.keystore.properties └── settings.gradle ├── app ├── app.js ├── assets │ ├── better.png │ └── chart.png ├── components │ ├── Code.js │ ├── CopyRight.js │ ├── Loading.js │ └── T.js ├── containers │ └── AppContainer.js ├── data.js ├── demos │ ├── BasicLineChart.js │ ├── CandleStick.js │ ├── CandleStickPanOverlay.js │ ├── CandleStickScrollView.js │ ├── CandleStickWithPan.js │ ├── ChartWithAxis.js │ ├── CustomStockChart.js │ ├── D3Scale.js │ ├── D3Shape.js │ ├── D3Ticks.js │ ├── MultipleAxes.js │ ├── PanResponderDemo.js │ ├── PanResponderOverlay.js │ ├── SVGBasic.js │ ├── StockChartWithVolume.js │ ├── VolumeChart.js │ └── index.js ├── redux │ ├── modules │ │ └── routing.js │ └── reducers.js ├── store │ └── configureStore.js └── util.js ├── index.android.js ├── index.ios.js ├── ios ├── navExpRedux.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── navExpRedux.xcscheme ├── navExpRedux │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ └── main.m └── navExpReduxTests │ ├── Info.plist │ └── navExpReduxTests.m ├── package.json └── screenshots ├── screenshot.gif ├── stock-chart.png └── volume-chart.png /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | end_of_line = lf 4 | indent_size = 2 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | 8 | [*.md] 9 | max_line_length = 0 10 | trim_trailing_whitespace = false 11 | 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // original version from https://github.com/fbsamples/f8app/blob/master/.eslintrc 3 | 4 | "parser": "babel-eslint", 5 | 6 | "ecmaFeatures": { 7 | "jsx": true 8 | }, 9 | 10 | "extends": "eslint-config-airbnb", 11 | 12 | "env": { 13 | "es6": true, 14 | }, 15 | 16 | "plugins": [ 17 | "react" 18 | ], 19 | 20 | // Map from global var to bool specifying if it can be redefined 21 | "globals": { 22 | "__BUNDLE_START_TIME__": false, 23 | "__DEV__": true, 24 | "__dirname": false, 25 | "__filename": false, 26 | "__fbBatchedBridgeConfig": false, 27 | "alert": false, 28 | "cancelAnimationFrame": false, 29 | "clearImmediate": true, 30 | "clearInterval": false, 31 | "clearTimeout": false, 32 | "console": false, 33 | "document": false, 34 | "escape": false, 35 | "exports": false, 36 | "global": false, 37 | "jest": false, 38 | "pit": false, 39 | "Map": true, 40 | "module": false, 41 | "navigator": false, 42 | "process": false, 43 | "Promise": false, 44 | "requestAnimationFrame": true, 45 | "require": false, 46 | "Set": true, 47 | "setImmediate": true, 48 | "setInterval": false, 49 | "setTimeout": false, 50 | "window": false, 51 | "FormData": true, 52 | "XMLHttpRequest": false, 53 | 54 | // Flow "known-globals" annotations: 55 | "ReactElement": false, 56 | "ReactClass": false, 57 | "Class": false 58 | }, 59 | 60 | "rules": { 61 | // cnYes rules! 62 | "space-before-function-paren": ["error", {"anonymous": "never", "named": "never"}], 63 | "arrow-body-style": 0, 64 | "react/prefer-stateless-function": 0, // disable https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md 65 | "import/no-unresolved": 0, 66 | 67 | // F8 app 68 | "comma-dangle": 0, // disallow trailing commas in object literals 69 | "no-cond-assign": 1, // disallow assignment in conditional expressions 70 | "no-console": 0, // disallow use of console (off by default in the node environment) 71 | "no-constant-condition": 0, // disallow use of constant expressions in conditions 72 | "no-control-regex": 1, // disallow control characters in regular expressions 73 | "no-debugger": 1, // disallow use of debugger 74 | "no-dupe-keys": 2, // disallow duplicate keys when creating object literals 75 | "no-empty": 0, // disallow empty statements 76 | "no-empty-character-class": 1, // disallow the use of empty character classes in regular expressions 77 | "no-ex-assign": 1, // disallow assigning to the exception in a catch block 78 | "no-extra-boolean-cast": 1, // disallow double-negation boolean casts in a boolean context 79 | "no-extra-parens": 0, // disallow unnecessary parentheses (off by default) 80 | "no-extra-semi": 1, // disallow unnecessary semicolons 81 | "no-func-assign": 0, // disallow overwriting functions written as function declarations 82 | "no-inner-declarations": 0, // disallow function or variable declarations in nested blocks 83 | "no-invalid-regexp": 1, // disallow invalid regular expression strings in the RegExp constructor 84 | "no-negated-in-lhs": 1, // disallow negation of the left operand of an in expression 85 | "no-obj-calls": 1, // disallow the use of object properties of the global object (Math and JSON) as functions 86 | "no-regex-spaces": 1, // disallow multiple spaces in a regular expression literal 87 | "no-reserved-keys": 0, // disallow reserved words being used as object literal keys (off by default) 88 | "no-sparse-arrays": 1, // disallow sparse arrays 89 | "no-unreachable": 2, // disallow unreachable statements after a return, throw, continue, or break statement 90 | "use-isnan": 1, // disallow comparisons with the value NaN 91 | "valid-jsdoc": 0, // Ensure JSDoc comments are valid (off by default) 92 | "valid-typeof": 1, // Ensure that the results of typeof are compared against a valid string 93 | 94 | // Best Practices 95 | // These are rules designed to prevent you from making mistakes. They either prescribe a better way of doing something or help you avoid footguns. 96 | 97 | "block-scoped-var": 0, // treat var statements as if they were block scoped (off by default) 98 | "complexity": 0, // specify the maximum cyclomatic complexity allowed in a program (off by default) 99 | "consistent-return": 0, // require return statements to either always or never specify values 100 | "curly": 1, // specify curly brace conventions for all control statements 101 | "default-case": 0, // require default case in switch statements (off by default) 102 | "dot-notation": 0, // encourages use of dot notation whenever possible 103 | "eqeqeq": 1, // require the use of === and !== 104 | "guard-for-in": 0, // make sure for-in loops have an if statement (off by default) 105 | "no-alert": 0, // disallow the use of alert, confirm, and prompt 106 | "no-caller": 1, // disallow use of arguments.caller or arguments.callee 107 | "no-div-regex": 1, // disallow division operators explicitly at beginning of regular expression (off by default) 108 | "no-else-return": 0, // disallow else after a return in an if (off by default) 109 | "no-eq-null": 0, // disallow comparisons to null without a type-checking operator (off by default) 110 | "no-eval": 1, // disallow use of eval() 111 | "no-extend-native": 1, // disallow adding to native types 112 | "no-extra-bind": 1, // disallow unnecessary function binding 113 | "no-fallthrough": 1, // disallow fallthrough of case statements 114 | "no-floating-decimal": 1, // disallow the use of leading or trailing decimal points in numeric literals (off by default) 115 | "no-implied-eval": 1, // disallow use of eval()-like methods 116 | "no-labels": 1, // disallow use of labeled statements 117 | "no-iterator": 1, // disallow usage of __iterator__ property 118 | "no-lone-blocks": 1, // disallow unnecessary nested blocks 119 | "no-loop-func": 0, // disallow creation of functions within loops 120 | "no-multi-str": 0, // disallow use of multiline strings 121 | "no-native-reassign": 0, // disallow reassignments of native objects 122 | "no-new": 1, // disallow use of new operator when not part of the assignment or comparison 123 | "no-new-func": 1, // disallow use of new operator for Function object 124 | "no-new-wrappers": 1, // disallows creating new instances of String,Number, and Boolean 125 | "no-octal": 1, // disallow use of octal literals 126 | "no-octal-escape": 1, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251"; 127 | "no-proto": 1, // disallow usage of __proto__ property 128 | "no-redeclare": 0, // disallow declaring the same variable more then once 129 | "no-return-assign": 1, // disallow use of assignment in return statement 130 | "no-script-url": 1, // disallow use of javascript: urls. 131 | "no-self-compare": 1, // disallow comparisons where both sides are exactly the same (off by default) 132 | "no-sequences": 1, // disallow use of comma operator 133 | "no-unused-expressions": 0, // disallow usage of expressions in statement position 134 | "no-void": 1, // disallow use of void operator (off by default) 135 | "no-warning-comments": 0, // disallow usage of configurable warning terms in comments": 1, // e.g. TODO or FIXME (off by default) 136 | "no-with": 1, // disallow use of the with statement 137 | "radix": 1, // require use of the second argument for parseInt() (off by default) 138 | "vars-on-top": 0, // requires to declare all vars on top of their containing scope (off by default) 139 | "wrap-iife": 0, // require immediate function invocation to be wrapped in parentheses (off by default) 140 | "yoda": 1, // require or disallow Yoda conditions 141 | 142 | // Strict Mode 143 | // These rules relate to using strict mode. 144 | 145 | // "strict": [2, "global"], // require or disallow the "use strict" pragma in the global scope (off by default in the node environment) 146 | 147 | // Variables 148 | // These rules have to do with variable declarations. 149 | 150 | "no-catch-shadow": 1, // disallow the catch clause parameter name being the same as a variable in the outer scope (off by default in the node environment) 151 | "no-delete-var": 1, // disallow deletion of variables 152 | "no-label-var": 1, // disallow labels that share a name with a variable 153 | "no-shadow": 1, // disallow declaration of variables already declared in the outer scope 154 | "no-shadow-restricted-names": 1, // disallow shadowing of names such as arguments 155 | "no-undef": 2, // disallow use of undeclared variables unless mentioned in a /*global */ block. 156 | "no-undefined": 0, // disallow use of undefined variable (off by default) 157 | "no-undef-init": 1, // disallow use of undefined when initializing variables 158 | "no-unused-vars": [1, {"vars": "all", "args": "none"}], // disallow declaration of variables that are not used in the code 159 | "no-use-before-define": 0, // disallow use of variables before they are defined 160 | 161 | // Node.js 162 | // These rules are specific to JavaScript running on Node.js. 163 | 164 | "handle-callback-err": 1, // enforces error handling in callbacks (off by default) (on by default in the node environment) 165 | "no-mixed-requires": 1, // disallow mixing regular variable and require declarations (off by default) (on by default in the node environment) 166 | "no-new-require": 1, // disallow use of new operator with the require function (off by default) (on by default in the node environment) 167 | "no-path-concat": 1, // disallow string concatenation with __dirname and __filename (off by default) (on by default in the node environment) 168 | "no-process-exit": 0, // disallow process.exit() (on by default in the node environment) 169 | "no-restricted-modules": 1, // restrict usage of specified node modules (off by default) 170 | "no-sync": 0, // disallow use of synchronous methods (off by default) 171 | 172 | // Stylistic Issues 173 | // These rules are purely matters of style and are quite subjective. 174 | 175 | "key-spacing": 0, 176 | "comma-spacing": 0, 177 | "no-multi-spaces": 0, 178 | "brace-style": 0, // enforce one true brace style (off by default) 179 | "camelcase": 0, // require camel case names 180 | "consistent-this": 1, // enforces consistent naming when capturing the current execution context (off by default) 181 | "eol-last": 1, // enforce newline at the end of file, with no multiple empty lines 182 | "func-names": 0, // require function expressions to have a name (off by default) 183 | "func-style": 0, // enforces use of function declarations or expressions (off by default) 184 | "new-cap": 0, // require a capital letter for constructors 185 | "new-parens": 1, // disallow the omission of parentheses when invoking a constructor with no arguments 186 | "no-nested-ternary": 0, // disallow nested ternary expressions (off by default) 187 | "no-array-constructor": 1, // disallow use of the Array constructor 188 | "no-lonely-if": 0, // disallow if as the only statement in an else block (off by default) 189 | "no-new-object": 1, // disallow use of the Object constructor 190 | "no-spaced-func": 1, // disallow space between function identifier and application 191 | "semi-spacing": 1, // disallow space before semicolon 192 | "no-ternary": 0, // disallow the use of ternary operators (off by default) 193 | "no-trailing-spaces": 1, // disallow trailing whitespace at the end of lines 194 | "no-underscore-dangle": 0, // disallow dangling underscores in identifiers 195 | "no-mixed-spaces-and-tabs": 1, // disallow mixed spaces and tabs for indentation 196 | "quotes": [1, "single", "avoid-escape"], // specify whether double or single quotes should be used 197 | "quote-props": 0, // require quotes around object literal property names (off by default) 198 | "semi": 1, // require or disallow use of semicolons instead of ASI 199 | "sort-vars": 0, // sort variables within the same declaration block (off by default) 200 | "keyword-spacing": 1, // require a space after certain keywords (off by default) 201 | "space-in-brackets": 0, // require or disallow spaces inside brackets (off by default) 202 | "space-in-parens": 0, // require or disallow spaces inside parentheses (off by default) 203 | "space-infix-ops": 1, // require spaces around operators 204 | "space-unary-ops": [1, { "words": true, "nonwords": false }], // require or disallow spaces before/after unary operators (words on by default, nonwords off by default) 205 | "max-nested-callbacks": 0, // specify the maximum depth callbacks can be nested (off by default) 206 | "one-var": 0, // allow just one var statement per function (off by default) 207 | "wrap-regex": 0, // require regex literals to be wrapped in parentheses (off by default) 208 | 209 | // Legacy 210 | // The following rules are included for compatibility with JSHint and JSLint. While the names of the rules may not match up with the JSHint/JSLint counterpart, the functionality is the same. 211 | 212 | "max-depth": 0, // specify the maximum depth that blocks can be nested (off by default) 213 | "max-len": 0, // specify the maximum length of a line in your program (off by default) 214 | "max-params": 0, // limits the number of parameters that can be used in the function declaration. (off by default) 215 | "max-statements": 0, // specify the maximum number of statement allowed in a function (off by default) 216 | "no-bitwise": 1, // disallow use of bitwise operators (off by default) 217 | "no-plusplus": 0, // disallow use of unary operators, ++ and -- (off by default) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | # We fork some components by platform. 4 | .*/*.web.js 5 | .*/*.android.js 6 | 7 | # Some modules have their own node_modules with overlap 8 | .*/node_modules/node-haste/.* 9 | 10 | # Ugh 11 | .*/node_modules/babel.* 12 | .*/node_modules/babylon.* 13 | .*/node_modules/invariant.* 14 | 15 | # Ignore react and fbjs where there are overlaps, but don't ignore 16 | # anything that react-native relies on 17 | .*/node_modules/fbjs/lib/Map.js 18 | .*/node_modules/fbjs/lib/ErrorUtils.js 19 | 20 | # Flow has a built-in definition for the 'react' module which we prefer to use 21 | # over the currently-untyped source 22 | .*/node_modules/react/react.js 23 | .*/node_modules/react/lib/React.js 24 | .*/node_modules/react/lib/ReactDOM.js 25 | 26 | .*/__mocks__/.* 27 | .*/__tests__/.* 28 | 29 | .*/commoner/test/source/widget/share.js 30 | 31 | # Ignore commoner tests 32 | .*/node_modules/commoner/test/.* 33 | 34 | # See https://github.com/facebook/flow/issues/442 35 | .*/react-tools/node_modules/commoner/lib/reader.js 36 | 37 | # Ignore jest 38 | .*/node_modules/jest-cli/.* 39 | 40 | # Ignore Website 41 | .*/website/.* 42 | 43 | # Ignore generators 44 | .*/local-cli/generator.* 45 | 46 | # Ignore BUCK generated folders 47 | .*\.buckd/ 48 | 49 | # Ignore RNPM 50 | .*/local-cli/rnpm/.* 51 | 52 | .*/node_modules/is-my-json-valid/test/.*\.json 53 | .*/node_modules/iconv-lite/encodings/tables/.*\.json 54 | .*/node_modules/y18n/test/.*\.json 55 | .*/node_modules/spdx-license-ids/spdx-license-ids.json 56 | .*/node_modules/spdx-exceptions/index.json 57 | .*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json 58 | .*/node_modules/resolve/lib/core.json 59 | .*/node_modules/jsonparse/samplejson/.*\.json 60 | .*/node_modules/json5/test/.*\.json 61 | .*/node_modules/ua-parser-js/test/.*\.json 62 | .*/node_modules/builtin-modules/builtin-modules.json 63 | .*/node_modules/binary-extensions/binary-extensions.json 64 | .*/node_modules/url-regex/tlds.json 65 | .*/node_modules/joi/.*\.json 66 | .*/node_modules/isemail/.*\.json 67 | .*/node_modules/tr46/.*\.json 68 | 69 | 70 | [include] 71 | 72 | [libs] 73 | node_modules/react-native/Libraries/react-native/react-native-interface.js 74 | node_modules/react-native/flow 75 | flow/ 76 | 77 | [options] 78 | module.system=haste 79 | 80 | esproposal.class_static_fields=enable 81 | esproposal.class_instance_fields=enable 82 | 83 | munge_underscores=true 84 | 85 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' 86 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 87 | 88 | suppress_type=$FlowIssue 89 | suppress_type=$FlowFixMe 90 | suppress_type=$FixMe 91 | 92 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-6]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 93 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-6]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 94 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 95 | 96 | [version] 97 | ^0.26.0 98 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | *.iml 28 | .idea 29 | .gradle 30 | local.properties 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | 37 | # BUCK 38 | buck-out/ 39 | \.buckd/ 40 | android/app/libs 41 | android/keystores/debug.keystore 42 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Joshua Lyman 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-Native-Stock-Chart 2 | 3 | Step-by-Step guide to build stock chart using [victory-chart-native](https://github.com/FormidableLabs/victory-chart-native) 4 | 5 | ## getting started 6 | $ npm install 7 | 8 | $ react-native run-ios 9 | 10 | ## Screenshots 11 | ![Rect](./screenshots/stock-chart.png) 12 | ![Rect](./screenshots/screenshot.gif) 13 | -------------------------------------------------------------------------------- /android/app/BUCK: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # To learn about Buck see [Docs](https://buckbuild.com/). 4 | # To run your application with Buck: 5 | # - install Buck 6 | # - `npm start` - to start the packager 7 | # - `cd android` 8 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 9 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 10 | # - `buck install -r android/app` - compile, install and run application 11 | # 12 | 13 | lib_deps = [] 14 | for jarfile in glob(['libs/*.jar']): 15 | name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) 16 | lib_deps.append(':' + name) 17 | prebuilt_jar( 18 | name = name, 19 | binary_jar = jarfile, 20 | ) 21 | 22 | for aarfile in glob(['libs/*.aar']): 23 | name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) 24 | lib_deps.append(':' + name) 25 | android_prebuilt_aar( 26 | name = name, 27 | aar = aarfile, 28 | ) 29 | 30 | android_library( 31 | name = 'all-libs', 32 | exported_deps = lib_deps 33 | ) 34 | 35 | android_library( 36 | name = 'app-code', 37 | srcs = glob([ 38 | 'src/main/java/**/*.java', 39 | ]), 40 | deps = [ 41 | ':all-libs', 42 | ':build_config', 43 | ':res', 44 | ], 45 | ) 46 | 47 | android_build_config( 48 | name = 'build_config', 49 | package = 'com.navexpredux', 50 | ) 51 | 52 | android_resource( 53 | name = 'res', 54 | res = 'src/main/res', 55 | package = 'com.navexpredux', 56 | ) 57 | 58 | android_binary( 59 | name = 'app', 60 | package_type = 'debug', 61 | manifest = 'src/main/AndroidManifest.xml', 62 | keystore = '//android/keystores:debug', 63 | deps = [ 64 | ':app-code', 65 | ], 66 | ) 67 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // whether to bundle JS and assets in debug mode 22 | * bundleInDebug: false, 23 | * 24 | * // whether to bundle JS and assets in release mode 25 | * bundleInRelease: true, 26 | * 27 | * // whether to bundle JS and assets in another build variant (if configured). 28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 29 | * // The configuration property can be in the following formats 30 | * // 'bundleIn${productFlavor}${buildType}' 31 | * // 'bundleIn${buildType}' 32 | * // bundleInFreeDebug: true, 33 | * // bundleInPaidRelease: true, 34 | * // bundleInBeta: true, 35 | * 36 | * // the root of your project, i.e. where "package.json" lives 37 | * root: "../../", 38 | * 39 | * // where to put the JS bundle asset in debug mode 40 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 41 | * 42 | * // where to put the JS bundle asset in release mode 43 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 44 | * 45 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 46 | * // require('./image.png')), in debug mode 47 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 48 | * 49 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 50 | * // require('./image.png')), in release mode 51 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 52 | * 53 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 54 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 55 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 56 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 57 | * // for example, you might want to remove it from here. 58 | * inputExcludes: ["android/**", "ios/**"], 59 | * 60 | * // override which node gets called and with what additional arguments 61 | * nodeExecutableAndArgs: ["node"] 62 | * 63 | * // supply additional arguments to the packager 64 | * extraPackagerArgs: [] 65 | * ] 66 | */ 67 | 68 | apply from: "../../node_modules/react-native/react.gradle" 69 | 70 | /** 71 | * Set this to true to create two separate APKs instead of one: 72 | * - An APK that only works on ARM devices 73 | * - An APK that only works on x86 devices 74 | * The advantage is the size of the APK is reduced by about 4MB. 75 | * Upload all the APKs to the Play Store and people will download 76 | * the correct one based on the CPU architecture of their device. 77 | */ 78 | def enableSeparateBuildPerCPUArchitecture = false 79 | 80 | /** 81 | * Run Proguard to shrink the Java bytecode in release builds. 82 | */ 83 | def enableProguardInReleaseBuilds = false 84 | 85 | android { 86 | compileSdkVersion 23 87 | buildToolsVersion "23.0.1" 88 | 89 | defaultConfig { 90 | applicationId "com.navexpredux" 91 | minSdkVersion 16 92 | targetSdkVersion 22 93 | versionCode 1 94 | versionName "1.0" 95 | ndk { 96 | abiFilters "armeabi-v7a", "x86" 97 | } 98 | } 99 | splits { 100 | abi { 101 | reset() 102 | enable enableSeparateBuildPerCPUArchitecture 103 | universalApk false // If true, also generate a universal APK 104 | include "armeabi-v7a", "x86" 105 | } 106 | } 107 | buildTypes { 108 | release { 109 | minifyEnabled enableProguardInReleaseBuilds 110 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 111 | } 112 | } 113 | // applicationVariants are e.g. debug, release 114 | applicationVariants.all { variant -> 115 | variant.outputs.each { output -> 116 | // For each separate APK per architecture, set a unique version code as described here: 117 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 118 | def versionCodes = ["armeabi-v7a":1, "x86":2] 119 | def abi = output.getFilter(OutputFile.ABI) 120 | if (abi != null) { // null for the universal-debug, universal-release variants 121 | output.versionCodeOverride = 122 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 123 | } 124 | } 125 | } 126 | } 127 | 128 | dependencies { 129 | compile project(':react-native-svg') 130 | compile fileTree(dir: "libs", include: ["*.jar"]) 131 | compile "com.android.support:appcompat-v7:23.0.1" 132 | compile "com.facebook.react:react-native:+" // From node_modules 133 | } 134 | 135 | // Run this once to be able to run the application with BUCK 136 | // puts all compile dependencies into folder libs for BUCK to use 137 | task copyDownloadableDepsToLibs(type: Copy) { 138 | from configurations.compile 139 | into 'libs' 140 | } 141 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native ; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # okhttp 54 | 55 | -keepattributes Signature 56 | -keepattributes *Annotation* 57 | -keep class okhttp3.** { *; } 58 | -keep interface okhttp3.** { *; } 59 | -dontwarn okhttp3.** 60 | 61 | # okio 62 | 63 | -keep class sun.misc.Unsafe { *; } 64 | -dontwarn java.nio.file.* 65 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 66 | -dontwarn okio.** 67 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/navexpredux/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.navexpredux; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import com.horcrux.svg.RNSvgPackage; 5 | 6 | public class MainActivity extends ReactActivity { 7 | 8 | /** 9 | * Returns the name of the main component registered from JavaScript. 10 | * This is used to schedule rendering of the component. 11 | */ 12 | @Override 13 | protected String getMainComponentName() { 14 | return "navExpRedux"; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/navexpredux/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.navexpredux; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.facebook.react.ReactApplication; 7 | import com.facebook.react.ReactInstanceManager; 8 | import com.facebook.react.ReactNativeHost; 9 | import com.facebook.react.ReactPackage; 10 | import com.facebook.react.shell.MainReactPackage; 11 | 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | public class MainApplication extends Application implements ReactApplication { 16 | 17 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 18 | @Override 19 | protected boolean getUseDeveloperSupport() { 20 | return BuildConfig.DEBUG; 21 | } 22 | 23 | @Override 24 | protected List getPackages() { 25 | return Arrays.asList( 26 | new MainReactPackage() 27 | ); 28 | } 29 | }; 30 | 31 | @Override 32 | public ReactNativeHost getReactNativeHost() { 33 | return mReactNativeHost; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunghe/React-Native-Stock-Chart/ab151943641e87969a2b61618c069f5d33cd7954/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunghe/React-Native-Stock-Chart/ab151943641e87969a2b61618c069f5d33cd7954/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunghe/React-Native-Stock-Chart/ab151943641e87969a2b61618c069f5d33cd7954/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunghe/React-Native-Stock-Chart/ab151943641e87969a2b61618c069f5d33cd7954/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | navExpRedux 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.1' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | jcenter() 19 | maven { 20 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 21 | url "$rootDir/../node_modules/react-native/android" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useDeprecatedNdk=true 21 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunghe/React-Native-Stock-Chart/ab151943641e87969a2b61618c069f5d33cd7954/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = 'debug', 3 | store = 'debug.keystore', 4 | properties = 'debug.keystore.properties', 5 | visibility = [ 6 | 'PUBLIC', 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'navExpRedux' 2 | 3 | include ':app' 4 | include ':react-native-svg' 5 | project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android') 6 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | 4 | import AppContainer from './containers/AppContainer'; 5 | import configureStore from './store/configureStore'; 6 | console.disableYellowBox = true; 7 | const store = configureStore(); 8 | 9 | export default class App extends Component { 10 | render() { 11 | return ( 12 | 13 | 14 | 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/assets/better.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunghe/React-Native-Stock-Chart/ab151943641e87969a2b61618c069f5d33cd7954/app/assets/better.png -------------------------------------------------------------------------------- /app/assets/chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunghe/React-Native-Stock-Chart/ab151943641e87969a2b61618c069f5d33cd7954/app/assets/chart.png -------------------------------------------------------------------------------- /app/components/Code.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { View, Text, StyleSheet } from 'react-native'; 3 | 4 | class Code extends Component { 5 | render() { 6 | const { style } = this.props; 7 | return ( 8 | 9 | {this.props.children} 10 | 11 | ); 12 | } 13 | } 14 | 15 | Code.propTypes = { 16 | children: PropTypes.string.isRequired, 17 | style: PropTypes.object 18 | }; 19 | 20 | const styles = StyleSheet.create({ 21 | code: { 22 | backgroundColor: '#efefef' 23 | }, 24 | codeText: { 25 | fontFamily: 'Menlo-Regular', 26 | }, 27 | }); 28 | 29 | export default Code; 30 | -------------------------------------------------------------------------------- /app/components/CopyRight.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import React, { Component } from 'react'; 3 | import { Dimensions } from 'react-native'; 4 | import { 5 | Svg, 6 | G, 7 | Path, 8 | Polygon, 9 | Circle 10 | } from 'react-native-svg'; 11 | 12 | export default class CopyRight extends Component { 13 | shouldComponentUpdate() { 14 | return false; 15 | } 16 | render() { 17 | const origHeight = 188, 18 | origWidth = 222, 19 | d = Dimensions.get('window'), 20 | width = d.width * 222 / 375, 21 | height = origHeight * width / origWidth, 22 | scale = width / origWidth; 23 | 24 | return ( 25 | 26 | 27 | 28 | 29 | 33 | 34 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | 60 | 61 | 62 | 65 | 74 | 77 | 78 | 80 | 81 | 82 | 83 | 85 | 87 | 97 | 106 | 107 | 108 | 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/components/Loading.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { ActivityIndicator, View, StyleSheet } from 'react-native'; 3 | 4 | class Loading extends Component { 5 | render() { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } 12 | } 13 | 14 | const styles = StyleSheet.create({ 15 | container: { 16 | flex: 1, 17 | backgroundColor: '#fff', 18 | alignItems: 'center', 19 | justifyContent: 'center' 20 | } 21 | }); 22 | 23 | export default Loading; 24 | -------------------------------------------------------------------------------- /app/components/T.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { View, Text, StyleSheet } from 'react-native'; 3 | 4 | class T extends Component { 5 | render() { 6 | const { heading, children } = this.props; 7 | if (heading) { 8 | return ( 9 | 10 | {this.props.children} 11 | 12 | ); 13 | } 14 | return ( 15 | {children} 16 | ); 17 | } 18 | } 19 | 20 | T.propTypes = { 21 | heading: PropTypes.bool, 22 | children: PropTypes.string 23 | }; 24 | 25 | const styles = StyleSheet.create({ 26 | heading: { 27 | paddingBottom: 10, 28 | borderStyle: 'solid', 29 | borderColor: '#ddd', 30 | borderBottomWidth: 1, 31 | marginTop: 20, 32 | marginHorizontal: 15, 33 | marginBottom: 10 34 | }, 35 | headingText: { 36 | fontSize: 24, 37 | }, 38 | text: { 39 | padding: 20, 40 | paddingBottom: 0 41 | }, 42 | }); 43 | 44 | export default T; 45 | -------------------------------------------------------------------------------- /app/containers/AppContainer.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { View, NavigationExperimental, StyleSheet } from 'react-native'; 3 | import { connect } from 'react-redux'; 4 | 5 | import { navigatePush, navigatePop } from '../redux/modules/routing'; 6 | 7 | import Demos from '../demos'; 8 | import BasicLineChart from '../demos/BasicLineChart'; 9 | import ChartWithAxis from '../demos/ChartWithAxis'; 10 | import MultipleAxes from '../demos/MultipleAxes'; 11 | import VolumeChart from '../demos/VolumeChart'; 12 | import StockChartWithVolume from '../demos/StockChartWithVolume'; 13 | import SVGBasic from '../demos/SVGBasic'; 14 | import D3Scale from '../demos/D3Scale'; 15 | import D3Ticks from '../demos/D3Ticks'; 16 | import D3Shape from '../demos/D3Shape'; 17 | import CustomStockChart from '../demos/CustomStockChart'; 18 | import CandleStick from '../demos/CandleStick'; 19 | import CandleStickWithPan from '../demos/CandleStickWithPan'; 20 | import PanResponderDemo from '../demos/PanResponderDemo'; 21 | import PanResponderOverlay from '../demos/PanResponderOverlay'; 22 | import CandleStickPanOverlay from '../demos/CandleStickPanOverlay'; 23 | import CandleStickScrollView from '../demos/CandleStickScrollView'; 24 | 25 | const { 26 | CardStack: NavigationCardStack, 27 | Header: NavigationHeader, 28 | } = NavigationExperimental; 29 | 30 | 31 | class AppContainer extends React.Component { 32 | constructor(props) { 33 | super(props); 34 | this._renderHeader = this._renderHeader.bind(this); 35 | this._renderScene = this._renderScene.bind(this); 36 | } 37 | 38 | _renderScene({ scene }) { 39 | const { route } = scene; 40 | const componentsMapping = { 41 | Demos, 42 | BasicLineChart, 43 | ChartWithAxis, 44 | MultipleAxes, 45 | VolumeChart, 46 | StockChartWithVolume, 47 | SVGBasic, 48 | D3Scale, 49 | D3Ticks, 50 | D3Shape, 51 | CustomStockChart, 52 | PanResponderDemo, 53 | CandleStick, 54 | CandleStickWithPan, 55 | PanResponderOverlay, 56 | CandleStickPanOverlay, 57 | CandleStickScrollView 58 | }; 59 | 60 | const toRender = componentsMapping[route.key] || null; 61 | // make all the Views' marginTop equals header height 62 | return ( 63 | 64 | {React.createElement(toRender, { route })} 65 | 66 | ); 67 | } 68 | 69 | _renderHeader(sceneProps) { 70 | const { handleNavigateBack } = this.props; 71 | const title = sceneProps.scene.route.title || 'Stock Chart'; 72 | return ( 73 | { 78 | // const route = props.scene.route; 79 | return {title}; 80 | }} 81 | /> 82 | ); 83 | } 84 | 85 | render() { 86 | const { navigationState, handleNavigateBack } = this.props; 87 | 88 | return ( 89 | // Redux is handling the reduction of our state for us. We grab the navigationState 90 | // we have in our Redux store and pass it directly to the . 91 | 98 | ); 99 | } 100 | 101 | } 102 | 103 | AppContainer.propTypes = { 104 | navigationState: PropTypes.object, 105 | handleNavigateBack: PropTypes.func.isRequired, 106 | onNavigate: PropTypes.func.isRequired 107 | }; 108 | 109 | const styles = StyleSheet.create({ 110 | outerContainer: { 111 | flex: 1 112 | }, 113 | container: { 114 | flex: 1 115 | }, 116 | center: { 117 | flex: 1, 118 | alignItems: 'center', 119 | justifyContent: 'center', 120 | } 121 | }); 122 | 123 | export default connect( 124 | state => ({ 125 | navigationState: state.routing 126 | }), 127 | dispatch => ({ 128 | handleNavigateBack: (action) => { 129 | dispatch(navigatePop()); 130 | }, 131 | onNavigate: (action) => { 132 | // Two types of actions are likely to be passed, both representing "back" 133 | // style actions. Check if a type has been indicated, and try to match it. 134 | if (action.type && ( action.type === 'BackAction')) { 135 | dispatch(navigatePop()); 136 | } else { 137 | // Currently unused by NavigationExperimental (only passes back actions), 138 | // but could potentially be used by custom components. 139 | dispatch(navigatePush(action)); 140 | } 141 | } 142 | }) 143 | )(AppContainer); 144 | -------------------------------------------------------------------------------- /app/data.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | "code": "TSE", 4 | "previousClose": 8857.75, 5 | "dayTradeVolume": 41614922580, 6 | "highestPrice": 8869.21, 7 | "lowestPrice": 8818.26, 8 | "tradingHours": [ 9 | 1468458000, 10 | 1468474259 11 | ], 12 | "ticks": [ 13 | { 14 | "time": 1468458000, 15 | "volume": 0, 16 | "price": 8857.75, 17 | "mark": 1 18 | }, 19 | { 20 | "time": 1468458055, 21 | "volume": 2524886780, 22 | "price": 8839.27, 23 | "mark": 0 24 | }, 25 | { 26 | "time": 1468458115, 27 | "volume": 785683310, 28 | "price": 8830.2, 29 | "mark": 0 30 | }, 31 | { 32 | "time": 1468458175, 33 | "volume": 760131960, 34 | "price": 8826.74, 35 | "mark": 0 36 | }, 37 | { 38 | "time": 1468458235, 39 | "volume": 670755050, 40 | "price": 8825, 41 | "mark": 0 42 | }, 43 | { 44 | "time": 1468458295, 45 | "volume": 602054660, 46 | "price": 8822.66, 47 | "mark": 0 48 | }, 49 | { 50 | "time": 1468458355, 51 | "volume": 543185590, 52 | "price": 8820.56, 53 | "mark": 0 54 | }, 55 | { 56 | "time": 1468458415, 57 | "volume": 485556590, 58 | "price": 8819.99, 59 | "mark": 0 60 | }, 61 | { 62 | "time": 1468458475, 63 | "volume": 479789580, 64 | "price": 8819.64, 65 | "mark": 0 66 | }, 67 | { 68 | "time": 1468458535, 69 | "volume": 412291470, 70 | "price": 8819.41, 71 | "mark": 0 72 | }, 73 | { 74 | "time": 1468458595, 75 | "volume": 474509390, 76 | "price": 8820.21, 77 | "mark": 2 78 | }, 79 | { 80 | "time": 1468458655, 81 | "volume": 430165880, 82 | "price": 8819.77, 83 | "mark": 0 84 | }, 85 | { 86 | "time": 1468458715, 87 | "volume": 612613140, 88 | "price": 8831.57, 89 | "mark": 2 90 | }, 91 | { 92 | "time": 1468458775, 93 | "volume": 431751250, 94 | "price": 8834, 95 | "mark": 2 96 | }, 97 | { 98 | "time": 1468458835, 99 | "volume": 628293720, 100 | "price": 8838.79, 101 | "mark": 2 102 | }, 103 | { 104 | "time": 1468458895, 105 | "volume": 560474280, 106 | "price": 8843.93, 107 | "mark": 2 108 | }, 109 | { 110 | "time": 1468458955, 111 | "volume": 644525435, 112 | "price": 8845.1, 113 | "mark": 2 114 | }, 115 | { 116 | "time": 1468459015, 117 | "volume": 638692060, 118 | "price": 8850.02, 119 | "mark": 2 120 | }, 121 | { 122 | "time": 1468459075, 123 | "volume": 561567410, 124 | "price": 8849.78, 125 | "mark": 0 126 | }, 127 | { 128 | "time": 1468459135, 129 | "volume": 443538360, 130 | "price": 8849.37, 131 | "mark": 0 132 | }, 133 | { 134 | "time": 1468459195, 135 | "volume": 441057850, 136 | "price": 8848.16, 137 | "mark": 0 138 | }, 139 | { 140 | "time": 1468459255, 141 | "volume": 426256240, 142 | "price": 8848.95, 143 | "mark": 2 144 | }, 145 | { 146 | "time": 1468459315, 147 | "volume": 399121760, 148 | "price": 8848.52, 149 | "mark": 0 150 | }, 151 | { 152 | "time": 1468459375, 153 | "volume": 599859360, 154 | "price": 8856.18, 155 | "mark": 2 156 | }, 157 | { 158 | "time": 1468459435, 159 | "volume": 585147270, 160 | "price": 8862.58, 161 | "mark": 2 162 | }, 163 | { 164 | "time": 1468459495, 165 | "volume": 526068160, 166 | "price": 8866.69, 167 | "mark": 2 168 | }, 169 | { 170 | "time": 1468459555, 171 | "volume": 442998830, 172 | "price": 8857.78, 173 | "mark": 0 174 | }, 175 | { 176 | "time": 1468459615, 177 | "volume": 366801110, 178 | "price": 8860.64, 179 | "mark": 2 180 | }, 181 | { 182 | "time": 1468459675, 183 | "volume": 364752640, 184 | "price": 8855.64, 185 | "mark": 0 186 | }, 187 | { 188 | "time": 1468459735, 189 | "volume": 449797210, 190 | "price": 8850.2, 191 | "mark": 0 192 | }, 193 | { 194 | "time": 1468459795, 195 | "volume": 391833730, 196 | "price": 8852.08, 197 | "mark": 2 198 | }, 199 | { 200 | "time": 1468459855, 201 | "volume": 430187540, 202 | "price": 8850.06, 203 | "mark": 0 204 | }, 205 | { 206 | "time": 1468459915, 207 | "volume": 351018780, 208 | "price": 8850.96, 209 | "mark": 2 210 | }, 211 | { 212 | "time": 1468459975, 213 | "volume": 439807920, 214 | "price": 8853.02, 215 | "mark": 2 216 | }, 217 | { 218 | "time": 1468460035, 219 | "volume": 770627470, 220 | "price": 8858.66, 221 | "mark": 2 222 | }, 223 | { 224 | "time": 1468460095, 225 | "volume": 415784400, 226 | "price": 8858.52, 227 | "mark": 0 228 | }, 229 | { 230 | "time": 1468460155, 231 | "volume": 339660450, 232 | "price": 8862.81, 233 | "mark": 2 234 | }, 235 | { 236 | "time": 1468460215, 237 | "volume": 266343890, 238 | "price": 8858.43, 239 | "mark": 0 240 | }, 241 | { 242 | "time": 1468460275, 243 | "volume": 288673520, 244 | "price": 8857.89, 245 | "mark": 0 246 | }, 247 | { 248 | "time": 1468460335, 249 | "volume": 255110000, 250 | "price": 8857.25, 251 | "mark": 0 252 | }, 253 | { 254 | "time": 1468460395, 255 | "volume": 245320070, 256 | "price": 8851.43, 257 | "mark": 0 258 | }, 259 | { 260 | "time": 1468460455, 261 | "volume": 289512530, 262 | "price": 8854.55, 263 | "mark": 2 264 | }, 265 | { 266 | "time": 1468460515, 267 | "volume": 242013540, 268 | "price": 8856, 269 | "mark": 2 270 | }, 271 | { 272 | "time": 1468460575, 273 | "volume": 298979410, 274 | "price": 8858.43, 275 | "mark": 2 276 | }, 277 | { 278 | "time": 1468460635, 279 | "volume": 392772580, 280 | "price": 8854.95, 281 | "mark": 0 282 | }, 283 | { 284 | "time": 1468460695, 285 | "volume": 352859510, 286 | "price": 8863.84, 287 | "mark": 2 288 | }, 289 | { 290 | "time": 1468460755, 291 | "volume": 322539460, 292 | "price": 8862.15, 293 | "mark": 0 294 | }, 295 | { 296 | "time": 1468460815, 297 | "volume": 272027590, 298 | "price": 8861.58, 299 | "mark": 0 300 | }, 301 | { 302 | "time": 1468460875, 303 | "volume": 265260650, 304 | "price": 8861.07, 305 | "mark": 0 306 | }, 307 | { 308 | "time": 1468460935, 309 | "volume": 258755440, 310 | "price": 8858.65, 311 | "mark": 0 312 | }, 313 | { 314 | "time": 1468460995, 315 | "volume": 287263600, 316 | "price": 8863.75, 317 | "mark": 2 318 | }, 319 | { 320 | "time": 1468461055, 321 | "volume": 279118080, 322 | "price": 8855.07, 323 | "mark": 0 324 | }, 325 | { 326 | "time": 1468461115, 327 | "volume": 235696150, 328 | "price": 8858.51, 329 | "mark": 2 330 | }, 331 | { 332 | "time": 1468461175, 333 | "volume": 249394730, 334 | "price": 8863.67, 335 | "mark": 2 336 | }, 337 | { 338 | "time": 1468461235, 339 | "volume": 258012360, 340 | "price": 8859.74, 341 | "mark": 0 342 | }, 343 | { 344 | "time": 1468461295, 345 | "volume": 297883190, 346 | "price": 8856.4, 347 | "mark": 0 348 | }, 349 | { 350 | "time": 1468461355, 351 | "volume": 250060800, 352 | "price": 8854.67, 353 | "mark": 0 354 | }, 355 | { 356 | "time": 1468461415, 357 | "volume": 344216300, 358 | "price": 8853.59, 359 | "mark": 0 360 | }, 361 | { 362 | "time": 1468461475, 363 | "volume": 283884360, 364 | "price": 8857.6, 365 | "mark": 2 366 | }, 367 | { 368 | "time": 1468461535, 369 | "volume": 263553730, 370 | "price": 8850.74, 371 | "mark": 0 372 | }, 373 | { 374 | "time": 1468461595, 375 | "volume": 348191150, 376 | "price": 8852.54, 377 | "mark": 2 378 | }, 379 | { 380 | "time": 1468461655, 381 | "volume": 636490100, 382 | "price": 8860.52, 383 | "mark": 2 384 | }, 385 | { 386 | "time": 1468461715, 387 | "volume": 552523120, 388 | "price": 8864.1, 389 | "mark": 2 390 | }, 391 | { 392 | "time": 1468461775, 393 | "volume": 376710130, 394 | "price": 8863.47, 395 | "mark": 0 396 | }, 397 | { 398 | "time": 1468461835, 399 | "volume": 276315270, 400 | "price": 8867.17, 401 | "mark": 2 402 | }, 403 | { 404 | "time": 1468461895, 405 | "volume": 278854450, 406 | "price": 8867.39, 407 | "mark": 2 408 | }, 409 | { 410 | "time": 1468461955, 411 | "volume": 283910950, 412 | "price": 8861.72, 413 | "mark": 0 414 | }, 415 | { 416 | "time": 1468462015, 417 | "volume": 236643030, 418 | "price": 8868.68, 419 | "mark": 2 420 | }, 421 | { 422 | "time": 1468462075, 423 | "volume": 232744120, 424 | "price": 8862.82, 425 | "mark": 0 426 | }, 427 | { 428 | "time": 1468462135, 429 | "volume": 230048730, 430 | "price": 8864.75, 431 | "mark": 2 432 | }, 433 | { 434 | "time": 1468462195, 435 | "volume": 225593930, 436 | "price": 8865.14, 437 | "mark": 2 438 | }, 439 | { 440 | "time": 1468462255, 441 | "volume": 259144650, 442 | "price": 8863.88, 443 | "mark": 0 444 | }, 445 | { 446 | "time": 1468462315, 447 | "volume": 239990520, 448 | "price": 8860.78, 449 | "mark": 0 450 | }, 451 | { 452 | "time": 1468462435, 453 | "volume": 625338940, 454 | "price": 8858.44, 455 | "mark": 0 456 | }, 457 | { 458 | "time": 1468462495, 459 | "volume": 361796060, 460 | "price": 8859.12, 461 | "mark": 2 462 | }, 463 | { 464 | "time": 1468462555, 465 | "volume": 235130800, 466 | "price": 8863.7, 467 | "mark": 2 468 | }, 469 | { 470 | "time": 1468462615, 471 | "volume": 247490070, 472 | "price": 8858.28, 473 | "mark": 0 474 | }, 475 | { 476 | "time": 1468462675, 477 | "volume": 268354010, 478 | "price": 8864.87, 479 | "mark": 2 480 | }, 481 | { 482 | "time": 1468462735, 483 | "volume": 222752700, 484 | "price": 8860.17, 485 | "mark": 0 486 | }, 487 | { 488 | "time": 1468462795, 489 | "volume": 217002080, 490 | "price": 8857.61, 491 | "mark": 0 492 | }, 493 | { 494 | "time": 1468462855, 495 | "volume": 226252870, 496 | "price": 8859.73, 497 | "mark": 2 498 | }, 499 | { 500 | "time": 1468462915, 501 | "volume": 227321850, 502 | "price": 8863.95, 503 | "mark": 2 504 | }, 505 | { 506 | "time": 1468462975, 507 | "volume": 202780760, 508 | "price": 8860.92, 509 | "mark": 0 510 | }, 511 | { 512 | "time": 1468463035, 513 | "volume": 211381320, 514 | "price": 8861.79, 515 | "mark": 2 516 | }, 517 | { 518 | "time": 1468463095, 519 | "volume": 226961150, 520 | "price": 8857.96, 521 | "mark": 0 522 | }, 523 | { 524 | "time": 1468463155, 525 | "volume": 222292970, 526 | "price": 8854.95, 527 | "mark": 0 528 | }, 529 | { 530 | "time": 1468463215, 531 | "volume": 283282970, 532 | "price": 8854.66, 533 | "mark": 0 534 | }, 535 | { 536 | "time": 1468463275, 537 | "volume": 199740110, 538 | "price": 8857.75, 539 | "mark": 2 540 | }, 541 | { 542 | "time": 1468463335, 543 | "volume": 214249000, 544 | "price": 8854.85, 545 | "mark": 0 546 | }, 547 | { 548 | "time": 1468463395, 549 | "volume": 223906740, 550 | "price": 8853.79, 551 | "mark": 0 552 | }, 553 | { 554 | "time": 1468463455, 555 | "volume": 230988920, 556 | "price": 8854.16, 557 | "mark": 2 558 | }, 559 | { 560 | "time": 1468463515, 561 | "volume": 264219350, 562 | "price": 8860.96, 563 | "mark": 2 564 | }, 565 | { 566 | "time": 1468463575, 567 | "volume": 243485820, 568 | "price": 8855.51, 569 | "mark": 0 570 | }, 571 | { 572 | "time": 1468463635, 573 | "volume": 230122240, 574 | "price": 8856.65, 575 | "mark": 2 576 | }, 577 | { 578 | "time": 1468463695, 579 | "volume": 248221980, 580 | "price": 8858.98, 581 | "mark": 2 582 | }, 583 | { 584 | "time": 1468463755, 585 | "volume": 265325820, 586 | "price": 8856.15, 587 | "mark": 0 588 | }, 589 | { 590 | "time": 1468463815, 591 | "volume": 237650130, 592 | "price": 8860.06, 593 | "mark": 2 594 | }, 595 | { 596 | "time": 1468463875, 597 | "volume": 275231840, 598 | "price": 8860.9, 599 | "mark": 2 600 | }, 601 | { 602 | "time": 1468463935, 603 | "volume": 177070660, 604 | "price": 8856.62, 605 | "mark": 0 606 | }, 607 | { 608 | "time": 1468463995, 609 | "volume": 285602560, 610 | "price": 8863.78, 611 | "mark": 2 612 | }, 613 | { 614 | "time": 1468464055, 615 | "volume": 228560800, 616 | "price": 8862.55, 617 | "mark": 0 618 | }, 619 | { 620 | "time": 1468464115, 621 | "volume": 158016500, 622 | "price": 8863.85, 623 | "mark": 2 624 | }, 625 | { 626 | "time": 1468464175, 627 | "volume": 181772450, 628 | "price": 8863.06, 629 | "mark": 0 630 | }, 631 | { 632 | "time": 1468464235, 633 | "volume": 204605490, 634 | "price": 8863.58, 635 | "mark": 2 636 | }, 637 | { 638 | "time": 1468464295, 639 | "volume": 216296480, 640 | "price": 8861.95, 641 | "mark": 0 642 | }, 643 | { 644 | "time": 1468464355, 645 | "volume": 206096670, 646 | "price": 8863.28, 647 | "mark": 2 648 | }, 649 | { 650 | "time": 1468464475, 651 | "volume": 421891520, 652 | "price": 8862.42, 653 | "mark": 0 654 | }, 655 | { 656 | "time": 1468464535, 657 | "volume": 209275120, 658 | "price": 8859.33, 659 | "mark": 0 660 | }, 661 | { 662 | "time": 1468464595, 663 | "volume": 237555200, 664 | "price": 8862.66, 665 | "mark": 2 666 | }, 667 | { 668 | "time": 1468464655, 669 | "volume": 214527780, 670 | "price": 8858.23, 671 | "mark": 0 672 | }, 673 | { 674 | "time": 1468464715, 675 | "volume": 231924280, 676 | "price": 8863.1, 677 | "mark": 2 678 | }, 679 | { 680 | "time": 1468464775, 681 | "volume": 292545960, 682 | "price": 8863.64, 683 | "mark": 2 684 | }, 685 | { 686 | "time": 1468464835, 687 | "volume": 223667290, 688 | "price": 8866.17, 689 | "mark": 2 690 | }, 691 | { 692 | "time": 1468464895, 693 | "volume": 217754565, 694 | "price": 8864.46, 695 | "mark": 0 696 | }, 697 | { 698 | "time": 1468464955, 699 | "volume": 227609210, 700 | "price": 8863.1, 701 | "mark": 0 702 | }, 703 | { 704 | "time": 1468465015, 705 | "volume": 161211270, 706 | "price": 8860.38, 707 | "mark": 0 708 | } 709 | ] 710 | } 711 | -------------------------------------------------------------------------------- /app/demos/BasicLineChart.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { ScrollView, StyleSheet } from 'react-native'; 3 | import { 4 | VictoryLine, 5 | } from 'victory-chart-native'; 6 | 7 | import T from '../components/T'; 8 | import data from '../data'; 9 | 10 | class BasicLineChart extends Component { 11 | render() { 12 | const { ticks } = data; 13 | return ( 14 | 15 | Draw basic line chart using VictoryLine 16 | d.time} 19 | y={'price'} 20 | /> 21 | 22 | ); 23 | } 24 | } 25 | 26 | const styles = StyleSheet.create({ 27 | container: { 28 | flex: 1, 29 | backgroundColor: '#fff' 30 | }, 31 | }); 32 | 33 | export default BasicLineChart; 34 | -------------------------------------------------------------------------------- /app/demos/CandleStick.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { TouchableOpacity, View, Text, Dimensions, ScrollView, StyleSheet } from 'react-native'; 3 | 4 | import Svg, { G, Text as SvgText, Rect, Path } from 'react-native-svg'; 5 | 6 | import * as d3Scale from 'd3-scale'; 7 | 8 | import T from '../components/T'; 9 | 10 | const deviceWidth = Dimensions.get('window').width; 11 | const barMargin = 1; // 1 on each side 12 | const defaultStockChartHeight = 200; 13 | const barWidth = 5; 14 | 15 | 16 | class CandleStick extends Component { 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | data: {}, 21 | showGridline: false 22 | }; 23 | } 24 | 25 | componentDidMount() { 26 | this.getStockQuotes(); 27 | } 28 | 29 | getStockQuotes = () => { 30 | const d = new Date(); 31 | const today = new Date(`${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`).getTime(); 32 | const from = today + 86400 * 1000; 33 | const to = from - 86400 * 30 * 1000 * 3; // 3 month ago 34 | const url = `http://m.cnyes.com/api/v1/charting/history?symbol=tse:2330&from=${Math.floor(from / 1000)}&to=${Math.floor(to / 1000)}&resolution=D`; 35 | fetch(url) 36 | .then(rsp => rsp.json()) 37 | .then(data => { 38 | this.setState({ data, current: 0 }); 39 | }); 40 | } 41 | 42 | getLinearScale(domain, range, isTime = false) { 43 | return (isTime ? d3Scale.scaleTime() : d3Scale.scaleLinear()).domain(domain).range(range); 44 | } 45 | 46 | getItemByIndex = (i) => { 47 | const { c, h, l, o, t, v, s } = this.state.data; 48 | return { 49 | c: c[i], 50 | h: h[i], 51 | l: l[i], 52 | o: o[i], 53 | t: new Date(t[i] * 1000), 54 | v: v[i], 55 | s, 56 | color: o[i] <= c[i] ? 'rgb(210, 72, 62)' : 'rgb(28, 193, 135)' 57 | }; 58 | } 59 | 60 | setCurrentItem = (i) => { 61 | if (i <= this.state.data.o.length - 1) { 62 | this.setState({ current: i }); 63 | } 64 | } 65 | 66 | handlePress = (e) => { 67 | const { locationX } = e.nativeEvent; 68 | 69 | // console.log({locationX, locationY}); 70 | const current = Math.floor((deviceWidth - locationX) / (barWidth + 2 * barMargin)); 71 | this.setCurrentItem(current); 72 | } 73 | 74 | toggleGridline = () => { 75 | this.setState({ showGridline: !this.state.showGridline }); 76 | } 77 | 78 | 79 | render() { 80 | const { current } = this.state; 81 | const { c, h, l, o, t, s } = this.state.data; 82 | if (s === undefined) { 83 | return null; 84 | } 85 | const highestPrice = Math.max(...h); 86 | const lowestPrice = Math.min(...l); 87 | const priceScale = this.getLinearScale([lowestPrice, highestPrice], [0, defaultStockChartHeight].reverse()); 88 | 89 | 90 | return ( 91 | 92 | CandleStick chart 93 | {`時間: ${new Date(t[current] * 1000)}`} 94 | 95 | {`收盤: ${c[current]}`} 96 | {`開盤: ${o[current]}`} 97 | {`最高: ${h[current]}`} 98 | {`最低: ${l[current]}`} 99 | 100 | 104 | 105 | 106 | { 107 | t.map((_, i) => { 108 | const item = this.getItemByIndex(i); 109 | const [scaleO, scaleC, yTop, yBottom] = [item.o, item.c, item.h, item.l].map(priceScale); 110 | // deviceWidth divided columns each has (barWidth + 2) width 111 | // leave 1 as the padding on each side 112 | // const x = deviceWidth - i * (barWidth + 2) - barWidth; 113 | const x = deviceWidth - barWidth * (i + 1) - barMargin * (2 * i + 1); 114 | const barHeight = Math.max(Math.abs(scaleO - scaleC), 1); // if open === close, make sure chartHigh = 1 115 | return ( 116 | 119 | 126 | 127 | {current === i && 128 | 129 | } 130 | {current === i && 131 | 132 | } 133 | 134 | ); 135 | }, this) 136 | } 137 | { 138 | this.state.showGridline && 139 | priceScale.ticks(10).map((p, i) => { 140 | return ( 141 | 142 | 149 | {`${p}`} 150 | 151 | 152 | 153 | ); 154 | }) 155 | } 156 | 157 | 158 | 159 | 160 | toggle grid line 161 | 162 | 163 | 164 | ); 165 | } 166 | } 167 | 168 | const styles = StyleSheet.create({ 169 | container: { 170 | flex: 1, 171 | backgroundColor: '#fff' 172 | }, 173 | button: { 174 | borderWidth: 1, 175 | borderColor: '#666', 176 | borderStyle: 'solid', 177 | padding: 10, 178 | marginRight: 10 179 | } 180 | }); 181 | 182 | export default CandleStick; 183 | -------------------------------------------------------------------------------- /app/demos/CandleStickPanOverlay.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { PanResponder, TouchableOpacity, View, Text, Dimensions, ScrollView, StyleSheet } from 'react-native'; 3 | 4 | import Svg, { G, Text as SvgText, Rect, Path } from 'react-native-svg'; 5 | 6 | import * as d3Scale from 'd3-scale'; 7 | 8 | import T from '../components/T'; 9 | 10 | const deviceWidth = Dimensions.get('window').width; 11 | const defaultStockChartHeight = 200; 12 | const barWidth = 5; 13 | const barMargin = 1; // 1 on each side 14 | 15 | class CandleStickPanOverlay extends Component { 16 | constructor(props) { 17 | super(props); 18 | this.state = { 19 | data: {}, 20 | showGridline: false, 21 | svgWidth: deviceWidth * 3 22 | }; 23 | this._previousOffset = (this.state.svgWidth - deviceWidth) * -1; 24 | this.elPanStyle = { 25 | style: { 26 | transform: [{ translateX: this._previousOffset }] 27 | } 28 | }; 29 | } 30 | 31 | componentWillMount() { 32 | this._panResponder = PanResponder.create({ 33 | onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder, 34 | onMoveShouldSetPanResponder: this._alwaysTrue, 35 | onPanResponderGrant: this._alwaysTrue, 36 | onPanResponderMove: this._handlePanResponderMove, 37 | onPanResponderRelease: this._handlePanResponderEnd, 38 | onPanResponderTerminate: this._handlePanResponderEnd 39 | }); 40 | } 41 | 42 | componentDidMount() { 43 | this.getStockQuotes(); 44 | } 45 | 46 | getStockQuotes = () => { 47 | const d = new Date(); 48 | const today = new Date(`${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`).getTime(); 49 | const from = today + 86400 * 1000; 50 | const to = from - 86400 * 30 * 1000; 51 | const url = `http://m.cnyes.com/api/v1/charting/history?symbol=tse:2330&from=${Math.floor(from / 1000)}&to=${Math.floor(to / 1000)}&resolution=D`; 52 | fetch(url) 53 | .then(rsp => rsp.json()) 54 | .then(data => { 55 | this.setState({ data, current: 0 }); 56 | }); 57 | } 58 | 59 | getLinearScale(domain, range, isTime = false) { 60 | return (isTime ? d3Scale.scaleTime() : d3Scale.scaleLinear()).domain(domain).range(range); 61 | } 62 | 63 | getItemByIndex = (i) => { 64 | const { c, h, l, o, t, v, s } = this.state.data; 65 | return { 66 | c: c[i], 67 | h: h[i], 68 | l: l[i], 69 | o: o[i], 70 | t: new Date(t[i] * 1000), 71 | v: v[i], 72 | s, 73 | color: o[i] <= c[i] ? 'rgb(210, 72, 62)' : 'rgb(28, 193, 135)' 74 | }; 75 | } 76 | 77 | setCurrentItem = (i) => { 78 | if (i <= this.state.data.o.length - 1) { 79 | this.setState({ current: i }); 80 | } 81 | } 82 | 83 | loadMore = () => { 84 | const { nextTime } = this.state.data; 85 | const to = (nextTime * 1000 - 86400 * 30 * 1000) / 1000; 86 | if (nextTime) { 87 | const url = `http://m.cnyes.com/api/v1/charting/history?symbol=tse:2330&from=${nextTime}&to=${Math.floor(to)}&resolution=D`; 88 | fetch(url) 89 | .then(rsp => rsp.json()) 90 | .then(data => { 91 | const originData = this.state.data; 92 | const newData = { 93 | ...originData, 94 | ...data, 95 | c: [...originData.c, ...data.c], 96 | h: [...originData.h, ...data.h], 97 | l: [...originData.l, ...data.l], 98 | o: [...originData.o, ...data.o], 99 | t: [...originData.t, ...data.t], 100 | v: [...originData.v, ...data.v], 101 | }; 102 | this.setState({ data: newData }); 103 | }); 104 | } 105 | } 106 | 107 | toggleGridline = () => { 108 | this.setState({ showGridline: !this.state.showGridline }); 109 | } 110 | 111 | _alwaysTrue = () => true 112 | 113 | // limit translate: 0 >= translate >= -750 114 | limit = (translate) => { 115 | return Math.max(Math.min(0, translate), -1 * (this.state.svgWidth - deviceWidth)); 116 | } 117 | 118 | _handleStartShouldSetPanResponder = (e, gestureState) => { 119 | const { locationX } = e.nativeEvent; 120 | const current = Math.floor((this.state.svgWidth - locationX) / (barWidth + 2 * barMargin)); 121 | this.setCurrentItem(current); 122 | } 123 | 124 | _handlePanResponderMove = (e, gestureState) => { 125 | this.elPanStyle.style.transform[0].translateX = this.limit(this._previousOffset + gestureState.dx); 126 | this._updateNativeStyles(); 127 | } 128 | 129 | _handlePanResponderEnd = (e, gestureState) => { 130 | this._previousOffset = this.limit(this._previousOffset + gestureState.dx); 131 | } 132 | 133 | _updateNativeStyles = () => { 134 | this.elPan && this.elPan.setNativeProps(this.elPanStyle); 135 | } 136 | 137 | render() { 138 | const { current, svgWidth } = this.state; 139 | const { c, h, l, o, t, s } = this.state.data; 140 | if (s === undefined) { 141 | return null; 142 | } 143 | const highestPrice = Math.max(...h); 144 | const lowestPrice = Math.min(...l); 145 | const priceScale = this.getLinearScale([lowestPrice, highestPrice], [0, defaultStockChartHeight].reverse()); 146 | 147 | return ( 148 | 149 | CandleStick with PanResponder Overlay 150 | {`時間: ${new Date(t[current] * 1000)}`} 151 | 152 | {`收盤: ${c[current]}`} 153 | {`開盤: ${o[current]}`} 154 | {`最高: ${h[current]}`} 155 | {`最低: ${l[current]}`} 156 | 157 | { this.elPan = elPan; }} 162 | style={[styles.elPan, { transform: [{ translateX: -1 * (svgWidth - deviceWidth) }] }]} 163 | > 164 | 168 | { 169 | t.map((_, i) => { 170 | const item = this.getItemByIndex(i); 171 | const [scaleO, scaleC, yTop, yBottom] = [item.o, item.c, item.h, item.l].map(priceScale); 172 | // deviceWidth divided columns each has (barWidth + 2) width 173 | // leave 1 as the padding on each side 174 | // const x = deviceWidth - i * (barWidth + 2) - barWidth; 175 | const x = svgWidth - barWidth * (i + 1) - barMargin * (2 * i + 1); 176 | const barHeight = Math.max(Math.abs(scaleO - scaleC), 1); // if open === close, make sure chartHigh = 1 177 | 178 | return ( 179 | 182 | 189 | 190 | {current === i && 191 | 192 | } 193 | {current === i && 194 | 195 | } 196 | 197 | ); 198 | }) 199 | } 200 | { 201 | this.state.showGridline && 202 | priceScale.ticks(10).map((p, i) => { 203 | return ( 204 | 205 | 212 | {`${p}`} 213 | 214 | 215 | 216 | ); 217 | }) 218 | } 219 | 220 | 221 | 222 | 223 | toggle grid line 224 | 225 | 226 | load more 227 | 228 | 229 | 230 | ); 231 | } 232 | } 233 | 234 | const styles = StyleSheet.create({ 235 | container: { 236 | flex: 1, 237 | backgroundColor: '#fff' 238 | }, 239 | button: { 240 | borderWidth: 1, 241 | borderColor: '#666', 242 | borderStyle: 'solid', 243 | padding: 10, 244 | marginRight: 10 245 | }, 246 | elPan: { 247 | backgroundColor: '#efefef', 248 | } 249 | }); 250 | 251 | export default CandleStickPanOverlay; 252 | -------------------------------------------------------------------------------- /app/demos/CandleStickScrollView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { TouchableOpacity, View, Text, Dimensions, ScrollView, StyleSheet } from 'react-native'; 3 | 4 | import Svg, { G, Text as SvgText, Rect, Path } from 'react-native-svg'; 5 | 6 | import * as d3Scale from 'd3-scale'; 7 | import T from '../components/T'; 8 | 9 | const deviceWidth = Dimensions.get('window').width; 10 | const defaultStockChartHeight = 200; 11 | const barMargin = 1; // 1 on each side 12 | const barWidth = 5; 13 | 14 | class CandleStickScrollView extends Component { 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | data: {}, 19 | showGridline: false, 20 | scrollEnabled: true 21 | }; 22 | } 23 | 24 | componentDidMount() { 25 | this.getStockQuotes(); 26 | } 27 | 28 | getSvgWidth() { 29 | return deviceWidth * 3; 30 | } 31 | 32 | getStockQuotes = () => { 33 | const d = new Date(); 34 | const today = new Date(`${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`).getTime(); 35 | const from = today + 86400 * 1000; 36 | const to = from - 86400 * 30 * 1000 * 3; 37 | const url = `http://m.cnyes.com/api/v1/charting/history?symbol=tse:2330&from=${Math.floor(from / 1000)}&to=${Math.floor(to / 1000)}&resolution=D`; 38 | fetch(url) 39 | .then(rsp => rsp.json()) 40 | .then(data => { 41 | this.setState({ data, current: 0 }); 42 | }); 43 | } 44 | 45 | getLinearScale(domain, range, isTime = false) { 46 | return (isTime ? d3Scale.scaleTime() : d3Scale.scaleLinear()).domain(domain).range(range); 47 | } 48 | 49 | getItemByIndex = (i) => { 50 | const { c, h, l, o, t, v, s } = this.state.data; 51 | return { 52 | c: c[i], 53 | h: h[i], 54 | l: l[i], 55 | o: o[i], 56 | t: new Date(t[i] * 1000), 57 | v: v[i], 58 | s, 59 | color: o[i] <= c[i] ? 'rgb(210, 72, 62)' : 'rgb(28, 193, 135)' 60 | }; 61 | } 62 | 63 | setCurrentItem = (i) => { 64 | if (i <= this.state.data.o.length - 1) { 65 | this.setState({ current: i }); 66 | } 67 | } 68 | 69 | loadMore = () => { 70 | const { nextTime } = this.state.data; 71 | const to = (nextTime * 1000 - 86400 * 30 * 1000) / 1000; 72 | if (nextTime) { 73 | const url = `http://m.cnyes.com/api/v1/charting/history?symbol=tse:2330&from=${nextTime}&to=${Math.floor(to)}&resolution=D`; 74 | fetch(url) 75 | .then(rsp => rsp.json()) 76 | .then(data => { 77 | const originData = this.state.data; 78 | const newData = { 79 | ...originData, 80 | ...data, 81 | c: [...originData.c, ...data.c], 82 | h: [...originData.h, ...data.h], 83 | l: [...originData.l, ...data.l], 84 | o: [...originData.o, ...data.o], 85 | t: [...originData.t, ...data.t], 86 | v: [...originData.v, ...data.v], 87 | }; 88 | this.setState({ data: newData }); 89 | }); 90 | } 91 | } 92 | 93 | toggleGridline = () => { 94 | this.setState({ showGridline: !this.state.showGridline }); 95 | } 96 | 97 | 98 | handleTouchStart = (e) => { 99 | console.log('handleTouchStart', e.nativeEvent); 100 | const { locationX, locationY, timestamp } = e.nativeEvent; 101 | this._touchStartTime = timestamp; 102 | this._touchStartPosition = { 103 | locationX, locationY 104 | }; 105 | } 106 | 107 | // move less than 1 px 108 | closeEnough(p1, p2) { 109 | if (Math.abs(p1 - p2) < 2) { 110 | return true; 111 | } 112 | return false; 113 | } 114 | 115 | handleTouchMove = (e) => { 116 | console.log('handleTouchMove'); 117 | const { timestamp, locationX, locationY } = e.nativeEvent; 118 | if (this.state.scrollEnabled !== false) { 119 | if (timestamp - this._touchStartTime > 300) { 120 | console.log('check position', e.nativeEvent); 121 | if (this.closeEnough(locationX, this._touchStartPosition.locationX) && 122 | this.closeEnough(locationY, this._touchStartPosition.locationY)) { 123 | console.log('-- lock --'); 124 | this.setState({ scrollEnabled: false }); 125 | } 126 | } 127 | } else { 128 | console.log('this.cross', this.cross, locationX); 129 | this.cross && this.cross.setNativeProps({ style: { left: locationX } }); 130 | } 131 | } 132 | 133 | handleTouchEnd = () => { 134 | console.log('handleTouchEnd'); 135 | if (this.state.scrollEnabled === false) { 136 | this.setState({ scrollEnabled: true }); 137 | } 138 | } 139 | 140 | handleScrollBeginDrag = () => { 141 | console.log('handleScrollBeginDrag'); 142 | } 143 | 144 | handleScrollEndDrag = () => { 145 | console.log('handleScrollEndDrag'); 146 | } 147 | 148 | render() { 149 | console.log('render'); 150 | const svgWidth = this.getSvgWidth(); 151 | const { h, l, t, s } = this.state.data; 152 | if (s === undefined) { 153 | return null; 154 | } 155 | const highestPrice = Math.max(...h); 156 | const lowestPrice = Math.min(...l); 157 | const priceScale = this.getLinearScale([lowestPrice, highestPrice], [0, defaultStockChartHeight].reverse()); 158 | 159 | return ( 160 | 161 | CandleStick with ScrollView 162 | 163 | 181 | 186 | { 187 | t.map((_, i) => { 188 | const item = this.getItemByIndex(i); 189 | const [scaleO, scaleC, yTop, yBottom] = [item.o, item.c, item.h, item.l].map(priceScale); 190 | // deviceWidth divided columns each has (barWidth + 2) width 191 | // leave 1 as the padding on each side 192 | // const x = deviceWidth - i * (barWidth + 2) - barWidth; 193 | const x = svgWidth - barWidth * (i + 1) - barMargin * (2 * i + 1); 194 | const barHeight = Math.max(Math.abs(scaleO - scaleC), 1); // if open === close, make sure chartHigh = 1 195 | return ( 196 | 199 | 206 | 207 | 208 | ); 209 | }) 210 | } 211 | { 212 | this.state.showGridline && 213 | priceScale.ticks(10).map((p, i) => { 214 | return ( 215 | 216 | 223 | {`${p}`} 224 | 225 | 226 | 227 | ); 228 | }) 229 | } 230 | 231 | { this.cross = cross; }} 233 | style={styles.cross} 234 | /> 235 | 236 | 237 | 238 | 239 | toggle grid line 240 | 241 | 242 | load more 243 | 244 | 245 | 246 | ); 247 | } 248 | } 249 | 250 | const styles = StyleSheet.create({ 251 | container: { 252 | flex: 1, 253 | backgroundColor: '#fff' 254 | }, 255 | cross: { 256 | position: 'absolute', 257 | top: 0 , 258 | left: 0, 259 | backgroundColor: '#999', 260 | width: 2, 261 | height: defaultStockChartHeight 262 | }, 263 | button: { 264 | borderWidth: 1, 265 | borderColor: '#666', 266 | borderStyle: 'solid', 267 | padding: 10, 268 | marginRight: 10 269 | }, 270 | }); 271 | 272 | export default CandleStickScrollView; 273 | -------------------------------------------------------------------------------- /app/demos/CandleStickWithPan.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { PanResponder, TouchableOpacity, View, Text, Dimensions, ScrollView, StyleSheet } from 'react-native'; 3 | 4 | import Svg, { G, Text as SvgText, Rect, Path } from 'react-native-svg'; 5 | import StaticContainer from 'react-static-container'; 6 | 7 | import * as d3Scale from 'd3-scale'; 8 | 9 | import T from '../components/T'; 10 | 11 | 12 | const deviceWidth = Dimensions.get('window').width; 13 | const barMargin = 1; // 1 on each side 14 | const defaultStockChartHeight = 200; 15 | const barWidth = 5; 16 | 17 | 18 | class CandleStickWithPan extends Component { 19 | constructor(props) { 20 | super(props); 21 | this.state = { 22 | data: {}, 23 | showGridline: false, 24 | offset: 0, 25 | dragging: false 26 | }; 27 | this._previousOffset = 0; 28 | } 29 | 30 | componentWillMount() { 31 | this._panResponder = PanResponder.create({ 32 | onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder, 33 | onMoveShouldSetPanResponder: this._alwaysTrue, 34 | onPanResponderGrant: this._alwaysTrue, 35 | onPanResponderMove: this._handlePanResponderMove, 36 | onPanResponderRelease: this._handlePanResponderEnd, 37 | onPanResponderTerminate: this._handlePanResponderEnd 38 | }); 39 | } 40 | 41 | 42 | componentDidMount() { 43 | this.getStockQuotes(); 44 | } 45 | 46 | getSvgWidth() { 47 | return deviceWidth * 3; 48 | } 49 | 50 | setCurrentItem = (i) => { 51 | if (i <= this.state.data.o.length - 1) { 52 | this.setState({ current: i }); 53 | } 54 | } 55 | 56 | getItemByIndex = (i) => { 57 | const { c, h, l, o, t, v, s } = this.state.data; 58 | return { 59 | c: c[i], 60 | h: h[i], 61 | l: l[i], 62 | o: o[i], 63 | t: new Date(t[i] * 1000), 64 | v: v[i], 65 | s, 66 | color: o[i] <= c[i] ? 'rgb(210, 72, 62)' : 'rgb(28, 193, 135)' 67 | }; 68 | } 69 | 70 | getStockQuotes = () => { 71 | const d = new Date(); 72 | const today = new Date(`${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}`).getTime(); 73 | const from = today + 86400 * 1000; 74 | const to = from - 86400 * 30 * 1000; 75 | const url = `http://m.cnyes.com/api/v1/charting/history?symbol=tse:2330&from=${Math.floor(from / 1000)}&to=${Math.floor(to / 1000)}&resolution=D`; 76 | fetch(url) 77 | .then(rsp => rsp.json()) 78 | .then(data => { 79 | this.setState({ data, current: 0 }); 80 | }); 81 | } 82 | 83 | getLinearScale(domain, range, isTime = false) { 84 | return (isTime ? d3Scale.scaleTime() : d3Scale.scaleLinear()).domain(domain).range(range); 85 | } 86 | 87 | loadMore = () => { 88 | const { nextTime } = this.state.data; 89 | const to = (nextTime * 1000 - 86400 * 30 * 1000) / 1000; 90 | if (nextTime) { 91 | const url = `http://m.cnyes.com/api/v1/charting/history?symbol=tse:2330&from=${nextTime}&to=${Math.floor(to)}&resolution=D`; 92 | fetch(url) 93 | .then(rsp => rsp.json()) 94 | .then(data => { 95 | const originData = this.state.data; 96 | const newData = { 97 | ...originData, 98 | ...data, 99 | c: [...originData.c, ...data.c], 100 | h: [...originData.h, ...data.h], 101 | l: [...originData.l, ...data.l], 102 | o: [...originData.o, ...data.o], 103 | t: [...originData.t, ...data.t], 104 | v: [...originData.v, ...data.v], 105 | }; 106 | this.setState({ data: newData }); 107 | }); 108 | } 109 | } 110 | 111 | _handlePanResponderMove = (e, gestureState) => { 112 | const { dx } = gestureState; 113 | const newOffset = Math.max(this._previousOffset + dx, 0); 114 | if (newOffset !== this.state.offset) { 115 | this.setState({ offset: newOffset }); 116 | } 117 | } 118 | 119 | // show the cross line 120 | _handleStartShouldSetPanResponder = (e, gestureState) => { 121 | const { locationX } = e.nativeEvent; 122 | const current = Math.floor((deviceWidth - locationX + this.state.offset) / (barWidth + 2 * barMargin)); 123 | this.setCurrentItem(current); 124 | this.setState({ dragging: true }); 125 | return true; 126 | } 127 | 128 | _handlePanResponderEnd = (e, gestureState) => { 129 | this._previousOffset = Math.max(this._previousOffset + gestureState.dx, 0); 130 | this.setState({ dragging: false }); 131 | } 132 | 133 | _alwaysTrue = () => true 134 | 135 | toggleGridline = () => { 136 | this.setState({ showGridline: !this.state.showGridline }); 137 | } 138 | 139 | render() { 140 | const { current, offset } = this.state; 141 | const { c, h, l, o, t, s } = this.state.data; 142 | if (s === undefined) { 143 | return null; 144 | } 145 | const highestPrice = Math.max(...h); 146 | const lowestPrice = Math.min(...l); 147 | const priceScale = this.getLinearScale([lowestPrice, highestPrice], [0, defaultStockChartHeight].reverse()); 148 | // const svgWidth = Math.max(deviceWidth, c.length * (barWidth + 2 * barMargin)); 149 | const svgWidth = this.getSvgWidth(); 150 | 151 | return ( 152 | 153 | CandleStick chart 154 | {`時間: ${new Date(t[current] * 1000)}`} 155 | 156 | {`收盤: ${c[current]}`} 157 | {`開盤: ${o[current]}`} 158 | {`最高: ${h[current]}`} 159 | {`最低: ${l[current]}`} 160 | 161 | 167 | 168 | 169 | 170 | 171 | { 172 | !this.state.dragging && 173 | t.map((_, i) => { 174 | const item = this.getItemByIndex(i); 175 | const [scaleO, scaleC, yTop, yBottom] = [item.o, item.c, item.h, item.l].map(priceScale); 176 | // deviceWidth divided columns each has (barWidth + 2) width 177 | // leave 1 as the padding on each side 178 | // const x = deviceWidth - i * (barWidth + 2) - barWidth; 179 | const x = deviceWidth - barWidth * (i + 1) - barMargin * (2 * i + 1); 180 | const barHeight = Math.max(Math.abs(scaleO - scaleC), 1); // if open === close, make sure chartHigh = 1 181 | return ( 182 | 185 | 192 | 193 | {current === i && 194 | } 195 | {current === i && 196 | 197 | } 198 | 199 | ); 200 | }) 201 | } 202 | { 203 | this.state.showGridline && 204 | priceScale.ticks(10).map((p, i) => { 205 | return ( 206 | 207 | 214 | {`${p}`} 215 | 216 | 217 | 218 | ); 219 | }) 220 | } 221 | 222 | 223 | 224 | 225 | 226 | 227 | toggle grid line 228 | 229 | 230 | load more 231 | 232 | 233 | 234 | ); 235 | } 236 | } 237 | 238 | const styles = StyleSheet.create({ 239 | container: { 240 | flex: 1, 241 | backgroundColor: '#fff' 242 | }, 243 | button: { 244 | borderWidth: 1, 245 | borderColor: '#666', 246 | borderStyle: 'solid', 247 | padding: 10, 248 | marginRight: 10 249 | } 250 | }); 251 | 252 | export default CandleStickWithPan; 253 | -------------------------------------------------------------------------------- /app/demos/ChartWithAxis.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { InteractionManager, ScrollView, StyleSheet } from 'react-native'; 3 | import { 4 | VictoryAxis, 5 | VictoryLine, 6 | VictoryChart 7 | } from 'victory-chart-native'; 8 | 9 | import T from '../components/T'; 10 | import Loading from '../components/Loading'; 11 | import data from '../data'; 12 | 13 | class ChartWithAxis extends Component { 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | isReady: false 18 | }; 19 | } 20 | componentDidMount() { 21 | InteractionManager.runAfterInteractions(() => { 22 | this.setState({ isReady: true }); 23 | }); 24 | } 25 | render() { 26 | const { ticks, tradingHours, highestPrice, lowestPrice, previousClose } = data; 27 | if (!this.state.isReady) { 28 | return ; 29 | } 30 | return ( 31 | 32 | Line Chart with Axis 33 | VictoryChart will calcualte proper scale with axis for you 34 | 35 | d.time} 38 | y={'price'} 39 | /> 40 | 41 | 42 | add scale: "time" to x-axis, VictoryChart will display proper time format 43 | 48 | d.time * 1000} 51 | y={'price'} 52 | /> 53 | 54 | 55 | it turns out this is only partial data, add domain to the x-axis to make the chart only occupy part of the chart 56 | t * 1000) 62 | }} 63 | > 64 | d.time * 1000} 67 | y={'price'} 68 | /> 69 | 70 | 71 | add some padding at the bottom of the chart 72 | t * 1000) 81 | }} 82 | > 83 | d.time * 1000} 86 | y={'price'} 87 | /> 88 | 89 | 90 | add grid lines 91 | t * 1000) 100 | }} 101 | > 102 | 103 | 114 | d.time * 1000} 117 | y={'price'} 118 | /> 119 | 120 | 121 | add grid line to show previous close price 122 | t * 1000) 131 | }} 132 | > 133 | 134 | 145 | 146 | new Date(d.time * 1000)} 149 | y={'price'} 150 | /> 151 | 157 | 158 | 159 | somehow the domain of y-aix is wrongly calculated, let's calculate it manually 160 | t * 1000), 169 | y: [lowestPrice, highestPrice] 170 | }} 171 | > 172 | 173 | 184 | 185 | new Date(d.time * 1000)} 188 | y={'price'} 189 | /> 190 | 196 | 197 | 198 | ); 199 | } 200 | } 201 | 202 | const styles = StyleSheet.create({ 203 | container: { 204 | flex: 1, 205 | backgroundColor: '#fff' 206 | }, 207 | }); 208 | 209 | export default ChartWithAxis; 210 | -------------------------------------------------------------------------------- /app/demos/CustomStockChart.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { ScrollView, StyleSheet, Dimensions } from 'react-native'; 3 | import Svg, { Rect, G, Path, Text } from 'react-native-svg'; 4 | 5 | import * as d3Shape from 'd3-shape'; 6 | import * as d3Scale from 'd3-scale'; 7 | import * as d3TimeFormat from 'd3-time-format'; 8 | 9 | import data from '../data'; 10 | 11 | const defaultWidth = Dimensions.get('window').width; 12 | const defaultStockChartHeight = 200; 13 | const defaultVolumeChartHeight = 80; 14 | const bottomAxisHeight = 20; 15 | const priceTickCounts = 3; 16 | 17 | class CustomStockChart extends Component { 18 | constructor(props) { 19 | super(props); 20 | const { ticks, lowestPrice, highestPrice, tradingHours } = data; 21 | this.timeScale = 22 | d3Scale 23 | .scaleTime() 24 | .domain(tradingHours.map( t => t * 1000)) 25 | .range([0, defaultWidth]); 26 | this.priceScale = 27 | d3Scale 28 | .scaleLinear() 29 | .domain([lowestPrice, highestPrice]) 30 | // reverse bacause the origin point of d3 svg is top left 31 | .range([0, defaultStockChartHeight].reverse()); 32 | this.volumes = ticks.map(t => t.volume); 33 | this.volumeScale = 34 | d3Scale 35 | .scaleLinear() 36 | .domain([Math.min(...this.volumes), Math.max(...this.volumes)]) 37 | .range([0, defaultVolumeChartHeight]); 38 | const chartPercent = (ticks[ticks.length - 1].time - tradingHours[0]) / (tradingHours[1] - tradingHours[0]); 39 | this.barWidth = chartPercent * defaultWidth / this.volumes.length; 40 | } 41 | 42 | getStockPath() { 43 | const { ticks, } = data; 44 | const lineFunction = 45 | d3Shape 46 | .line() 47 | .x(d => this.timeScale(d.time * 1000)) 48 | .y(d => this.priceScale(d.price)); 49 | 50 | return lineFunction(ticks); 51 | } 52 | 53 | getStockArea() { 54 | const { ticks, lowestPrice } = data; 55 | const areaFunction = 56 | d3Shape 57 | .area() 58 | .x(d => this.timeScale(d.time * 1000)) 59 | .y(d => this.priceScale(d.price)) 60 | .y1(() => this.priceScale(lowestPrice)); 61 | return areaFunction(ticks); 62 | } 63 | 64 | 65 | getPriceTickValues() { 66 | const { tradingHours, previousClose } = data; 67 | const yTicks = this.priceScale.ticks(priceTickCounts); 68 | const timeScaled = tradingHours.map( t => this.timeScale(t * 1000)); 69 | const roundDecimal = this.roundDecimal; 70 | return yTicks.map(tick => { 71 | const priceScaled = this.priceScale(tick); 72 | return { 73 | x1: timeScaled[0], 74 | y1: priceScaled, 75 | x2: timeScaled[1], 76 | y2: priceScaled, 77 | tick: String(tick), 78 | percent: roundDecimal((tick - previousClose) * 100 / previousClose) 79 | }; 80 | }); 81 | } 82 | 83 | getTimeTickValues() { 84 | const timeTicks = this.timeScale.ticks(3); 85 | const timePositions = timeTicks.map(t => this.timeScale(t.valueOf())); 86 | const formatTime = d3TimeFormat.timeFormat('%H:%M'); 87 | return timeTicks.map( (t, index) => { 88 | return { 89 | x: timePositions[index], 90 | y: defaultVolumeChartHeight, 91 | time: formatTime(timeTicks[index]) 92 | }; 93 | }); 94 | } 95 | 96 | getTickPath(values) { 97 | return values.reduce((prev, current) => { 98 | return `${prev} M${current.x1} ${current.y1} ${current.x2} ${current.y2}`; 99 | }, ''); 100 | } 101 | 102 | roundDecimal(n, decimal = 2) { 103 | return Math.round(n * 100) / 100; 104 | } 105 | 106 | render() { 107 | const values = this.getPriceTickValues(); 108 | const { ticks } = data; 109 | 110 | return ( 111 | 112 | 113 | 114 | 115 | 116 | 117 | { 118 | // draw tickLabels 119 | values.map((value, index) => ( 120 | 121 | {value.tick} 122 | {`${value.percent}%`} 123 | 124 | )) 125 | } 126 | 127 | 128 | { // draw volume bars 129 | ticks.map( (tick, index) => { 130 | const volume = tick.volume; 131 | const width = this.barWidth; 132 | const height = this.volumeScale(volume); 133 | return ( 134 | 142 | ); 143 | }) 144 | } 145 | 149 | { 150 | this.getTimeTickValues() 151 | .map((p, index) => 152 | 153 | {p.time} 154 | 155 | 156 | ) 157 | } 158 | 159 | 160 | 161 | ); 162 | } 163 | } 164 | 165 | const styles = StyleSheet.create({ 166 | container: { 167 | flex: 1, 168 | backgroundColor: '#fff' 169 | }, 170 | }); 171 | 172 | export default CustomStockChart; 173 | -------------------------------------------------------------------------------- /app/demos/D3Scale.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Dimensions, ScrollView, StyleSheet } from 'react-native'; 3 | 4 | import Svg, { Circle, Path } from 'react-native-svg'; 5 | 6 | import * as d3Scale from 'd3-scale'; 7 | 8 | import T from '../components/T'; 9 | import Code from '../components/Code'; 10 | 11 | const deviceWidth = Dimensions.get('window').width; 12 | const defaultStockChartHeight = 200; 13 | 14 | class D3Scale extends Component { 15 | render() { 16 | const scalePrice = d3Scale.scaleLinear().domain([8200, 8300]).range([0, 200]); 17 | const scalePriceCorrect = d3Scale.scaleLinear().domain([8200, 8300]).range([0, 200].reverse()); 18 | return ( 19 | 20 | Domain(定義域)/Range(值域) 21 | {`Given: 22 | - x axis: time 23 | - y axis: stock price 24 | - lowest Price: 8200 25 | - highest Price: 8300`} 26 | 27 | put 8210, 8254, 8276 on the chart (height: 200) 28 | domain: [8200, 8300], range: [0, 200] 29 | {` scalePrice(8210): ${scalePrice(8210)} 30 | scalePrice(8254): ${scalePrice(8254)} 31 | scalePrice(8276): ${scalePrice(8276)} 32 | `} 33 | 34 | 35 | d3Scale 36 | 37 | {` 38 | import * as D3Scale from 'd3-scale'; 39 | 40 | const scalePrice = 41 | d3Scale 42 | .scaleLinear() 43 | .domain([8200, 8300]) 44 | .range([0, 200]); 45 | `} 46 | 47 | use scale function 48 | 49 | 50 | 51 | 52 | 53 | 54 | {` 55 | 56 | 57 | 58 | `} 59 | 60 | 61 | The original point of the SVG is at the top left corner 62 | 63 | 64 | 65 | 66 | 67 | 68 | {` 69 | import * as D3Scale from 'd3-scale'; 70 | 71 | const scalePriceCorrect = 72 | d3Scale 73 | .scaleLinear() 74 | .domain([8200, 8300]) 75 | .range([0, 200].reverse()); 76 | `} 77 | 78 | 79 | draw by Path 80 | 81 | 82 | 83 | 84 | {` 85 | 86 | `} 87 | 88 | 89 | 90 | ); 91 | } 92 | } 93 | 94 | const styles = StyleSheet.create({ 95 | container: { 96 | flex: 1, 97 | backgroundColor: '#fff', 98 | }, 99 | background: { 100 | backgroundColor: 'azure', 101 | } 102 | }); 103 | 104 | export default D3Scale; 105 | -------------------------------------------------------------------------------- /app/demos/D3Shape.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Dimensions, ScrollView, StyleSheet } from 'react-native'; 3 | 4 | import Svg, { Path } from 'react-native-svg'; 5 | 6 | import * as d3Shape from 'd3-shape'; 7 | import * as d3Scale from 'd3-scale'; 8 | 9 | import data from '../data'; 10 | import T from '../components/T'; 11 | import Code from '../components/Code'; 12 | 13 | const deviceWidth = Dimensions.get('window').width; 14 | const defaultStockChartHeight = 200; 15 | 16 | class D3Shape extends Component { 17 | render() { 18 | const { ticks, lowestPrice, highestPrice, tradingHours } = data; 19 | const timeScale = 20 | d3Scale 21 | .scaleTime() 22 | .domain(tradingHours.map( t => t * 1000)) 23 | .range([0, deviceWidth]); 24 | const priceScale = 25 | d3Scale 26 | .scaleLinear() 27 | .domain([lowestPrice, highestPrice]) 28 | .range([0, defaultStockChartHeight].reverse()); 29 | const lineFunction = 30 | d3Shape 31 | .line() 32 | .x(d => timeScale(d.time * 1000)) 33 | .y(d => priceScale(d.price)); 34 | 35 | const areaFunction = 36 | d3Shape 37 | .area() 38 | .x(d => timeScale(d.time * 1000)) 39 | .y(d => priceScale(d.price)) 40 | .y1(() => priceScale(lowestPrice)); 41 | 42 | return ( 43 | 44 | - To draw more complicated chart, you will need d3Shape 45 | - Arcs/Pie/Lines/Areas/Curves/Stacks, etc 46 | Example: 47 | 48 | {` 49 | import * as d3Shape from 'd3-shape'; 50 | 51 | var arc = d3Shape.arc() 52 | .innerRadius(0) 53 | .outerRadius(100) 54 | .startAngle(0) 55 | .endAngle(Math.PI / 2); 56 | 57 | arc(); 58 | // "M0,-100A100,100,0,0,1,100,0L0,0Z" 59 | `} 60 | 61 | - For stock chart, only needed D3Shape.line()/D3Shape.area() function 62 | - preparing two scale functions: timeScale/priceScale 63 | timeScale: 64 | 65 | {` 66 | const timeScale = 67 | d3Scale 68 | .scaleTime() 69 | .domain(tradingHours.map( t => t * 1000)) 70 | .range([0, deviceWidth]); 71 | `} 72 | 73 | priceScale: 74 | 75 | {` 76 | const priceScale = 77 | d3Scale 78 | .scaleLinear() 79 | .domain([lowestPrice, highestPrice]) 80 | .range([0, defaultStockChartHeight].reverse()); 81 | `} 82 | 83 | define a line generator for a time series by scaling fields of your data to fit the chart 84 | 85 | {` 86 | const lineFunction = 87 | d3Shape 88 | .line() 89 | .x(d => timeScale(d.time * 1000)) 90 | .y(d => priceScale(d.price)); 91 | 92 | // ticks: array of {time: ..., price: ...} 93 | lineFunction(ticks); 94 | // M0,44.98527968596417L1.2685281997662834,117.52698724239188L2.652377144965865,153.13052011775952L4.036226090165447,166.7124631992159L5.420075035365028,173.54268891069705L6.80392398056461,182.72816486751822L8.187772925764191,... 95 | `} 96 | 97 | 98 | 99 | 100 | 101 | {` 102 | 103 | 104 | 105 | `} 106 | 107 | define a area generator for a time series by scaling fields of your data to fit the chart 108 | 109 | {` 110 | const areaFunction = 111 | d3Shape 112 | .area() 113 | .x(d => timeScale(d.time * 1000)) 114 | .y(d => priceScale(d.price)) 115 | .y1(() => priceScale(lowestPrice)); 116 | `} 117 | 118 | 其中 y1 用來決定如何圍成一個 area 119 | 120 | 121 | 122 | 123 | {` 124 | 125 | 126 | 127 | `} 128 | 129 | 放在同一張圖上 130 | 131 | 132 | 133 | 134 | 135 | {` 136 | 137 | 138 | 139 | 140 | `} 141 | 142 | 143 | ); 144 | } 145 | } 146 | 147 | const styles = StyleSheet.create({ 148 | container: { 149 | flex: 1, 150 | backgroundColor: '#fff' 151 | }, 152 | }); 153 | 154 | export default D3Shape; 155 | -------------------------------------------------------------------------------- /app/demos/D3Ticks.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Image, Dimensions, ScrollView, StyleSheet } from 'react-native'; 3 | 4 | import T from '../components/T'; 5 | import Code from '../components/Code'; 6 | import Svg, { Rect, G, Path, Text } from 'react-native-svg'; 7 | 8 | import data from '../data'; 9 | import * as d3Shape from 'd3-shape'; 10 | import * as d3Scale from 'd3-scale'; 11 | import * as d3Array from 'd3-array'; 12 | 13 | const deviceWidth = Dimensions.get('window').width; 14 | const defaultStockChartHeight = 200; 15 | const bottomAxisHeight = 20; 16 | 17 | class D3Ticks extends Component { 18 | 19 | getExclusiveTicks(start, end, count) { 20 | const ticks = d3Array.ticks(start, end, count); 21 | if (ticks.length <= 1) { 22 | return ticks; 23 | } 24 | const interval = ticks[1] - ticks[0]; 25 | if (ticks[0] - interval <= 0) { 26 | return [...ticks, ticks[ticks.length - 1] + interval]; 27 | } 28 | return [ticks[0] - interval, ...ticks, ticks[ticks.length - 1] + interval]; 29 | } 30 | 31 | formatTime(t) { 32 | const paddingZero = (n) => { 33 | if (n < 10) { 34 | return `0${n}`; 35 | } 36 | return n; 37 | }; 38 | 39 | return `${paddingZero(t.getHours())}:${paddingZero(t.getMinutes())}`; 40 | } 41 | 42 | render() { 43 | const { ticks, lowestPrice, highestPrice, tradingHours } = data; 44 | const tickCounts = 3; 45 | const timeScale = 46 | d3Scale 47 | .scaleTime() 48 | .domain(tradingHours.map( t => t * 1000)) 49 | .range([0, deviceWidth]); 50 | const priceScale = 51 | d3Scale 52 | .scaleLinear() 53 | .domain([lowestPrice, highestPrice]) 54 | .range([0, defaultStockChartHeight].reverse()); 55 | const lineFunction = 56 | d3Shape 57 | .line() 58 | .x(d => timeScale(d.time * 1000)) 59 | .y(d => priceScale(d.price)); 60 | const areaFunction = 61 | d3Shape 62 | .area() 63 | .x(d => timeScale(d.time * 1000)) 64 | .y(d => priceScale(d.price)) 65 | .y1(() => priceScale(lowestPrice)); 66 | const priceTicks = d3Array.ticks(lowestPrice, highestPrice, tickCounts); 67 | 68 | 69 | const adjustPriceTicks = this.getExclusiveTicks(lowestPrice, highestPrice, tickCounts); 70 | const adjustPriceScale = 71 | d3Scale 72 | .scaleLinear() 73 | .domain([adjustPriceTicks[0], adjustPriceTicks[adjustPriceTicks.length - 1]]) 74 | .range([0, defaultStockChartHeight].reverse()); 75 | const timeTicks = timeScale.ticks(tickCounts); 76 | 77 | return ( 78 | 79 | Challenge 80 | 1. ticks should be beatuiful number (multiply by 2, 5, 10) 81 | 2. the interval of grid line should be equally distributed 82 | 83 | 84 | 85 | 86 | d3Array.ticks(start, end, count) 87 | Returns an array of approximately count + 1 uniformly-spaced, nicely-rounded values between start and stop (inclusive). Each value is a power of ten multiplied by 1, 2 or 5. See also tickStep and linear.ticks. 88 | Ticks are inclusive in the sense that they may include the specified start and stop values if (and only if) they are exact, nicely-rounded values consistent with the inferred step. More formally, each returned tick t satisfies start ≤ t and t ≤ stop. 89 | 90 | highest price: {lowestPrice}, lowest price: {highestPrice}, generating {tickCounts} points: {`[${priceTicks.join(', ')}]`} 91 | 92 | 93 | {` 94 | import * as d3Array from 'd3-array'; 95 | 96 | const priceTicks = d3Array.ticks( 97 | lowestPrice, 98 | highestPrice, 99 | tickCounts 100 | ); 101 | `} 102 | 103 | 104 | 105 | 106 | { 107 | priceTicks.map(t => { 108 | return ( 109 | 110 | 117 | {`${t}`} 118 | 119 | 120 | 121 | ); 122 | }) 123 | } 124 | 125 | 126 | {` 127 | { 128 | priceTicks.map(t => { 129 | return ( 130 | 131 | 137 | {\`\${t}\`} 138 | 139 | 144 | 145 | ); 146 | }) 147 | } 148 | `} 149 | 150 | exclusivePriceTicks 151 | 152 | 153 | 154 | { 155 | adjustPriceTicks.map(t => { 156 | return ( 157 | 158 | 165 | {`${t}`} 166 | 167 | 168 | 169 | ); 170 | }) 171 | } 172 | 173 | Calculate timeScale and timeTicks 174 | 175 | 176 | 177 | { 178 | adjustPriceTicks.map(t => { 179 | return ( 180 | 181 | 188 | {`${t}`} 189 | 190 | 191 | 192 | ); 193 | }) 194 | } 195 | 196 | { 197 | timeTicks.map(t => { 198 | return ( 199 | 200 | 207 | {`${this.formatTime(t)}`} 208 | 209 | 210 | 211 | ); 212 | }) 213 | } 214 | 215 | 216 | 217 | {` 218 | 219 | const timeTicks = timeScale.ticks(tickCounts); 220 | 221 | { 222 | timeTicks.map(t => { 223 | return ( 224 | 225 | 232 | {\`\${this.formatTime(t)}\`} 233 | 234 | 235 | 236 | ); 237 | }) 238 | } 239 | `} 240 | 241 | 242 | ); 243 | } 244 | } 245 | 246 | const styles = StyleSheet.create({ 247 | container: { 248 | flex: 1, 249 | backgroundColor: '#fff', 250 | }, 251 | }); 252 | 253 | export default D3Ticks; 254 | -------------------------------------------------------------------------------- /app/demos/MultipleAxes.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { InteractionManager, ScrollView, StyleSheet, Dimensions } from 'react-native'; 3 | import { 4 | VictoryArea, 5 | VictoryAxis, 6 | VictoryLine, 7 | } from 'victory-chart-native'; 8 | import Svg from 'react-native-svg'; 9 | 10 | import Loading from '../components/Loading'; 11 | import T from '../components/T'; 12 | import data from '../data'; 13 | import * as util from '../util'; 14 | 15 | const defaultHeight = 300; 16 | const defaultWidth = Dimensions.get('window').width; 17 | 18 | class MultipleAxes extends Component { 19 | constructor(props) { 20 | super(props); 21 | this.state = { 22 | isReady: false 23 | }; 24 | } 25 | componentDidMount() { 26 | InteractionManager.runAfterInteractions(() => { 27 | this.setState({ isReady: true }); 28 | }); 29 | } 30 | render() { 31 | const { ticks, tradingHours, lowestPrice, highestPrice, previousClose } = data; 32 | if (!this.state.isReady) { 33 | return ; 34 | } 35 | return ( 36 | 37 | Line chart with multiple Axes 38 | let's add another y-axis to display percentage change, if you try to put more than one y-axis in a VictoryChart , it will cause an error, you have to wrap two y-axis in a Svg element from 'react-native-svg' and calculate the domain manually 39 | make sure you set standalone: false or it will crash 40 | let's begin with the very basic chart with 2 y-axes 41 | 42 | 45 | 49 | 54 | 55 | 56 | Let's draw the price line here 57 | 58 | 61 | 65 | 70 | new Date(d.time * 1000)} 74 | y={'price'} 75 | /> 76 | 77 | 78 | again, we have to set correct domain/scale for VictoryAxis/VictoryLine 79 | 80 | t * 1000)} 84 | /> 85 | 90 | 96 | t * 1000) 101 | }} 102 | x={(d) => new Date(d.time * 1000)} 103 | y={'price'} 104 | /> 105 | 106 | 107 | tweak the tickFormat of the right y-axis to display percentage 108 | 109 | t * 1000)} 113 | /> 114 | 119 | util.tickFormatPercent(t, previousClose)} 125 | /> 126 | t * 1000) 131 | }} 132 | x={(d) => new Date(d.time * 1000)} 133 | y={'price'} 134 | /> 135 | 136 | 137 | add previous close line/grid line back 138 | 139 | t * 1000)} 143 | /> 144 | 157 | util.tickFormatPercent(t, previousClose)} 163 | style={{ 164 | grid: { 165 | stroke: '#ddd', 166 | strokeWidth: 1 167 | }, 168 | axis: { stroke: 'transparent' }, 169 | ticks: { stroke: 'transparent' } 170 | }} 171 | /> 172 | t * 1000) 177 | }} 178 | x={(d) => new Date(d.time * 1000)} 179 | y={'price'} 180 | /> 181 | 197 | 198 | 199 | use AxisArea 200 | 201 | t * 1000)} 205 | style={{ 206 | axis: { strokeWidth: 1, stroke: '#ddd' }, 207 | ticks: { strokeWidth: 1, stroke: '#ddd' } 208 | }} 209 | /> 210 | 223 | util.tickFormatPercent(t, previousClose)} 229 | style={{ 230 | grid: { 231 | stroke: '#ddd', 232 | strokeWidth: 1 233 | }, 234 | axis: { stroke: 'transparent' }, 235 | ticks: { stroke: 'transparent' } 236 | }} 237 | /> 238 | t * 1000), 243 | y: [lowestPrice, highestPrice] 244 | }} 245 | x={(d) => new Date(d.time * 1000)} 246 | y={'price'} 247 | style={{ 248 | data: { 249 | stroke: 'rgba(0, 102, 221, 0.75)', 250 | fill: 'rgba(237, 247, 255, 0.75)', 251 | } 252 | }} 253 | /> 254 | 270 | 271 | 272 | ); 273 | } 274 | } 275 | 276 | const styles = StyleSheet.create({ 277 | container: { 278 | flex: 1, 279 | backgroundColor: '#fff' 280 | }, 281 | }); 282 | 283 | export default MultipleAxes; 284 | -------------------------------------------------------------------------------- /app/demos/PanResponderDemo.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { PanResponder, View, StyleSheet } from 'react-native'; 3 | 4 | class PanResponderDemo extends Component { 5 | 6 | componentWillMount() { 7 | this._panResponder = PanResponder.create({ 8 | onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder, 9 | onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder, 10 | onPanResponderGrant: this._handlePanResponderGrant, 11 | onPanResponderMove: this._handlePanResponderMove, 12 | onPanResponderRelease: this._handlePanResponderEnd, 13 | onPanResponderTerminate: this._handlePanResponderEnd, 14 | }); 15 | this._previousLeft = 20; 16 | this._previousTop = 84; 17 | this._circleStyles = { 18 | style: { 19 | left: this._previousLeft, 20 | top: this._previousTop, 21 | backgroundColor: 'green', 22 | } 23 | }; 24 | } 25 | 26 | componentDidMount() { 27 | this._updateNativeStyles(); 28 | } 29 | 30 | _previousLeft: 0 31 | _previousTop: 0 32 | 33 | _handleStartShouldSetPanResponder() { 34 | // Should we become active when the user presses down on the circle? 35 | return true; 36 | } 37 | 38 | _handleMoveShouldSetPanResponder() { 39 | // Should we become active when the user moves a touch over the circle? 40 | return true; 41 | } 42 | 43 | _handlePanResponderGrant = () => { 44 | this._highlight(); 45 | } 46 | _handlePanResponderMove = (e, gestureState) => { 47 | this._circleStyles.style.left = this._previousLeft + gestureState.dx; 48 | this._circleStyles.style.top = this._previousTop + gestureState.dy; 49 | this._updateNativeStyles(); 50 | } 51 | _handlePanResponderEnd = (e, gestureState) => { 52 | this._unHighlight(); 53 | this._previousLeft += gestureState.dx; 54 | this._previousTop += gestureState.dy; 55 | } 56 | 57 | _highlight = () => { 58 | this._circleStyles.style.backgroundColor = 'blue'; 59 | this._updateNativeStyles(); 60 | } 61 | 62 | _unHighlight = () => { 63 | this._circleStyles.style.backgroundColor = 'green'; 64 | this._updateNativeStyles(); 65 | } 66 | 67 | _updateNativeStyles = () => { 68 | this.circle && this.circle.setNativeProps(this._circleStyles); 69 | } 70 | 71 | render() { 72 | return ( 73 | 74 | { this.circle = circle; }} 77 | {...this._panResponder.panHandlers} 78 | /> 79 | 80 | ); 81 | } 82 | } 83 | 84 | const styles = StyleSheet.create({ 85 | container: { 86 | flex: 1, 87 | backgroundColor: '#fff' 88 | }, 89 | button: { 90 | borderWidth: 1, 91 | borderColor: '#666', 92 | borderStyle: 'solid', 93 | padding: 10, 94 | marginRight: 10 95 | }, 96 | circle: { 97 | height: 80, 98 | width: 80, 99 | borderRadius: 40, 100 | position: 'absolute', 101 | left: 0, 102 | top: 0 103 | } 104 | }); 105 | 106 | export default PanResponderDemo; 107 | -------------------------------------------------------------------------------- /app/demos/PanResponderOverlay.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { PanResponder, View, Dimensions, ScrollView, StyleSheet } from 'react-native'; 3 | 4 | import Svg, { G, Text as SvgText, Rect } from 'react-native-svg'; 5 | 6 | import * as d3Array from 'd3-array'; 7 | 8 | import T from '../components/T'; 9 | 10 | const deviceWidth = Dimensions.get('window').width; 11 | const defaultStockChartHeight = 200; 12 | 13 | class PanResponderOverlay extends Component { 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | data: {}, 18 | showGridline: false, 19 | scrollEnabled: true 20 | }; 21 | this._previousOffset = 0; 22 | this.elPanStyle = { 23 | style: { 24 | transform: [{ translateX: this._previousOffset }] 25 | } 26 | }; 27 | } 28 | 29 | componentWillMount() { 30 | this._panResponder = PanResponder.create({ 31 | onStartShouldSetPanResponder: this._alwaysTrue, 32 | onMoveShouldSetPanResponder: this._alwaysTrue, 33 | onPanResponderGrant: this._alwaysTrue, 34 | onPanResponderMove: this._handlePanResponderMove, 35 | onPanResponderRelease: this._handlePanResponderEnd, 36 | onPanResponderTerminate: this._handlePanResponderEnd 37 | }); 38 | } 39 | 40 | componentDidMount() { 41 | this._updateNativeStyles(); 42 | } 43 | 44 | getSvgWidth() { 45 | return deviceWidth * 3; 46 | } 47 | 48 | _alwaysTrue = () => true 49 | 50 | _handlePanResponderMove = (e, gestureState) => { 51 | console.log('move'); 52 | this.elPanStyle.style.transform[0].translateX = this._previousOffset + gestureState.dx; 53 | this._updateNativeStyles(); 54 | } 55 | 56 | _handlePanResponderEnd = (e, gestureState) => { 57 | console.log('end'); 58 | this._previousOffset += gestureState.dx; 59 | } 60 | 61 | _updateNativeStyles = () => { 62 | this.elPan && this.elPan.setNativeProps(this.elPanStyle); 63 | } 64 | 65 | 66 | handleTouchStart = (e) => { 67 | console.log('handleTouchStart', e.nativeEvent); 68 | const { locationX, locationY, timestamp } = e.nativeEvent; 69 | this._touchStartTime = timestamp; 70 | this._touchStartPosition = { 71 | locationX, locationY 72 | }; 73 | } 74 | 75 | // move less than 1 px 76 | closeEnough(p1, p2) { 77 | if (Math.abs(p1 - p2) < 2) { 78 | return true; 79 | } 80 | return false; 81 | } 82 | 83 | handleTouchMove = (e) => { 84 | console.log('handleTouchMove'); 85 | const { timestamp, locationX, locationY } = e.nativeEvent; 86 | if (this.state.scrollEnabled !== false) { 87 | if (timestamp - this._touchStartTime > 300) { 88 | console.log('check position', e.nativeEvent); 89 | if (this.closeEnough(locationX, this._touchStartPosition.locationX) && 90 | this.closeEnough(locationY, this._touchStartPosition.locationY)) { 91 | console.log('-- lock --'); 92 | this.setState({ scrollEnabled: false }); 93 | } 94 | } 95 | } else { 96 | console.log('this.cross', this.cross, locationX); 97 | this.cross && this.cross.setNativeProps({ style: { left: locationX } }); 98 | } 99 | } 100 | 101 | handleTouchEnd = () => { 102 | console.log('handleTouchEnd'); 103 | if (this.state.scrollEnabled === false) { 104 | this.setState({ scrollEnabled: true }); 105 | } 106 | } 107 | 108 | handleScrollBeginDrag = () => { 109 | console.log('handleScrollBeginDrag'); 110 | } 111 | 112 | handleScrollEndDrag = () => { 113 | console.log('handleScrollEndDrag'); 114 | } 115 | 116 | render() { 117 | console.log('render'); 118 | const svgWidth = this.getSvgWidth(); 119 | const points = d3Array.range(0, svgWidth, 50); 120 | 121 | // scroll view 的 onTouchMove event fire 很正常 122 | // TouchWithoutFeedback/svg 的 panhandelr 常常會斷掉 (move -> end) 123 | return ( 124 | 125 | PanResponder overlay 126 | 127 | 145 | 149 | {points.map(point => 150 | 151 | 152 | {`${point}`} 153 | 154 | )} 155 | 156 | 157 | { this.cross = cross; }} 159 | style={styles.cross} 160 | /> 161 | 162 | 163 | 164 | ); 165 | } 166 | } 167 | 168 | const styles = StyleSheet.create({ 169 | container: { 170 | flex: 1, 171 | backgroundColor: '#fff' 172 | }, 173 | cross: { 174 | position: 'absolute', 175 | top: 0 , 176 | left: 0, 177 | backgroundColor: '#999', 178 | width: 2, 179 | height: defaultStockChartHeight 180 | } 181 | }); 182 | 183 | export default PanResponderOverlay; 184 | -------------------------------------------------------------------------------- /app/demos/SVGBasic.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, Text, Image, Dimensions, ScrollView, StyleSheet } from 'react-native'; 3 | import Svg, { Path } from 'react-native-svg'; 4 | 5 | import T from '../components/T'; 6 | import Code from '../components/Code'; 7 | import CopyRight from '../components/CopyRight'; 8 | 9 | const deviceWidth = Dimensions.get('window').width; 10 | 11 | class SVGBasic extends Component { 12 | render() { 13 | return ( 14 | 15 | SVG 16 | - Scalable Vector Graphics 17 | - XML-based vector image format for two-dimensional graphics with support for interactivity and animation. 18 | 19 | {` 20 | 21 | 22 | 23 | 24 | 25 | 26 | `} 27 | 28 | 32 | 33 | 34 | Coordinate Systems 35 | - the top left corner of the document is considered to be the point (0,0). 36 | - Positions are then measured in pixels from the top left corner, with the positive x direction being to the right, and the positive y direction being to the bottom. 37 | 38 | 39 | Install 40 | 41 | {` 42 | $ npm install react-native-svg 43 | $ rnpm link react-native-svg 44 | `} 45 | 46 | 47 | Usage 48 | 49 | {` 50 | import Svg, {Path} from 'react-native-svg'; 51 | 52 | 53 | 54 | 55 | `} 56 | 57 | - svg elements must be uppercase, path -> Path 58 | - Components: Circle, Ellipse, G, LinearGradient, RadialGradient, Line, Path, Polygon, Polyline, Rect, Symbol, Text, Use, Defs, Stop 59 | 60 | Examples 61 | SVG element with 100 width 100 height 62 | 63 | 64 | {` 65 | 70 | `} 71 | 72 | SVG element with width equals to device width 73 | 74 | 75 | {` 76 | 81 | `} 82 | 83 | Draw complicated logo with react-native-svg (Circle, Path, Polygon) 84 | 85 | 86 | Path element 87 | - The attribute d contains a seriers of commands and parameters in the SVG Path mini-language 88 | 89 | 90 | M(m) 91 | moveto: Move the pen to a new location. No line is drawn. All path data must begin with a 'moveto' command. 92 | 93 | 94 | L(l) 95 | lineto: Draw a line from the current point to the point (x,y). 96 | 97 | 98 | H(h) 99 | horizontal lineto: Draw a horizontal line from the current point to x. 100 | 101 | 102 | V(h) 103 | vertical lineto: Draw a horizontal line from the current point to y. 104 | 105 | 106 | 107 | 108 | 109 | 118 | 119 | 120 | {` 121 | 130 | `} 131 | 132 | 133 | 134 | 135 | 136 | 145 | 146 | 147 | {` 148 | 157 | `} 158 | 159 | 160 | 161 | Example: strokeWidth/dashArray 162 | 163 | 164 | 165 | 166 | 167 | {` 168 | 169 | 170 | `} 171 | 172 | 173 | ); 174 | } 175 | } 176 | 177 | const styles = StyleSheet.create({ 178 | container: { 179 | flex: 1, 180 | backgroundColor: '#fff' 181 | }, 182 | tableRow: { 183 | flexDirection: 'row', 184 | borderBottomWidth: 1, 185 | borderColor: '#ccc', 186 | borderStyle: 'solid', 187 | paddingVertical: 10, 188 | paddingHorizontal: 20 189 | }, 190 | firstRow: { 191 | width: 50 192 | }, 193 | tableCell: { 194 | flex: 1 195 | } 196 | }); 197 | 198 | export default SVGBasic; 199 | -------------------------------------------------------------------------------- /app/demos/StockChartWithVolume.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | InteractionManager, 4 | View, 5 | Dimensions, 6 | ScrollView, 7 | StyleSheet 8 | } from 'react-native'; 9 | import { 10 | VictoryBar, 11 | VictoryLine, 12 | VictoryAxis, 13 | VictoryArea 14 | } from 'victory-chart-native'; 15 | import Svg from 'react-native-svg'; 16 | 17 | import T from '../components/T'; 18 | import Loading from '../components/Loading'; 19 | import data from '../data'; 20 | import * as util from '../util'; 21 | 22 | const defaultHeight = 200; 23 | const defaultWidth = Dimensions.get('window').width; 24 | const volumes = data.ticks.map( d => d.volume); 25 | const highestVolume = Math.max(...volumes); 26 | const lowestVolume = Math.min(...volumes); 27 | 28 | class StockChartWithVolume extends Component { 29 | constructor(props) { 30 | super(props); 31 | this.state = { 32 | isReady: false 33 | }; 34 | } 35 | componentDidMount() { 36 | InteractionManager.runAfterInteractions(() => { 37 | this.setState({ isReady: true }); 38 | }); 39 | } 40 | render() { 41 | const { ticks, tradingHours, lowestPrice, highestPrice, previousClose } = data; 42 | if (!this.state.isReady) { 43 | return ; 44 | } 45 | 46 | return ( 47 | 48 | 49 | Combine Stock Chart with Volume Chart 50 | 51 | put two charts together 52 | 53 | 68 | util.tickFormatPercent(t, previousClose)} 76 | style={{ 77 | axis: { stroke: 'transparent' }, 78 | ticks: { stroke: 'transparent' } 79 | }} 80 | /> 81 | 82 | t * 1000), 88 | y: [lowestPrice, highestPrice] 89 | }} 90 | x={(d) => new Date(d.time * 1000)} 91 | y={'price'} 92 | height={defaultHeight} 93 | style={{ 94 | data: { 95 | stroke: 'rgba(0, 102, 221, 0.75)', 96 | fill: 'rgba(237, 247, 255, 0.75)', 97 | } 98 | }} 99 | /> 100 | 118 | 119 | 120 | 121 | t * 1000)} 126 | scale="time" 127 | style={{ 128 | axis: { strokeWidth: 1, stroke: '#ddd' }, 129 | ticks: { strokeWidth: 1, stroke: '#ddd' } 130 | }} 131 | /> 132 | v / 100000000} 139 | tickCount={2} 140 | style={{ 141 | axis: { stroke: 'transparent' }, 142 | ticks: { stroke: 'transparent' } 143 | }} 144 | /> 145 | t * 1000) 150 | }} 151 | standalone={false} 152 | data={ticks} 153 | x={(d) => d.time * 1000} 154 | y={'volume'} 155 | style={{ 156 | data: { 157 | width: 1, 158 | fill: (d) => { return d.mark ? 'red' : 'green'; } 159 | } 160 | }} 161 | /> 162 | 163 | 164 | 165 | 166 | ); 167 | } 168 | } 169 | 170 | const styles = StyleSheet.create({ 171 | container: { 172 | flex: 1, 173 | backgroundColor: '#fff' 174 | }, 175 | }); 176 | 177 | export default StockChartWithVolume; 178 | -------------------------------------------------------------------------------- /app/demos/VolumeChart.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, InteractionManager, ScrollView, StyleSheet, Dimensions } from 'react-native'; 3 | import { 4 | VictoryAxis, 5 | VictoryBar, 6 | } from 'victory-chart-native'; 7 | import Svg from 'react-native-svg'; 8 | 9 | import T from '../components/T'; 10 | import Loading from '../components/Loading'; 11 | import data from '../data'; 12 | 13 | const defaultHeight = 300; 14 | const defaultWidth = Dimensions.get('window').width; 15 | const volumes = data.ticks.map( d => d.volume); 16 | const highestVolume = Math.max(...volumes); 17 | const lowestVolume = Math.min(...volumes); 18 | 19 | class VolumeChart extends Component { 20 | constructor(props) { 21 | super(props); 22 | this.state = { 23 | isReady: false 24 | }; 25 | } 26 | componentDidMount() { 27 | InteractionManager.runAfterInteractions(() => { 28 | this.setState({ isReady: true }); 29 | }); 30 | } 31 | render() { 32 | const { ticks, tradingHours } = data; 33 | if (!this.state.isReady) { 34 | return ; 35 | } 36 | return ( 37 | 38 | 39 | Volume chart 40 | basic volume chart using VictoryBar 41 | d.time} 45 | y={'volume'} 46 | /> 47 | 48 | add some color 49 | d.time} 53 | y={'volume'} 54 | style={{ 55 | data: { 56 | width: 2, 57 | fill: (d) => { return d.mark ? 'red' : 'green'; } 58 | } 59 | }} 60 | /> 61 | 62 | Add axis, set proper domain 63 | 64 | t * 1000)} 67 | scale="time" 68 | /> 69 | v / 100000000} 74 | /> 75 | t * 1000) 78 | }} 79 | standalone={false} 80 | data={ticks} 81 | x={(d) => d.time * 1000} 82 | y={'volume'} 83 | style={{ 84 | data: { 85 | width: 1, 86 | fill: (d) => { return d.mark ? 'red' : 'green'; } 87 | } 88 | }} 89 | /> 90 | 91 | 92 | remove the default padding to make the volume chart more compact and display fewer tickCount 93 | 94 | t * 1000)} 99 | scale="time" 100 | /> 101 | v / 100000000} 108 | tickCount={2} 109 | /> 110 | t * 1000) 115 | }} 116 | standalone={false} 117 | data={ticks} 118 | x={(d) => d.time * 1000} 119 | y={'volume'} 120 | style={{ 121 | data: { 122 | width: 1, 123 | fill: (d) => { return d.mark ? 'red' : 'green'; } 124 | } 125 | }} 126 | /> 127 | 128 | 129 | 130 | ); 131 | } 132 | } 133 | 134 | const styles = StyleSheet.create({ 135 | container: { 136 | flex: 1, 137 | backgroundColor: '#fff' 138 | }, 139 | }); 140 | 141 | export default VolumeChart; 142 | -------------------------------------------------------------------------------- /app/demos/index.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes, Component } from 'react'; 2 | import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from 'react-native'; 3 | import { connect } from 'react-redux'; 4 | 5 | import { navigatePush } from '../redux/modules/routing'; 6 | 7 | class Demos extends Component { 8 | render() { 9 | const { handlePress } = this.props; 10 | 11 | return ( 12 | 13 | 14 | 15 | use D3Path/D3Shape 16 | 17 | 18 | handlePress('SVGBasic', 'SVG Basics')}> 19 | SVG Basics 20 | 21 | handlePress('D3Scale', 'D3Scale')}> 22 | D3 Scale 23 | 24 | handlePress('D3Shape', 'D3 Shape')}> 25 | D3 Shape 26 | 27 | handlePress('D3Ticks', 'Draw the grid line')}> 28 | Draw the grid line 29 | 30 | handlePress('CustomStockChart', 'Custom Stock Chart')}> 31 | Custom Stock Chart 32 | 33 | handlePress('CandleStick', 'CandleStick')}> 34 | CandleStick 35 | 36 | handlePress('PanResponderDemo', 'PanResponder')}> 37 | PanResponder 38 | 39 | handlePress('CandleStickWithPan', 'CandleStick with PanResponder')}> 40 | CandleStick with PanResponder 41 | 42 | handlePress('PanResponderOverlay', 'PanResponder overlay')}> 43 | PanResponder Overlay 44 | 45 | handlePress('CandleStickPanOverlay', 'CandleStick with PanResponder Overlay')}> 46 | CandleStick with PanResponder Overlay 47 | 48 | handlePress('CandleStickScrollView', 'CandleStick with ScrollView')}> 49 | CandleStick with ScrollView 50 | 51 | 52 | 53 | use formidable chart 54 | 55 | handlePress('BasicLineChart')}> 56 | Basic Line Chart 57 | 58 | 59 | handlePress('ChartWithAxis')}> 60 | Line Chart with Axis 61 | 62 | 63 | handlePress('MultipleAxes')}> 64 | Line Chart with multiple Axes 65 | 66 | 67 | handlePress('VolumeChart')}> 68 | Volume Chart 69 | 70 | handlePress('StockChartWithVolume')}> 71 | Stock Chart with Volume 72 | 73 | 74 | ); 75 | } 76 | } 77 | 78 | Demos.propTypes = { 79 | handlePress: PropTypes.func.isRequired 80 | }; 81 | 82 | const styles = StyleSheet.create({ 83 | container: { 84 | flex: 1, 85 | backgroundColor: '#fff', 86 | }, 87 | header: { 88 | backgroundColor: 'rgb(234, 234, 234)', 89 | }, 90 | headerText: { 91 | fontSize: 18 92 | }, 93 | row: { 94 | height: 40, 95 | paddingLeft: 20, 96 | justifyContent: 'center' 97 | } 98 | }); 99 | 100 | const mapDispatchToProps = (dispatch) => { 101 | return { 102 | handlePress: (key, title) => { 103 | dispatch(navigatePush({ key, title })); 104 | } 105 | }; 106 | }; 107 | 108 | export default connect( 109 | null, 110 | mapDispatchToProps 111 | )(Demos); 112 | -------------------------------------------------------------------------------- /app/redux/modules/routing.js: -------------------------------------------------------------------------------- 1 | import * as NavigationStateUtils from 'NavigationStateUtils'; 2 | 3 | // Actions 4 | export const NAVIGATE = 'NAVIGATE'; 5 | export const NAV_PUSH = 'NAV_PUSH'; 6 | export const NAV_POP = 'NAV_POP'; 7 | export const NAV_JUMP_TO_KEY = 'NAV_JUMP_TO_KEY'; 8 | export const NAV_JUMP_TO_INDEX = 'NAV_JUMP_TO_INDEX'; 9 | export const NAV_RESET = 'NAV_RESET'; 10 | 11 | const initialNavState = { 12 | index: 0, 13 | routes: [ 14 | { key: 'Demos' } 15 | ] 16 | }; 17 | 18 | // Action Creators 19 | export function navigatePush(data) { 20 | // state = typeof state === 'string' ? { key: state, title: state } : state; // eslint-disable-line no-param-reassign 21 | return { 22 | type: NAV_PUSH, 23 | data 24 | }; 25 | } 26 | 27 | export function navigatePop() { 28 | return { 29 | type: NAV_POP 30 | }; 31 | } 32 | 33 | export function navigateJumpToKey(key) { 34 | return { 35 | type: NAV_JUMP_TO_KEY, 36 | key 37 | }; 38 | } 39 | 40 | export function navigateJumpToIndex(index) { 41 | return { 42 | type: NAV_JUMP_TO_INDEX, 43 | index 44 | }; 45 | } 46 | 47 | export function navigateReset(routes, index) { 48 | return { 49 | type: NAV_RESET, 50 | index, 51 | routes 52 | }; 53 | } 54 | 55 | export default function reducer(state = initialNavState, action) { 56 | switch (action.type) { 57 | case NAV_PUSH: 58 | if (state.routes[state.index].key === (action.state && action.state.key)) { 59 | return state; 60 | } 61 | return NavigationStateUtils.push(state, action.data); 62 | 63 | case NAV_POP: 64 | if (state.index === 0 || state.routes.length === 1) { 65 | return state; 66 | } 67 | return NavigationStateUtils.pop(state); 68 | 69 | case NAV_JUMP_TO_KEY: 70 | return NavigationStateUtils.jumpTo(state, action.key); 71 | 72 | case NAV_JUMP_TO_INDEX: 73 | return NavigationStateUtils.jumpToIndex(state, action.index); 74 | 75 | case NAV_RESET: 76 | return NavigationStateUtils.reset(state, action.routes, action.index); 77 | 78 | default: 79 | return state; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/redux/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import routing from './modules/routing'; 3 | 4 | export default combineReducers({ 5 | routing, 6 | }); 7 | -------------------------------------------------------------------------------- /app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native'; 2 | import { createStore, applyMiddleware, compose } from 'redux'; 3 | import reducers from '../redux/reducers'; 4 | import devTools from 'remote-redux-devtools'; 5 | import { apiMiddleware } from 'redux-api-middleware'; 6 | 7 | const createStoreWithMiddleware = applyMiddleware(apiMiddleware)(createStore); 8 | 9 | let enhancer; 10 | 11 | if (__DEV__) { 12 | enhancer = compose( 13 | devTools({ 14 | name: Platform.OS, 15 | hostname: 'localhost', 16 | port: 5678 17 | }) 18 | ); 19 | } 20 | 21 | export default function configureStore(initialState = {}) { 22 | const store = createStoreWithMiddleware(reducers, initialState, enhancer); 23 | 24 | if (module.hot) { 25 | module.hot.accept(() => { 26 | store.replaceReducer(require('../redux/reducers').default); // eslint-disable-line global-require 27 | }); 28 | } 29 | 30 | return store; 31 | } 32 | -------------------------------------------------------------------------------- /app/util.js: -------------------------------------------------------------------------------- 1 | 2 | export function tickFormatPercent(n, previousClose) { 3 | const percent = (n - previousClose) * 100 / previousClose; 4 | const round = Math.round(percent * 100) / 100; 5 | return `${round}%`; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /index.android.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './app/app'; 3 | 4 | AppRegistry.registerComponent('navExpRedux', () => App); 5 | -------------------------------------------------------------------------------- /index.ios.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './app/app'; 3 | 4 | AppRegistry.registerComponent('navExpRedux', () => App); 5 | -------------------------------------------------------------------------------- /ios/navExpRedux.xcodeproj/xcshareddata/xcschemes/navExpRedux.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /ios/navExpRedux/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ios/navExpRedux/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "AppDelegate.h" 11 | 12 | #import "RCTBundleURLProvider.h" 13 | #import "RCTRootView.h" 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 18 | { 19 | NSURL *jsCodeLocation; 20 | 21 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; 22 | 23 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 24 | moduleName:@"navExpRedux" 25 | initialProperties:nil 26 | launchOptions:launchOptions]; 27 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 28 | 29 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 30 | UIViewController *rootViewController = [UIViewController new]; 31 | rootViewController.view = rootView; 32 | self.window.rootViewController = rootViewController; 33 | [self.window makeKeyAndVisible]; 34 | return YES; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /ios/navExpRedux/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ios/navExpRedux/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /ios/navExpRedux/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UIViewControllerBasedStatusBarAppearance 38 | 39 | NSLocationWhenInUseUsageDescription 40 | 41 | NSAppTransportSecurity 42 | 43 | 44 | 45 | NSAllowsArbitraryLoads 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/navExpRedux/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ios/navExpReduxTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/navExpReduxTests/navExpReduxTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | #import "RCTLog.h" 14 | #import "RCTRootView.h" 15 | 16 | #define TIMEOUT_SECONDS 600 17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 18 | 19 | @interface navExpReduxTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation navExpReduxTests 24 | 25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 26 | { 27 | if (test(view)) { 28 | return YES; 29 | } 30 | for (UIView *subview in [view subviews]) { 31 | if ([self findSubviewInView:subview matching:test]) { 32 | return YES; 33 | } 34 | } 35 | return NO; 36 | } 37 | 38 | - (void)testRendersWelcomeScreen 39 | { 40 | UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; 41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 42 | BOOL foundElement = NO; 43 | 44 | __block NSString *redboxError = nil; 45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 46 | if (level >= RCTLogLevelError) { 47 | redboxError = message; 48 | } 49 | }); 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | RCTSetLogFunction(RCTDefaultLogFunction); 64 | 65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "navExpRedux", 3 | "version": "1.0.0", 4 | "private": true, 5 | "author": "Joshua Lyman", 6 | "license": "MIT", 7 | "scripts": { 8 | "lint": "eslint .", 9 | "lint-staged": "lint-staged", 10 | "lint-pass": "echo '\\033[4;32m♡' Awsome!!! you are great to commit ♡' \\033[0m'", 11 | "postinstall": "remotedev-debugger --hostname localhost --port 5678 --injectserver" 12 | }, 13 | "lint-staged": { 14 | "eslint": "*.js" 15 | }, 16 | "pre-commit": [ 17 | "lint-staged", 18 | "lint-pass" 19 | ], 20 | "dependencies": { 21 | "lodash.merge": "^4.4.0", 22 | "lodash.unescape": "^4.0.0", 23 | "react": "~15.3.1", 24 | "react-native": "^0.34.1", 25 | "react-native-html-render": "^1.0.4", 26 | "react-native-scrollable-tab-view": "^0.5.3", 27 | "react-native-svg": "^4.3.1", 28 | "react-redux": "^4.4.0", 29 | "react-static-container": "^1.0.1", 30 | "redux": "^3.3.1", 31 | "redux-api-middleware": "chunghe/RN-redux-api-middleware#master", 32 | "redux-thunk": "^2.0.1", 33 | "victory-chart-native": "^1.0.0" 34 | }, 35 | "devDependencies": { 36 | "babel-eslint": "^6.1.0", 37 | "eslint": "^2.13.1", 38 | "eslint-config-airbnb": "^9.0.1", 39 | "eslint-plugin-import": "^1.9.2", 40 | "eslint-plugin-jsx-a11y": "^1.5.3", 41 | "eslint-plugin-react": "^5.2.2", 42 | "lint-staged": "^1.0.1", 43 | "pre-commit": "^1.1.3", 44 | "remote-redux-devtools": "^0.4.9", 45 | "remote-redux-devtools-on-debugger": "^0.6.2", 46 | "rnpm-plugin-upgrade": "^0.26.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /screenshots/screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunghe/React-Native-Stock-Chart/ab151943641e87969a2b61618c069f5d33cd7954/screenshots/screenshot.gif -------------------------------------------------------------------------------- /screenshots/stock-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunghe/React-Native-Stock-Chart/ab151943641e87969a2b61618c069f5d33cd7954/screenshots/stock-chart.png -------------------------------------------------------------------------------- /screenshots/volume-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chunghe/React-Native-Stock-Chart/ab151943641e87969a2b61618c069f5d33cd7954/screenshots/volume-chart.png --------------------------------------------------------------------------------