├── .gitignore ├── .metadata ├── README.md ├── android ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── zhe │ │ │ │ └── kchart │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── example.png ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Flutter.podspec │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── chart │ ├── Pointer.dart │ ├── chart_calculator.dart │ ├── chart_model.dart │ ├── chart_painter.dart │ ├── chart_utils.dart │ └── kline_view.dart ├── depth │ ├── depth_model.dart │ ├── depth_painter.dart │ └── depth_view.dart ├── dio_util.dart ├── example.dart ├── generated │ └── i18n.dart ├── kline_datas.dart └── main.dart ├── pubspec.lock ├── pubspec.yaml ├── res └── values │ └── strings_en.arb └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1aedbb1835bd6eb44550293d57d4d124f19901f0 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kchart Flutter 2 | 3 | 纯Flutter开发的k线和深度图控件,实现了MA,BOLL, MACD, KDJ, RSI指标,欢迎issue 4 | 5 | 6 | 7 | ## 一、k线 8 | 9 | 使用第三方库`dio`请求网路数据,使用下面函数对k线数据进行加工 10 | ```dart 11 | // k线数据模型 12 | List getKlineDataList(List data) { 13 | List kDataList = List(); 14 | for (int i = 0; i < data.length; i++) { 15 | int timestamp = data[i][0].toInt(); 16 | //timestamp 17 | double openPrice = data[i][1].toDouble(); 18 | // open 19 | double closePrice = data[i][4].toDouble(); 20 | // close 21 | double maxPrice = data[i][2].toDouble(); 22 | // max 23 | double minPrice = data[i][3].toDouble(); 24 | // min 25 | double volume = data[i][5].toDouble(); 26 | if (volume > 0) { 27 | kDataList.add(ChartModel( 28 | timestamp, openPrice, closePrice, maxPrice, minPrice, volume)); 29 | } 30 | } 31 | return kDataList; 32 | } 33 | ``` 34 | 拿到处理好对数据加入控件`KlineView()` 35 | 36 | ```dart 37 | Container( 38 | child: KlineView( 39 | dataList: dataList, 40 | currentDataType: _currentDataType, 41 | isShowSubview: _isShowSubview, 42 | viewType: _viewTypeIndex, 43 | subviewType: _subviewTypeIndex, 44 | )), 45 | ``` 46 | 47 | 48 | ## 二、深度图 49 | 50 | 深度图现在还是半成品,现在还没有展示详细数据功能,但是不影响使用 51 | 52 | 使用很简单,请求网路数据,然后使用下面函数对数据进行加工 53 | ```dart 54 | List depthList(List dataList) { 55 | List depthList = List(); 56 | int length = 0; 57 | if (dataList.length > 12) { 58 | length = 12; 59 | } else { 60 | length = dataList.length; 61 | } 62 | for (int i = 0; i < length; i++) { 63 | double price = dataList[i][0].toDouble(); 64 | double volume = dataList[i][1].toDouble(); 65 | depthList.add(DepthModel(price, volume)); 66 | } 67 | return depthList; 68 | } 69 | 70 | ``` 71 | 72 | 拿到处理好对数据加入控件`DepthView()` 73 | 74 | ```dart 75 | DepthView(depthList(_bidsList), depthList(_asksList)), 76 | ``` 77 | 78 | 79 | ### 具体使用方法可以参考example.dart 80 | 81 | 82 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.zhe.kchart" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 66 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 67 | } 68 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 11 | 15 | 22 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/zhe/kchart/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.zhe.kchart 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.2.71' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.2.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/example.png -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE: This podspec is NOT to be published. It is only used as a local source! 3 | # 4 | 5 | Pod::Spec.new do |s| 6 | s.name = 'Flutter' 7 | s.version = '1.0.0' 8 | s.summary = 'High-performance, high-fidelity mobile apps.' 9 | s.description = <<-DESC 10 | Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. 11 | DESC 12 | s.homepage = 'https://flutter.io' 13 | s.license = { :type => 'MIT' } 14 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } 15 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } 16 | s.ios.deployment_target = '8.0' 17 | s.vendored_frameworks = 'Flutter.framework' 18 | end 19 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 97C146F11CF9000F007C117D /* Supporting Files */, 94 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 95 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 96 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 97 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 98 | ); 99 | path = Runner; 100 | sourceTree = ""; 101 | }; 102 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | ); 106 | name = "Supporting Files"; 107 | sourceTree = ""; 108 | }; 109 | /* End PBXGroup section */ 110 | 111 | /* Begin PBXNativeTarget section */ 112 | 97C146ED1CF9000F007C117D /* Runner */ = { 113 | isa = PBXNativeTarget; 114 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 115 | buildPhases = ( 116 | 9740EEB61CF901F6004384FC /* Run Script */, 117 | 97C146EA1CF9000F007C117D /* Sources */, 118 | 97C146EB1CF9000F007C117D /* Frameworks */, 119 | 97C146EC1CF9000F007C117D /* Resources */, 120 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 121 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 122 | ); 123 | buildRules = ( 124 | ); 125 | dependencies = ( 126 | ); 127 | name = Runner; 128 | productName = Runner; 129 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 130 | productType = "com.apple.product-type.application"; 131 | }; 132 | /* End PBXNativeTarget section */ 133 | 134 | /* Begin PBXProject section */ 135 | 97C146E61CF9000F007C117D /* Project object */ = { 136 | isa = PBXProject; 137 | attributes = { 138 | LastUpgradeCheck = 1110; 139 | ORGANIZATIONNAME = "The Chromium Authors"; 140 | TargetAttributes = { 141 | 97C146ED1CF9000F007C117D = { 142 | CreatedOnToolsVersion = 7.3.1; 143 | DevelopmentTeam = D8C9JL4DPA; 144 | LastSwiftMigration = 1110; 145 | }; 146 | }; 147 | }; 148 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 149 | compatibilityVersion = "Xcode 3.2"; 150 | developmentRegion = en; 151 | hasScannedForEncodings = 0; 152 | knownRegions = ( 153 | en, 154 | Base, 155 | ); 156 | mainGroup = 97C146E51CF9000F007C117D; 157 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 158 | projectDirPath = ""; 159 | projectRoot = ""; 160 | targets = ( 161 | 97C146ED1CF9000F007C117D /* Runner */, 162 | ); 163 | }; 164 | /* End PBXProject section */ 165 | 166 | /* Begin PBXResourcesBuildPhase section */ 167 | 97C146EC1CF9000F007C117D /* Resources */ = { 168 | isa = PBXResourcesBuildPhase; 169 | buildActionMask = 2147483647; 170 | files = ( 171 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 172 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 173 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 174 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXResourcesBuildPhase section */ 179 | 180 | /* Begin PBXShellScriptBuildPhase section */ 181 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 182 | isa = PBXShellScriptBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | ); 186 | inputPaths = ( 187 | ); 188 | name = "Thin Binary"; 189 | outputPaths = ( 190 | ); 191 | runOnlyForDeploymentPostprocessing = 0; 192 | shellPath = /bin/sh; 193 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 194 | }; 195 | 9740EEB61CF901F6004384FC /* Run Script */ = { 196 | isa = PBXShellScriptBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | ); 200 | inputPaths = ( 201 | ); 202 | name = "Run Script"; 203 | outputPaths = ( 204 | ); 205 | runOnlyForDeploymentPostprocessing = 0; 206 | shellPath = /bin/sh; 207 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 208 | }; 209 | /* End PBXShellScriptBuildPhase section */ 210 | 211 | /* Begin PBXSourcesBuildPhase section */ 212 | 97C146EA1CF9000F007C117D /* Sources */ = { 213 | isa = PBXSourcesBuildPhase; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 217 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | }; 221 | /* End PBXSourcesBuildPhase section */ 222 | 223 | /* Begin PBXVariantGroup section */ 224 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 225 | isa = PBXVariantGroup; 226 | children = ( 227 | 97C146FB1CF9000F007C117D /* Base */, 228 | ); 229 | name = Main.storyboard; 230 | sourceTree = ""; 231 | }; 232 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 233 | isa = PBXVariantGroup; 234 | children = ( 235 | 97C147001CF9000F007C117D /* Base */, 236 | ); 237 | name = LaunchScreen.storyboard; 238 | sourceTree = ""; 239 | }; 240 | /* End PBXVariantGroup section */ 241 | 242 | /* Begin XCBuildConfiguration section */ 243 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 244 | isa = XCBuildConfiguration; 245 | buildSettings = { 246 | ALWAYS_SEARCH_USER_PATHS = NO; 247 | CLANG_ANALYZER_NONNULL = YES; 248 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 249 | CLANG_CXX_LIBRARY = "libc++"; 250 | CLANG_ENABLE_MODULES = YES; 251 | CLANG_ENABLE_OBJC_ARC = YES; 252 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 253 | CLANG_WARN_BOOL_CONVERSION = YES; 254 | CLANG_WARN_COMMA = YES; 255 | CLANG_WARN_CONSTANT_CONVERSION = YES; 256 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 257 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 258 | CLANG_WARN_EMPTY_BODY = YES; 259 | CLANG_WARN_ENUM_CONVERSION = YES; 260 | CLANG_WARN_INFINITE_RECURSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 267 | CLANG_WARN_STRICT_PROTOTYPES = YES; 268 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 269 | CLANG_WARN_UNREACHABLE_CODE = YES; 270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 271 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 272 | COPY_PHASE_STRIP = NO; 273 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 274 | ENABLE_NS_ASSERTIONS = NO; 275 | ENABLE_STRICT_OBJC_MSGSEND = YES; 276 | GCC_C_LANGUAGE_STANDARD = gnu99; 277 | GCC_NO_COMMON_BLOCKS = YES; 278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 280 | GCC_WARN_UNDECLARED_SELECTOR = YES; 281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 282 | GCC_WARN_UNUSED_FUNCTION = YES; 283 | GCC_WARN_UNUSED_VARIABLE = YES; 284 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 285 | MTL_ENABLE_DEBUG_INFO = NO; 286 | SDKROOT = iphoneos; 287 | TARGETED_DEVICE_FAMILY = "1,2"; 288 | VALIDATE_PRODUCT = YES; 289 | }; 290 | name = Profile; 291 | }; 292 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 293 | isa = XCBuildConfiguration; 294 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 295 | buildSettings = { 296 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 297 | CLANG_ENABLE_MODULES = YES; 298 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 299 | DEVELOPMENT_TEAM = D8C9JL4DPA; 300 | ENABLE_BITCODE = NO; 301 | FRAMEWORK_SEARCH_PATHS = ( 302 | "$(inherited)", 303 | "$(PROJECT_DIR)/Flutter", 304 | ); 305 | INFOPLIST_FILE = Runner/Info.plist; 306 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 307 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 308 | LIBRARY_SEARCH_PATHS = ( 309 | "$(inherited)", 310 | "$(PROJECT_DIR)/Flutter", 311 | ); 312 | PRODUCT_BUNDLE_IDENTIFIER = com.zhe.kchart; 313 | PRODUCT_NAME = "$(TARGET_NAME)"; 314 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 315 | SWIFT_VERSION = 5.0; 316 | VERSIONING_SYSTEM = "apple-generic"; 317 | }; 318 | name = Profile; 319 | }; 320 | 97C147031CF9000F007C117D /* Debug */ = { 321 | isa = XCBuildConfiguration; 322 | buildSettings = { 323 | ALWAYS_SEARCH_USER_PATHS = NO; 324 | CLANG_ANALYZER_NONNULL = YES; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 326 | CLANG_CXX_LIBRARY = "libc++"; 327 | CLANG_ENABLE_MODULES = YES; 328 | CLANG_ENABLE_OBJC_ARC = YES; 329 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 330 | CLANG_WARN_BOOL_CONVERSION = YES; 331 | CLANG_WARN_COMMA = YES; 332 | CLANG_WARN_CONSTANT_CONVERSION = YES; 333 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INFINITE_RECURSION = YES; 338 | CLANG_WARN_INT_CONVERSION = YES; 339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 344 | CLANG_WARN_STRICT_PROTOTYPES = YES; 345 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 346 | CLANG_WARN_UNREACHABLE_CODE = YES; 347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = dwarf; 351 | ENABLE_STRICT_OBJC_MSGSEND = YES; 352 | ENABLE_TESTABILITY = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu99; 354 | GCC_DYNAMIC_NO_PIC = NO; 355 | GCC_NO_COMMON_BLOCKS = YES; 356 | GCC_OPTIMIZATION_LEVEL = 0; 357 | GCC_PREPROCESSOR_DEFINITIONS = ( 358 | "DEBUG=1", 359 | "$(inherited)", 360 | ); 361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 363 | GCC_WARN_UNDECLARED_SELECTOR = YES; 364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 365 | GCC_WARN_UNUSED_FUNCTION = YES; 366 | GCC_WARN_UNUSED_VARIABLE = YES; 367 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 368 | MTL_ENABLE_DEBUG_INFO = YES; 369 | ONLY_ACTIVE_ARCH = YES; 370 | SDKROOT = iphoneos; 371 | TARGETED_DEVICE_FAMILY = "1,2"; 372 | }; 373 | name = Debug; 374 | }; 375 | 97C147041CF9000F007C117D /* Release */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | ALWAYS_SEARCH_USER_PATHS = NO; 379 | CLANG_ANALYZER_NONNULL = YES; 380 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 381 | CLANG_CXX_LIBRARY = "libc++"; 382 | CLANG_ENABLE_MODULES = YES; 383 | CLANG_ENABLE_OBJC_ARC = YES; 384 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 385 | CLANG_WARN_BOOL_CONVERSION = YES; 386 | CLANG_WARN_COMMA = YES; 387 | CLANG_WARN_CONSTANT_CONVERSION = YES; 388 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 389 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 390 | CLANG_WARN_EMPTY_BODY = YES; 391 | CLANG_WARN_ENUM_CONVERSION = YES; 392 | CLANG_WARN_INFINITE_RECURSION = YES; 393 | CLANG_WARN_INT_CONVERSION = YES; 394 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 395 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 396 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 397 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 398 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 399 | CLANG_WARN_STRICT_PROTOTYPES = YES; 400 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 401 | CLANG_WARN_UNREACHABLE_CODE = YES; 402 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 403 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 404 | COPY_PHASE_STRIP = NO; 405 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 406 | ENABLE_NS_ASSERTIONS = NO; 407 | ENABLE_STRICT_OBJC_MSGSEND = YES; 408 | GCC_C_LANGUAGE_STANDARD = gnu99; 409 | GCC_NO_COMMON_BLOCKS = YES; 410 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 411 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 412 | GCC_WARN_UNDECLARED_SELECTOR = YES; 413 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 414 | GCC_WARN_UNUSED_FUNCTION = YES; 415 | GCC_WARN_UNUSED_VARIABLE = YES; 416 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 417 | MTL_ENABLE_DEBUG_INFO = NO; 418 | SDKROOT = iphoneos; 419 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 420 | TARGETED_DEVICE_FAMILY = "1,2"; 421 | VALIDATE_PRODUCT = YES; 422 | }; 423 | name = Release; 424 | }; 425 | 97C147061CF9000F007C117D /* Debug */ = { 426 | isa = XCBuildConfiguration; 427 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 428 | buildSettings = { 429 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 430 | CLANG_ENABLE_MODULES = YES; 431 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 432 | DEVELOPMENT_TEAM = D8C9JL4DPA; 433 | ENABLE_BITCODE = NO; 434 | FRAMEWORK_SEARCH_PATHS = ( 435 | "$(inherited)", 436 | "$(PROJECT_DIR)/Flutter", 437 | ); 438 | INFOPLIST_FILE = Runner/Info.plist; 439 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 440 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 441 | LIBRARY_SEARCH_PATHS = ( 442 | "$(inherited)", 443 | "$(PROJECT_DIR)/Flutter", 444 | ); 445 | PRODUCT_BUNDLE_IDENTIFIER = com.zhe.kchart; 446 | PRODUCT_NAME = "$(TARGET_NAME)"; 447 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 448 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 449 | SWIFT_VERSION = 5.0; 450 | VERSIONING_SYSTEM = "apple-generic"; 451 | }; 452 | name = Debug; 453 | }; 454 | 97C147071CF9000F007C117D /* Release */ = { 455 | isa = XCBuildConfiguration; 456 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 457 | buildSettings = { 458 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 459 | CLANG_ENABLE_MODULES = YES; 460 | CODE_SIGN_IDENTITY = "Apple Development"; 461 | CODE_SIGN_STYLE = Automatic; 462 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 463 | DEVELOPMENT_TEAM = 72RTPZP9YT; 464 | ENABLE_BITCODE = NO; 465 | FRAMEWORK_SEARCH_PATHS = ( 466 | "$(inherited)", 467 | "$(PROJECT_DIR)/Flutter", 468 | ); 469 | INFOPLIST_FILE = Runner/Info.plist; 470 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 471 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 472 | LIBRARY_SEARCH_PATHS = ( 473 | "$(inherited)", 474 | "$(PROJECT_DIR)/Flutter", 475 | ); 476 | PRODUCT_BUNDLE_IDENTIFIER = com.zhe.kchart; 477 | PRODUCT_NAME = "$(TARGET_NAME)"; 478 | PROVISIONING_PROFILE_SPECIFIER = ""; 479 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 480 | SWIFT_VERSION = 5.0; 481 | VERSIONING_SYSTEM = "apple-generic"; 482 | }; 483 | name = Release; 484 | }; 485 | /* End XCBuildConfiguration section */ 486 | 487 | /* Begin XCConfigurationList section */ 488 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 489 | isa = XCConfigurationList; 490 | buildConfigurations = ( 491 | 97C147031CF9000F007C117D /* Debug */, 492 | 97C147041CF9000F007C117D /* Release */, 493 | 249021D3217E4FDB00AE95B9 /* Profile */, 494 | ); 495 | defaultConfigurationIsVisible = 0; 496 | defaultConfigurationName = Release; 497 | }; 498 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 499 | isa = XCConfigurationList; 500 | buildConfigurations = ( 501 | 97C147061CF9000F007C117D /* Debug */, 502 | 97C147071CF9000F007C117D /* Release */, 503 | 249021D4217E4FDB00AE95B9 /* Profile */, 504 | ); 505 | defaultConfigurationIsVisible = 0; 506 | defaultConfigurationName = Release; 507 | }; 508 | /* End XCConfigurationList section */ 509 | }; 510 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 511 | } 512 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zzzmyfox/kline_flutter/413d2024d700e84af4522d5e9a68ddf74f6969a3/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | kchart 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/chart/Pointer.dart: -------------------------------------------------------------------------------- 1 | class Pointer { 2 | double x; 3 | double y; 4 | void setX(double x) { 5 | this.x = x; 6 | } 7 | 8 | void setY(double y) { 9 | this.y = y; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/chart/chart_calculator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'Pointer.dart'; 6 | import 'chart_model.dart'; 7 | 8 | class ChartCalculator { 9 | static int _day5 = 5; 10 | static int _day10 = 10; 11 | static int _day30 = 30; 12 | double bezierRatio = 0.16; 13 | static List _cacheList = List(); 14 | 15 | /// MA 16 | void calculateMa(List dataList, bool isEndData) { 17 | if (dataList == null || dataList.isEmpty) { 18 | return; 19 | } 20 | _cacheList.clear(); 21 | _cacheList.addAll(dataList); 22 | for (int i = 0; i < dataList.length; i++) { 23 | if (i + _day5 <= dataList.length) { 24 | //price ma5 25 | dataList[i + _day5 - 1] 26 | .setPriceMA5(_getPriceMA(_cacheList.sublist(i, i + _day5))); 27 | //volume ma5 28 | dataList[i + _day5 - 1] 29 | .setVolumeMA5(_getVolumeMA(_cacheList.sublist(i, i + _day5))); 30 | } 31 | if (i + _day10 <= dataList.length) { 32 | //price ma10 33 | dataList[i + _day10 - 1] 34 | .setPriceMA10(_getPriceMA(_cacheList.sublist(i, i + _day10))); 35 | //volume ma10 36 | dataList[i + _day10 - 1] 37 | .setVolumeMA10(_getVolumeMA(_cacheList.sublist(i, i + _day10))); 38 | } 39 | if (i + _day30 <= dataList.length) { 40 | //price ma 30 41 | if (dataList[i + _day30 - 1].priceMA30 != 0 && isEndData) { 42 | break; 43 | } else { 44 | dataList[i + _day30 - 1] 45 | .setPriceMA30(_getPriceMA(_cacheList.sublist(i, i + _day30))); 46 | } 47 | } 48 | } 49 | } 50 | 51 | // 52 | double _getPriceMA(List dataList) { 53 | if (dataList == null || dataList.isEmpty) { 54 | return -1; 55 | } 56 | double total = 0; 57 | for (ChartModel data in dataList) { 58 | total += data.closePrice; 59 | } 60 | return total / dataList.length; 61 | } 62 | 63 | // 64 | double _getVolumeMA(List dataList) { 65 | if (dataList == null || dataList.isEmpty) { 66 | return -1; 67 | } 68 | double total = 0; 69 | for (ChartModel data in dataList) { 70 | total += data.volume; 71 | } 72 | return total / dataList.length; 73 | } 74 | 75 | /// BOLL 76 | void calculateBoll( 77 | List dataList, int period, int k, bool isEndData) { 78 | if (dataList == null || 79 | dataList.isEmpty || 80 | period < 0 || 81 | period > dataList.length - 1) { 82 | return; 83 | } 84 | double mb; 85 | double up; 86 | double dn; 87 | 88 | double sum = 0; 89 | double sum2 = 0; 90 | double ma; 91 | double ma2; 92 | double md; 93 | 94 | for (int i = 0; i < dataList.length; i++) { 95 | if (dataList[i].bollMB != 0 && isEndData) { 96 | break; 97 | } 98 | ChartModel data = dataList[i]; 99 | sum += data.closePrice; 100 | sum2 += data.closePrice; 101 | if (i > period - 1) { 102 | sum -= dataList[i - period].closePrice; 103 | } 104 | if (i > period - 2) { 105 | sum2 -= dataList[i - period + 1].closePrice; 106 | } 107 | 108 | if (i < period - 1) { 109 | continue; 110 | } 111 | 112 | ma = sum / period; 113 | ma2 = sum2 / (period - 1); 114 | md = 0; 115 | for (int j = i + 1 - period; j <= i; j++) { 116 | md += pow(dataList[j].closePrice - ma, 2); 117 | } 118 | md = sqrt(md / period); 119 | mb = ma2; 120 | up = mb + k * md; 121 | dn = mb - k * md; 122 | 123 | data.setBollMB(mb); 124 | data.setBollUP(up); 125 | data.setBollDN(dn); 126 | } 127 | } 128 | 129 | /// MACD 130 | void calculateMACD(List dataList, int fastPeriod, int slowPeriod, 131 | int signalPeriod, bool isEndData) { 132 | if (dataList == null || dataList.isEmpty) { 133 | return; 134 | } 135 | double preEma_12 = 0; 136 | double preEma_26 = 0; 137 | double preDEA = 0; 138 | 139 | double ema_12 = 0; 140 | double ema_26 = 0; 141 | 142 | double dea = 0; 143 | double dif = 0; 144 | double macd = 0; 145 | 146 | for (int i = 0; i < dataList.length; i++) { 147 | if (dataList[i].macd != 0 && isEndData) { 148 | break; 149 | } 150 | 151 | ema_12 = preEma_12 * (fastPeriod - 1) / (fastPeriod + 1) + 152 | dataList[i].closePrice * 2 / (fastPeriod + 1); 153 | ema_26 = preEma_26 * (slowPeriod - 1) / (slowPeriod + 1) + 154 | dataList[i].closePrice * 2 / (slowPeriod + 1); 155 | 156 | dif = ema_12 - ema_26; 157 | dea = preDEA * (signalPeriod - 1) / (signalPeriod + 1) + 158 | dif * 2 / (signalPeriod + 1); 159 | macd = 2 * (dif - dea); 160 | 161 | preEma_12 = ema_12; 162 | preEma_26 = ema_26; 163 | preDEA = dea; 164 | 165 | dataList[i].setMACD(macd); 166 | dataList[i].setDEA(dea); 167 | dataList[i].setDIF(dif); 168 | } 169 | } 170 | 171 | /// KDJ 172 | void calculateKDJ( 173 | List dataList, int n1, int n2, int n3, bool isEndData) { 174 | if (dataList == null || dataList.isEmpty) { 175 | return; 176 | } 177 | List mK = List(); 178 | List mD = List(); 179 | double jValue; 180 | double highValue = dataList[0].maxPrice; 181 | double lowValue = dataList[0].minPrice; 182 | int highPosition = 0; 183 | int lowPosition = 0; 184 | double rsv = 0.0; 185 | for (int i = 0; i < dataList.length; i++) { 186 | if (dataList[i].k != 0 && isEndData) { 187 | break; 188 | } 189 | if (i == 0) { 190 | mK.insert(0, 50); 191 | mD.insert(0, 50); 192 | jValue = 50; 193 | } else { 194 | if (highValue <= dataList[i].maxPrice) { 195 | highValue = dataList[i].maxPrice; 196 | highPosition = i; 197 | } 198 | if (lowValue >= dataList[i].minPrice) { 199 | lowValue = dataList[i].minPrice; 200 | lowPosition = i; 201 | } 202 | if (i > (n1 - 1)) { 203 | if (highValue > dataList[i].maxPrice) { 204 | if (highPosition < (i - (n1 - 1))) { 205 | highValue = dataList[i - (n1 - 1)].maxPrice; 206 | for (int j = (i - (n1 - 2)); j <= i; j++) { 207 | if (highValue <= dataList[j].maxPrice) { 208 | highValue = dataList[j].maxPrice; 209 | highPosition = j; 210 | } 211 | } 212 | } 213 | } 214 | if (lowValue < dataList[i].minPrice) { 215 | if (lowPosition < i - (n1 - 1)) { 216 | lowValue = dataList[i].minPrice; 217 | for (int k = (i - (n1 - 2)); k <= i; k++) { 218 | if (lowValue >= dataList[k].minPrice) { 219 | lowValue = dataList[k].minPrice; 220 | lowPosition = k; 221 | } 222 | } 223 | } 224 | } 225 | } 226 | if (highValue != lowValue) { 227 | rsv = (dataList[i].closePrice - lowValue) / 228 | (highValue - lowValue) * 229 | 100; 230 | } 231 | mK.insert(i, (mK[i - 1] * (n2 - 1) + rsv) / n2); 232 | mD.insert(i, (mD[i - 1] * (n3 - 1) + mK[i]) / n3); 233 | jValue = 3 * mK[i] - 2 * mD[i]; 234 | } 235 | 236 | dataList[i].setK(mK[i]); 237 | dataList[i].setD(mD[i]); 238 | dataList[i].setJ(jValue); 239 | } 240 | } 241 | 242 | /// RSI 243 | void calculateRSI(List dataList, int period1, int period2, 244 | int period3, bool isEndData) { 245 | if (dataList == null || dataList.isEmpty) { 246 | return; 247 | } 248 | double upRateSum; 249 | double upRateCount; 250 | double dnRateSum; 251 | int dnRateCount; 252 | for (int i = 0; i < dataList.length; i++) { 253 | if (dataList[i].rs3 != 0 && isEndData) { 254 | break; 255 | } 256 | upRateSum = 0; 257 | upRateCount = 0; 258 | dnRateSum = 0; 259 | dnRateCount = 0; 260 | if (i >= period1 - 1) { 261 | for (int x = i; x >= i + 1 - period1; x--) { 262 | if (dataList[x].closePrice - dataList[x].openPrice >= 0) { 263 | upRateSum += (dataList[x].closePrice - dataList[x].openPrice) / 264 | dataList[x].openPrice; 265 | upRateCount++; 266 | } else { 267 | dnRateSum += (dataList[x].closePrice - dataList[x].openPrice) / 268 | dataList[x].openPrice; 269 | dnRateCount++; 270 | } 271 | } 272 | double avgUpRate = 0; 273 | double avgDnRate = 0; 274 | if (upRateSum > 0) { 275 | avgUpRate = upRateSum / upRateCount; 276 | } 277 | if (dnRateSum < 0) { 278 | avgDnRate = dnRateSum / dnRateCount; 279 | } 280 | dataList[i].setRS1(avgUpRate / (avgUpRate + avgDnRate.abs()) * 100); 281 | } 282 | upRateSum = 0; 283 | upRateCount = 0; 284 | dnRateSum = 0; 285 | dnRateCount = 0; 286 | if (i >= period2 - 1) { 287 | for (int x = i; x >= i + 1 - period2; x--) { 288 | if (dataList[x].closePrice - dataList[x].openPrice >= 0) { 289 | upRateSum += (dataList[x].closePrice - dataList[x].openPrice) / 290 | dataList[x].openPrice; 291 | upRateCount++; 292 | } else { 293 | dnRateSum += (dataList[x].closePrice - dataList[x].openPrice) / 294 | dataList[x].openPrice; 295 | dnRateCount++; 296 | } 297 | } 298 | double avgUpRate = 0; 299 | double avgDnRate = 0; 300 | if (upRateSum > 0) { 301 | avgUpRate = upRateSum / upRateCount; 302 | } 303 | if (dnRateSum < 0) { 304 | avgDnRate = dnRateSum / dnRateCount; 305 | } 306 | dataList[i].setRS2(avgUpRate / (avgUpRate + avgDnRate.abs()) * 100); 307 | } 308 | upRateSum = 0; 309 | upRateCount = 0; 310 | dnRateSum = 0; 311 | dnRateCount = 0; 312 | if (i >= period3 - 1) { 313 | for (int x = i; x >= i + 1 - period3; x--) { 314 | if (dataList[x].closePrice - dataList[x].openPrice >= 0) { 315 | upRateSum += (dataList[x].closePrice - dataList[x].openPrice) / 316 | dataList[x].openPrice; 317 | upRateCount++; 318 | } else { 319 | dnRateSum += (dataList[x].closePrice - dataList[x].openPrice) / 320 | dataList[x].openPrice; 321 | dnRateCount++; 322 | } 323 | } 324 | double avgUpRate = 0; 325 | double avgDnRate = 0; 326 | if (upRateSum > 0) { 327 | avgUpRate = upRateSum / upRateCount; 328 | } 329 | if (dnRateSum < 0) { 330 | avgDnRate = dnRateSum / dnRateCount; 331 | } 332 | dataList[i].setRS3(avgUpRate / (avgUpRate + avgDnRate.abs()) * 100); 333 | } 334 | } 335 | } 336 | 337 | /// third stage bezier path point 338 | void setBezierPath(List pointList, Path path) { 339 | path.reset(); 340 | if (pointList == null || pointList.isEmpty) { 341 | return; 342 | } 343 | path.moveTo(pointList[0].x, pointList[0].y); 344 | Pointer _leftControlPointer = Pointer(); 345 | Pointer _rightControlPointer = Pointer(); 346 | 347 | for (int i = 0; i < pointList.length; i++) { 348 | if (i == 0 && pointList.length > 2) { 349 | _leftControlPointer.setX(pointList[i].x + 350 | bezierRatio * (pointList[i + 1].x - pointList[0].x)); 351 | _leftControlPointer.setY(pointList[i].y + 352 | bezierRatio * (pointList[i + 1].y - pointList[0].y)); 353 | _rightControlPointer.setX(pointList[i + 1].x - 354 | bezierRatio * (pointList[i + 2].x - pointList[i].x)); 355 | _rightControlPointer.setY(pointList[i + 1].y - 356 | bezierRatio * (pointList[i + 2].y - pointList[i].y)); 357 | } else if (i == pointList.length - 2 && i > 1) { 358 | _leftControlPointer.setX(pointList[i].x + 359 | bezierRatio * (pointList[i + 1].x - pointList[i - 1].x)); 360 | _leftControlPointer.setY(pointList[i].y + 361 | bezierRatio * (pointList[i + 1].y - pointList[i - 1].y)); 362 | _rightControlPointer.setX(pointList[i + 1].x - 363 | bezierRatio * (pointList[i + 1].x - pointList[i].x)); 364 | _rightControlPointer.setY(pointList[i + 1].y - 365 | bezierRatio * (pointList[i + 1].y - pointList[i].y)); 366 | } else if (i > 0 && i < pointList.length - 2) { 367 | _leftControlPointer.setX(pointList[i].x + 368 | bezierRatio * (pointList[i + 1].x - pointList[i - 1].x)); 369 | _leftControlPointer.setY(pointList[i].y + 370 | bezierRatio * (pointList[i + 1].y - pointList[i - 1].y)); 371 | _rightControlPointer.setX(pointList[i + 1].x - 372 | bezierRatio * (pointList[i + 2].x - pointList[i].x)); 373 | _rightControlPointer.setY(pointList[i + 1].y - 374 | bezierRatio * (pointList[i + 2].y - pointList[i].y)); 375 | } 376 | if (i < pointList.length - 1) { 377 | path.cubicTo( 378 | _leftControlPointer.x, 379 | _leftControlPointer.y, 380 | _rightControlPointer.x, 381 | _rightControlPointer.y, 382 | pointList[i + 1].x, 383 | pointList[i + 1].y); 384 | } 385 | } 386 | } 387 | 388 | void setLinePath(List pointerList, Path path) { 389 | path.reset(); 390 | if (pointerList == null || pointerList.isEmpty) { 391 | return; 392 | } 393 | path.moveTo(pointerList[0].x, pointerList[0].y); 394 | for (int i = 1; i < pointerList.length; i++) { 395 | path.lineTo(pointerList[i].x, pointerList[i].y); 396 | } 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /lib/chart/chart_model.dart: -------------------------------------------------------------------------------- 1 | class ChartModel { 2 | int timestamp; 3 | double closePrice; 4 | double openPrice; 5 | double maxPrice; 6 | double minPrice; 7 | double volume; 8 | 9 | ///kline data 10 | ChartModel(int timestamp, double openPrice, double closePrice, 11 | double maxPrice, double minPrice, double volume) { 12 | this.timestamp = timestamp; 13 | this.openPrice = openPrice; 14 | this.closePrice = closePrice; 15 | this.maxPrice = maxPrice; 16 | this.minPrice = minPrice; 17 | this.volume = volume; 18 | } 19 | 20 | ///Main chart view 21 | double leftStartX; 22 | double rightEndX; 23 | double closeY; 24 | double openY; 25 | void setLeftStartX(double leftStartX) { 26 | this.leftStartX = leftStartX; 27 | } 28 | 29 | void setRightEndX(double rightEndX) { 30 | this.rightEndX = rightEndX; 31 | } 32 | 33 | void setCloseY(double closeY) { 34 | this.closeY = closeY; 35 | } 36 | 37 | void setOpenY(double openY) { 38 | this.openY = openY; 39 | } 40 | 41 | ///MA 42 | double priceMA5; 43 | double priceMA10; 44 | double priceMA30; 45 | double volumeMA5; 46 | double volumeMA10; 47 | // price MA 48 | void setPriceMA5(double priceMA5) { 49 | this.priceMA5 = priceMA5; 50 | } 51 | 52 | void setPriceMA10(double priceMA10) { 53 | this.priceMA10 = priceMA10; 54 | } 55 | 56 | void setPriceMA30(double priceMA30) { 57 | this.priceMA30 = priceMA30; 58 | } 59 | 60 | // volume ma 61 | void setVolumeMA5(double volumeMA5) { 62 | this.volumeMA5 = volumeMA5; 63 | } 64 | 65 | void setVolumeMA10(double volumeMA10) { 66 | this.volumeMA10 = volumeMA10; 67 | } 68 | 69 | /// BOLL 70 | double bollMB; 71 | double bollUP; 72 | double bollDN; 73 | void setBollMB(double bollMB) { 74 | this.bollMB = bollMB; 75 | } 76 | 77 | void setBollUP(double bollUP) { 78 | this.bollUP = bollUP; 79 | } 80 | 81 | void setBollDN(double bollDN) { 82 | this.bollDN = bollDN; 83 | } 84 | 85 | /// MACD 86 | double macd; 87 | double dea; 88 | double dif; 89 | void setMACD(double macd) { 90 | this.macd = macd; 91 | } 92 | 93 | void setDEA(double dea) { 94 | this.dea = dea; 95 | } 96 | 97 | void setDIF(double dif) { 98 | this.dif = dif; 99 | } 100 | 101 | /// KDJ 102 | double k; 103 | double d; 104 | double j; 105 | void setK(double k) { 106 | this.k = k; 107 | } 108 | 109 | void setD(double d) { 110 | this.d = d; 111 | } 112 | 113 | void setJ(double j) { 114 | this.j = j; 115 | } 116 | 117 | /// RSI 118 | double rs1; 119 | double rs2; 120 | double rs3; 121 | void setRS1(double rs1) { 122 | this.rs1 = rs1; 123 | } 124 | 125 | void setRS2(double rs2) { 126 | this.rs2 = rs2; 127 | } 128 | 129 | void setRS3(double rs3) { 130 | this.rs3 = rs3; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lib/chart/chart_painter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:flutter/material.dart'; 3 | import 'Pointer.dart'; 4 | import 'chart_calculator.dart'; 5 | import 'chart_model.dart'; 6 | 7 | class ChartPainter extends CustomPainter { 8 | ChartPainter({ 9 | this.viewDataList, 10 | this.maxViewDataNum, 11 | this.lastData, 12 | this.detailDataList, 13 | this.isShowDetails, 14 | this.isShowSubview, 15 | this.viewType, 16 | this.subviewType, 17 | }); 18 | 19 | ///data list 20 | final List viewDataList; 21 | final int maxViewDataNum; 22 | final List detailDataList; 23 | final ChartModel lastData; 24 | final bool isShowDetails; 25 | final bool isShowSubview; 26 | final int viewType; 27 | final int subviewType; 28 | 29 | /// painter 30 | Paint _paint = Paint(); 31 | 32 | ///xy value list from scale lines 33 | List _verticalXList = List(); 34 | List _horizontalYList = List(); 35 | 36 | ///line start point 37 | double _leftStart; 38 | double _rightEnd; 39 | double _bottomEnd; 40 | double _topStart; 41 | 42 | ///colors 43 | Color scaleTextColor = Colors.grey; 44 | Color scaleLineColor = Colors.grey; 45 | Color riseColor = Color(0xFF03c086); 46 | Color fallColor = Color(0xFFff524a); 47 | Color ma5Color = Color(0xFFF6DC93); 48 | Color ma10Color = Color(0xFF61D1C0); 49 | Color ma30Color = Color(0xFFCB92FE); 50 | 51 | ///detail text 52 | /// cn 53 | List detailTitleCN = ["时间", "开", "高", "收", "低", "涨跌额", "涨跌幅", "成交量"]; 54 | 55 | /// en 56 | List detailTitleEN = [ 57 | "time", 58 | "open", 59 | "hign", 60 | "close", 61 | "low", 62 | "up or down amount", 63 | "up or down rate", 64 | "volume" 65 | ]; 66 | 67 | /// main view variable 68 | double _maxPrice; 69 | double _minPrice; 70 | double _maxVolume; 71 | double _maxPriceX, _maxPriceY, _minPriceX, _minPriceY; 72 | double _verticalSpace; 73 | 74 | ///candlestick 75 | double _perPriceRectWidth, _perPriceRectHeight, _perVolumeRectHeight; 76 | double _subViewTopY; 77 | double _priceChartBottom, _volumeChartBottom; 78 | double _topPrice; 79 | double _botPrice; 80 | 81 | /// 82 | double _maxMACD; 83 | double _minMACD; 84 | double _perMACDHeight; 85 | double _subviewCenterY; 86 | double _perDEAHeight; 87 | double _perDifHeight; 88 | 89 | /// kdj 90 | double _maxK; 91 | double _kHeight; 92 | double _dHeight; 93 | double _jHeight; 94 | 95 | /// rsi 96 | double _rsiHeight; 97 | 98 | /// 99 | ChartCalculator _chartCalculator = ChartCalculator(); 100 | List mainMa5PointList = List(); 101 | List mainMa10PointList = List(); 102 | List mainMa30PointList = List(); 103 | List volumeMa5PointList = List(); 104 | List volumeMa10PointList = List(); 105 | List subviewMA5List = List(); 106 | List subviewMA10List = List(); 107 | List subviewMA30List = List(); 108 | Path path = Path(); 109 | 110 | /// draw 111 | @override 112 | void paint(Canvas canvas, Size size) { 113 | if (viewDataList.isEmpty) return; 114 | _leftStart = 5.0; 115 | _topStart = 20.0; 116 | _rightEnd = size.width; 117 | _bottomEnd = size.height; 118 | if (viewDataList.isEmpty) { 119 | return; 120 | } 121 | 122 | ///view 123 | _drawScaleLine(canvas); 124 | _drawMainChartView(canvas); 125 | 126 | ///curve 127 | _drawBezierCurve(canvas); 128 | 129 | ///text 130 | _drawMaxAndMinPriceText(canvas); 131 | _drawAbscissaText(canvas); 132 | _drawOrdinateText(canvas); 133 | _drawTopText(canvas); 134 | _drawVolumeText(canvas); 135 | 136 | /// details 137 | _drawCrossHairLine(canvas); 138 | _drawDetails(canvas); 139 | _drawLastData(canvas); 140 | } 141 | 142 | /// current price 143 | void _drawLastData(Canvas canvas) { 144 | if (lastData == null || isShowDetails == true) { 145 | return; 146 | } 147 | // horizontal line 148 | double moveY = lastData.closeY; 149 | 150 | if (moveY < _horizontalYList[0]) { 151 | moveY = _horizontalYList[0]; 152 | } else if (moveY > _priceChartBottom) { 153 | moveY = _priceChartBottom; 154 | } 155 | resetPaintStyle(color: Colors.blue, paintingStyle: PaintingStyle.fill); 156 | 157 | var max = _verticalXList[_verticalXList.length - 1]; // size获取到宽度 158 | var dashWidth = 5; 159 | var dashSpace = 5; 160 | double startX = _verticalXList[0]; 161 | final space = (dashSpace + dashWidth); 162 | 163 | while (startX < max) { 164 | canvas.drawLine( 165 | Offset(startX, moveY), Offset(startX + dashWidth, moveY), _paint); 166 | startX += space; 167 | } 168 | 169 | resetPaintStyle(color: Colors.blue, paintingStyle: PaintingStyle.fill); 170 | // left label 171 | String movePrice = setPrecision(lastData.closePrice, 2); 172 | Rect leftRect = Rect.fromLTRB( 173 | _verticalXList[_verticalXList.length - 1], 174 | moveY + _getTextBounds(movePrice).height, 175 | _rightEnd, 176 | moveY - _getTextBounds(movePrice).height); 177 | canvas.drawRect(leftRect, _paint); 178 | _drawText( 179 | canvas, 180 | movePrice, 181 | Colors.black, 182 | Offset(_verticalXList[_verticalXList.length - 1] + dp2px(1.0), 183 | moveY - _getTextBounds(movePrice).height / 2)); 184 | } 185 | 186 | ///draw lines for the background which uses to measures the spaces 187 | /// width is size of device's width and height is so on 188 | void _drawScaleLine(Canvas canvas) { 189 | resetPaintStyle( 190 | color: scaleLineColor, 191 | strokeWidth: 0.2, 192 | paintingStyle: PaintingStyle.fill); 193 | //vertical scale line 194 | _verticalXList.clear(); 195 | double _horizontalSpace = (_rightEnd - _leftStart - 50) / 4; 196 | for (int i = 0; i < 5; i++) { 197 | canvas.drawLine( 198 | Offset(_leftStart + _horizontalSpace * i, _topStart), 199 | Offset(_leftStart + _horizontalSpace * i, _bottomEnd - dp2px(6.0)), 200 | _paint); 201 | _verticalXList.add(_leftStart + _horizontalSpace * i); 202 | } 203 | //horizontal scale line 204 | _horizontalYList.clear(); 205 | _verticalSpace = (_bottomEnd - _topStart - dp2px(6.0)) / 5; 206 | double _horizontalRightEnd; 207 | for (int i = 0; i < 6; i++) { 208 | if (i == 0 || i == 5 || i == 4 || (isShowSubview && i == 3)) { 209 | _horizontalRightEnd = _rightEnd; 210 | } else { 211 | _horizontalRightEnd = _verticalXList[_verticalXList.length - 1]; 212 | } 213 | canvas.drawLine(Offset(_leftStart, _topStart + _verticalSpace * i), 214 | Offset(_horizontalRightEnd, _topStart + _verticalSpace * i), _paint); 215 | _horizontalYList.add(_topStart + _verticalSpace * i); 216 | } 217 | //subview's top line 218 | _subViewTopY = _horizontalYList[4] + dp2px(5.0); 219 | double dx = _verticalXList[_verticalXList.length - 1]; 220 | double dy = _horizontalYList[4] + _verticalSpace / 2 + dp2px(5.0); 221 | canvas.drawLine(Offset(_leftStart, dy), Offset(dx, dy), _paint); 222 | //value's middle scale line 223 | if (isShowSubview) { 224 | double dx = _verticalXList[_verticalXList.length - 1]; 225 | double dy = _horizontalYList[3] + _verticalSpace / 2 + dp2px(5.0); 226 | canvas.drawLine(Offset(_leftStart, dy), Offset(dx, dy), _paint); 227 | } 228 | } 229 | 230 | /// main view 231 | void _drawMainChartView(Canvas canvas) { 232 | //perWidth =(leftStart - rightEnd) / maxViewData 233 | _perPriceRectWidth = 234 | (_verticalXList[_verticalXList.length - 1] - _verticalXList[0]) / 235 | maxViewDataNum; 236 | //max and min price 237 | _maxPrice = viewDataList[0].maxPrice; 238 | _minPrice = viewDataList[0].minPrice; 239 | _maxVolume = viewDataList[0].volume; 240 | _maxMACD = viewDataList[0].macd; 241 | _minMACD = viewDataList[0].macd; 242 | double maxDEA = viewDataList[0].dea; 243 | double minDEA = viewDataList[0].dea; 244 | double maxDIF = viewDataList[0].dif; 245 | double minDIF = viewDataList[0].dif; 246 | _maxK = viewDataList[0].k; 247 | double maxD = viewDataList[0].d; 248 | double maxJ = viewDataList[0].j; 249 | 250 | for (int i = 0; i < viewDataList.length; i++) { 251 | viewDataList[i].setLeftStartX(_verticalXList[_verticalXList.length - 1] - 252 | (viewDataList.length - i) * _perPriceRectWidth); 253 | viewDataList[i].setRightEndX(_verticalXList[_verticalXList.length - 1] - 254 | (viewDataList.length - i - 1) * _perPriceRectWidth); 255 | // max price 256 | if (viewDataList[i].maxPrice >= _maxPrice) { 257 | _maxPrice = viewDataList[i].maxPrice; 258 | _maxPriceX = viewDataList[i].leftStartX + _perPriceRectWidth / 2; 259 | } 260 | // min price 261 | if (viewDataList[i].minPrice <= _minPrice) { 262 | _minPrice = viewDataList[i].minPrice; 263 | _minPriceX = viewDataList[i].leftStartX + _perPriceRectWidth / 2; 264 | } 265 | // max volume 266 | if (viewDataList[i].volume >= _maxVolume) { 267 | _maxVolume = viewDataList[i].volume; 268 | } 269 | 270 | if (isShowSubview && subviewType == 0) { 271 | if (viewDataList[i].macd >= _maxMACD) { 272 | _maxMACD = viewDataList[i].macd; 273 | } 274 | if (viewDataList[i].macd <= _minMACD) { 275 | _minMACD = viewDataList[i].macd; 276 | } 277 | if (viewDataList[i].dea >= maxDEA) { 278 | maxDEA = viewDataList[i].dea; 279 | } 280 | if (viewDataList[i].dea <= minDEA) { 281 | minDEA = viewDataList[i].dea; 282 | } 283 | if (viewDataList[i].dif >= maxDIF) { 284 | maxDIF = viewDataList[i].dif; 285 | } 286 | if (viewDataList[i].dif <= minDIF) { 287 | minDIF = viewDataList[i].dif; 288 | } 289 | } else if (isShowSubview && subviewType == 1) { 290 | if (viewDataList[i].k >= _maxK) { 291 | _maxK = viewDataList[i].k; 292 | } 293 | if (viewDataList[i].d >= maxD) { 294 | maxD = viewDataList[i].d; 295 | } 296 | if (viewDataList[i].j >= maxJ) { 297 | maxJ = viewDataList[i].j; 298 | } 299 | } 300 | } 301 | _topPrice = _maxPrice + (_maxPrice - _minPrice) * 0.1; 302 | _botPrice = _minPrice - (_maxPrice - _minPrice) * 0.1; 303 | //show the subview 304 | if (!isShowSubview) { 305 | _priceChartBottom = _horizontalYList[4]; 306 | _volumeChartBottom = _horizontalYList[5]; 307 | } else { 308 | _priceChartBottom = _horizontalYList[3]; 309 | _volumeChartBottom = _horizontalYList[4]; 310 | } 311 | //price data 312 | _perPriceRectHeight = 313 | (_priceChartBottom - _horizontalYList[0]) / (_topPrice - _botPrice); 314 | _maxPriceY = 315 | _horizontalYList[0] + (_topPrice - _maxPrice) * _perPriceRectHeight; 316 | _minPriceY = 317 | _horizontalYList[0] + (_topPrice - _minPrice) * _perPriceRectHeight; 318 | //volume data 319 | _perVolumeRectHeight = 320 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / 321 | _maxVolume; 322 | // subview 323 | if (isShowSubview && subviewType == 0) { 324 | // macd 325 | if (_maxMACD > 0 && _minMACD < 0) { 326 | _perMACDHeight = 327 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / 328 | (_maxMACD - _minMACD).abs(); 329 | _subviewCenterY = _subViewTopY + _maxMACD * _perMACDHeight; 330 | } else if (_maxMACD <= 0) { 331 | _perMACDHeight = 332 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / 333 | _minMACD.abs(); 334 | _subviewCenterY = _subViewTopY; 335 | } else if (_maxMACD >= 0) { 336 | _perMACDHeight = 337 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / 338 | _maxMACD.abs(); 339 | _subviewCenterY = _horizontalYList[_horizontalYList.length - 1]; 340 | } 341 | //dea 342 | if (maxDEA > 0 && minDEA < 0) { 343 | _perDEAHeight = 344 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / 345 | (maxDEA - minDEA); 346 | } else if (maxDEA <= 0) { 347 | _perDEAHeight = 348 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / 349 | minDEA.abs(); 350 | } else if (minDEA >= 0) { 351 | _perDEAHeight = 352 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / 353 | maxDEA.abs(); 354 | } 355 | //dif 356 | if (maxDIF > 0 && minDIF < 0) { 357 | _perDifHeight = 358 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / 359 | (maxDIF - minDIF); 360 | } else if (maxDIF <= 0) { 361 | _perDifHeight = 362 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / 363 | minDEA.abs(); 364 | } else if (minDIF >= 0) { 365 | _perDifHeight = 366 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / 367 | maxDIF.abs(); 368 | } 369 | } else if (isShowSubview && subviewType == 1) { 370 | _kHeight = 371 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / 372 | _maxK; 373 | _dHeight = 374 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / maxD; 375 | _jHeight = 376 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / maxJ; 377 | } else if (isShowSubview && subviewType == 2) { 378 | _rsiHeight = 379 | (_horizontalYList[_horizontalYList.length - 1] - _subViewTopY) / 100; 380 | } 381 | 382 | for (int i = 0; i < viewDataList.length; i++) { 383 | double _openPrice = viewDataList[i].openPrice; 384 | double _closePrice = viewDataList[i].closePrice; 385 | double _higherPrice; 386 | double _lowerPrice; 387 | if (_openPrice >= _closePrice) { 388 | _higherPrice = _openPrice; 389 | _lowerPrice = _closePrice; 390 | resetPaintStyle(color: fallColor, paintingStyle: PaintingStyle.fill); 391 | } else { 392 | _higherPrice = _closePrice; 393 | _lowerPrice = _openPrice; 394 | resetPaintStyle(color: riseColor, paintingStyle: PaintingStyle.fill); 395 | } 396 | viewDataList[i].setCloseY(_horizontalYList[0] + 397 | (_topPrice - _closePrice) * _perPriceRectHeight); 398 | viewDataList[i].setOpenY( 399 | _horizontalYList[0] + (_topPrice - _openPrice) * _perPriceRectHeight); 400 | // price rect 401 | Rect priceRect = Rect.fromLTRB( 402 | viewDataList[i].leftStartX + dp2px(0.2), 403 | _maxPriceY + (_maxPrice - _higherPrice) * _perPriceRectHeight, 404 | viewDataList[i].rightEndX - dp2px(0.2), 405 | _maxPriceY + (_maxPrice - _lowerPrice) * _perPriceRectHeight); 406 | canvas.drawRect(priceRect, _paint); 407 | // price line 408 | canvas.drawLine( 409 | Offset( 410 | viewDataList[i].leftStartX + _perPriceRectWidth / 2, 411 | _maxPriceY + 412 | (_maxPrice - viewDataList[i].maxPrice) * _perPriceRectHeight), 413 | Offset( 414 | viewDataList[i].leftStartX + _perPriceRectWidth / 2, 415 | _maxPriceY + 416 | (_maxPrice - viewDataList[i].minPrice) * _perPriceRectHeight), 417 | _paint); 418 | // volume rect 419 | Rect volumeRect = Rect.fromLTRB( 420 | viewDataList[i].leftStartX + dp2px(0.2), 421 | _volumeChartBottom - viewDataList[i].volume * _perVolumeRectHeight, 422 | viewDataList[i].rightEndX - dp2px(0.2), 423 | _volumeChartBottom); 424 | canvas.drawRect(volumeRect, _paint); 425 | // macd 426 | double w = (viewDataList[i].leftStartX + viewDataList[i].rightEndX) / 2; 427 | if (isShowSubview && subviewType == 0) { 428 | double macd = viewDataList[i].macd; 429 | if (macd > 0) { 430 | resetPaintStyle(color: riseColor); 431 | canvas.drawLine(Offset(w, _subviewCenterY - macd * _perMACDHeight), 432 | Offset(w, _subviewCenterY), _paint); 433 | } else { 434 | resetPaintStyle(color: fallColor); 435 | canvas.drawLine(Offset(w, _subviewCenterY), 436 | Offset(w, _subviewCenterY + macd.abs() * _perMACDHeight), _paint); 437 | } 438 | } 439 | } 440 | } 441 | 442 | /// draw bezier line 443 | void _drawBezierCurve(Canvas canvas) { 444 | mainMa5PointList.clear(); 445 | mainMa10PointList.clear(); 446 | mainMa30PointList.clear(); 447 | volumeMa5PointList.clear(); 448 | volumeMa10PointList.clear(); 449 | subviewMA5List.clear(); 450 | subviewMA10List.clear(); 451 | subviewMA30List.clear(); 452 | 453 | for (int i = 0; i < viewDataList.length; i++) { 454 | // volume 455 | Pointer volumeMa5Pointer = Pointer(); 456 | if (viewDataList[i].volumeMA5 != null) { 457 | volumeMa5Pointer.setX(viewDataList[i].leftStartX); 458 | volumeMa5Pointer.setY(_volumeChartBottom - 459 | viewDataList[i].volumeMA5 * _perVolumeRectHeight); 460 | volumeMa5PointList.add(volumeMa5Pointer); 461 | } 462 | Pointer volumeMa10Pointer = Pointer(); 463 | if (viewDataList[i].volumeMA10 != null) { 464 | volumeMa10Pointer.setX(viewDataList[i].leftStartX); 465 | volumeMa10Pointer.setY(_volumeChartBottom - 466 | viewDataList[i].volumeMA10 * _perVolumeRectHeight); 467 | volumeMa10PointList.add(volumeMa10Pointer); 468 | } 469 | switch (viewType) { 470 | case 0: 471 | // price 472 | Pointer priceMa5Pointer = Pointer(); 473 | if (viewDataList[i].priceMA5 != null) { 474 | priceMa5Pointer.setX(viewDataList[i].leftStartX); 475 | priceMa5Pointer.setY(_maxPriceY + 476 | (_maxPrice - viewDataList[i].priceMA5) * _perPriceRectHeight); 477 | mainMa5PointList.add(priceMa5Pointer); 478 | } 479 | Pointer priceMa10Pointer = Pointer(); 480 | if (viewDataList[i].priceMA10 != null) { 481 | priceMa10Pointer.setX(viewDataList[i].leftStartX); 482 | priceMa10Pointer.setY(_maxPriceY + 483 | (_maxPrice - viewDataList[i].priceMA10) * _perPriceRectHeight); 484 | mainMa10PointList.add(priceMa10Pointer); 485 | } 486 | Pointer priceMa30Pointer = Pointer(); 487 | if (viewDataList[i].priceMA30 != null) { 488 | priceMa30Pointer.setX(viewDataList[i].leftStartX); 489 | priceMa30Pointer.setY(_maxPriceY + 490 | (_maxPrice - viewDataList[i].priceMA30) * _perPriceRectHeight); 491 | mainMa30PointList.add(priceMa30Pointer); 492 | } 493 | break; 494 | case 1: 495 | Pointer bollMBPointer = Pointer(); 496 | if (viewDataList[i].bollMB != null) { 497 | bollMBPointer.setX(viewDataList[i].leftStartX); 498 | bollMBPointer.setY(_maxPriceY + 499 | (_maxPrice - viewDataList[i].bollMB) * _perPriceRectHeight); 500 | mainMa5PointList.add(bollMBPointer); 501 | } 502 | Pointer bollUPPointer = Pointer(); 503 | if (viewDataList[i].bollUP != null) { 504 | bollUPPointer.setX(viewDataList[i].leftStartX); 505 | bollUPPointer.setY(_maxPriceY + 506 | (_maxPrice - viewDataList[i].bollUP) * _perPriceRectHeight); 507 | mainMa10PointList.add(bollUPPointer); 508 | } 509 | Pointer bollDNPointer = Pointer(); 510 | if (viewDataList[i].bollDN != null) { 511 | bollDNPointer.setX(viewDataList[i].leftStartX); 512 | bollDNPointer.setY(_maxPriceY + 513 | (_maxPrice - viewDataList[i].bollDN) * _perPriceRectHeight); 514 | mainMa30PointList.add(bollDNPointer); 515 | } 516 | break; 517 | case 2: 518 | break; 519 | } 520 | 521 | if (isShowSubview && subviewType == 0) { 522 | Pointer difPoint = Pointer(); 523 | if (viewDataList[i].dif != null) { 524 | difPoint.setX(viewDataList[i].leftStartX + _perPriceRectWidth / 2); 525 | difPoint.setY(_subviewCenterY - viewDataList[i].dif * _perDifHeight); 526 | } else { 527 | difPoint.setX(viewDataList[i].leftStartX + _perPriceRectWidth / 2); 528 | difPoint.setY( 529 | _subviewCenterY - (viewDataList[i].dif * _perDifHeight).abs()); 530 | } 531 | subviewMA5List.add(difPoint); 532 | 533 | Pointer deaPoint = Pointer(); 534 | if (viewDataList[i].dif != null) { 535 | deaPoint.setX(viewDataList[i].leftStartX + _perPriceRectWidth / 2); 536 | deaPoint.setY(_subviewCenterY - viewDataList[i].dea * _perDEAHeight); 537 | } else { 538 | deaPoint.setX(viewDataList[i].leftStartX + _perPriceRectWidth / 2); 539 | deaPoint.setY(_subviewCenterY - viewDataList[i].dea * _perDEAHeight); 540 | } 541 | subviewMA10List.add(deaPoint); 542 | } else if (isShowSubview && subviewType == 1) { 543 | Pointer kPoint = Pointer(); 544 | if (viewDataList[i].k > 0) { 545 | kPoint.setX(viewDataList[i].leftStartX + _perPriceRectWidth / 2); 546 | kPoint.setY(_horizontalYList[5] - viewDataList[i].k * _kHeight); 547 | subviewMA5List.add(kPoint); 548 | } 549 | Pointer dPoint = Pointer(); 550 | if (viewDataList[i].d > 0) { 551 | dPoint.setX(viewDataList[i].leftStartX + _perPriceRectWidth / 2); 552 | dPoint.setY(_horizontalYList[5] - viewDataList[i].d * _dHeight); 553 | subviewMA10List.add(dPoint); 554 | } 555 | Pointer jPoint = Pointer(); 556 | if (viewDataList[i].j > 0) { 557 | jPoint.setX(viewDataList[i].leftStartX + _perPriceRectWidth / 2); 558 | jPoint.setY(_horizontalYList[5] - viewDataList[i].j * _jHeight); 559 | subviewMA30List.add(jPoint); 560 | } 561 | } else if (isShowSubview && subviewType == 2) { 562 | Pointer rs1Point = Pointer(); 563 | if (viewDataList[i].rs1 != null) { 564 | rs1Point.setX(viewDataList[i].leftStartX + _perPriceRectWidth / 2); 565 | rs1Point.setY(_horizontalYList[5] - viewDataList[i].rs1 * _rsiHeight); 566 | subviewMA5List.add(rs1Point); 567 | } 568 | Pointer rs2Point = Pointer(); 569 | if (viewDataList[i].rs2 != null) { 570 | rs2Point.setX(viewDataList[i].leftStartX + _perPriceRectWidth / 2); 571 | rs2Point.setY(_horizontalYList[5] - viewDataList[i].rs2 * _rsiHeight); 572 | subviewMA10List.add(rs2Point); 573 | } 574 | Pointer rs3Point = Pointer(); 575 | if (viewDataList[i].rs3 != null) { 576 | rs3Point.setX(viewDataList[i].leftStartX + _perPriceRectWidth / 2); 577 | rs3Point.setY(_horizontalYList[5] - viewDataList[i].rs3 * _rsiHeight); 578 | subviewMA30List.add(rs3Point); 579 | } 580 | } 581 | } 582 | _drawMainBezierCurve(canvas); 583 | _drawVolumeBezierCurve(canvas); 584 | if (isShowSubview) { 585 | _drawSubviewCurve(canvas); 586 | } 587 | } 588 | 589 | void _drawMainBezierCurve(Canvas canvas) { 590 | ///ma5 591 | _chartCalculator.setBezierPath(mainMa5PointList, path); 592 | resetPaintStyle(color: ma5Color, strokeWidth: 1); 593 | canvas.drawPath(path, _paint); 594 | 595 | ///ma10 596 | _chartCalculator.setBezierPath(mainMa10PointList, path); 597 | resetPaintStyle(color: ma10Color, strokeWidth: 1); 598 | canvas.drawPath(path, _paint); 599 | 600 | ///ma30 601 | _chartCalculator.setBezierPath(mainMa30PointList, path); 602 | resetPaintStyle(color: ma30Color, strokeWidth: 1); 603 | canvas.drawPath(path, _paint); 604 | } 605 | 606 | void _drawVolumeBezierCurve(Canvas canvas) { 607 | // ma5 608 | _chartCalculator.setBezierPath(volumeMa5PointList, path); 609 | resetPaintStyle(color: ma5Color, strokeWidth: 1); 610 | canvas.drawPath(path, _paint); 611 | // ma 10 612 | _chartCalculator.setBezierPath(volumeMa10PointList, path); 613 | resetPaintStyle(color: ma10Color, strokeWidth: 1); 614 | canvas.drawPath(path, _paint); 615 | } 616 | 617 | void _drawSubviewCurve(Canvas canvas) { 618 | // 5 619 | _chartCalculator.setLinePath(subviewMA5List, path); 620 | resetPaintStyle(color: ma5Color); 621 | canvas.drawPath(path, _paint); 622 | // 10 623 | _chartCalculator.setLinePath(subviewMA10List, path); 624 | resetPaintStyle(color: ma10Color); 625 | canvas.drawPath(path, _paint); 626 | // 30 627 | _chartCalculator.setLinePath(subviewMA30List, path); 628 | resetPaintStyle(color: ma30Color); 629 | canvas.drawPath(path, _paint); 630 | } 631 | 632 | /// draw max and min price text 633 | void _drawMaxAndMinPriceText(Canvas canvas) { 634 | resetPaintStyle(color: Colors.white); 635 | // max price text 636 | String _maxPriceText = formatDataNum(_maxPrice); 637 | double _maxPriceTextX; 638 | if (_maxPriceX + _getTextBounds(_maxPriceText).width + dp2px(5.0) < 639 | _verticalXList[_verticalXList.length - 1]) { 640 | _maxPriceTextX = _maxPriceX + dp2px(5.0); 641 | canvas.drawLine(Offset(_maxPriceX, _maxPriceY), 642 | Offset(_maxPriceTextX, _maxPriceY), _paint); 643 | } else { 644 | _maxPriceTextX = 645 | _maxPriceX - _getTextBounds(_maxPriceText).width - dp2px(5); 646 | canvas.drawLine(Offset(_maxPriceX - dp2px(5.0), _maxPriceY), 647 | Offset(_maxPriceX, _maxPriceY), _paint); 648 | } 649 | // max text 650 | _drawText( 651 | canvas, 652 | _maxPriceText, 653 | Colors.white, 654 | Offset(_maxPriceTextX, 655 | _maxPriceY - _getTextBounds(_maxPriceText).height / 2)); 656 | // min price text 657 | String _minPriceText = formatDataNum(_minPrice); 658 | double _minPriceTextX; 659 | if (_minPriceX + _getTextBounds(_minPriceText).width + dp2px(5.0) < 660 | _verticalXList[_verticalXList.length - 1]) { 661 | _minPriceTextX = _minPriceX + dp2px(5.0); 662 | canvas.drawLine(Offset(_minPriceTextX - dp2px(5.0), _minPriceY), 663 | Offset(_minPriceTextX, _minPriceY), _paint); 664 | } else { 665 | _minPriceTextX = 666 | _minPriceX - _getTextBounds(_minPriceText).width - dp2px(5.0); 667 | canvas.drawLine(Offset(_minPriceX - dp2px(5.0), _minPriceY), 668 | Offset(_minPriceX, _minPriceY), _paint); 669 | } 670 | // min text 671 | _drawText( 672 | canvas, 673 | _minPriceText, 674 | Colors.white, 675 | Offset(_minPriceTextX, 676 | _minPriceY - _getTextBounds(_minPriceText).height / 2)); 677 | } 678 | 679 | /// draw abscissa scale text 680 | void _drawAbscissaText(Canvas canvas) { 681 | for (int i = 0; i < _verticalXList.length; i++) { 682 | if (i == 0 && 683 | viewDataList[0].leftStartX <= 684 | _verticalXList[0] + _perPriceRectWidth / 2 && 685 | viewDataList[0].rightEndX > _verticalXList[0]) { 686 | String timestamp = dateFormat(viewDataList[0].timestamp); 687 | _drawText(canvas, timestamp, scaleTextColor, 688 | Offset(_leftStart, _horizontalYList[_horizontalYList.length - 1])); 689 | } else if (i == _verticalXList.length - 1) { 690 | String dateTime = 691 | dateFormat(viewDataList[viewDataList.length - 1].timestamp); 692 | _drawText( 693 | canvas, 694 | dateTime, 695 | scaleTextColor, 696 | Offset(_verticalXList[_verticalXList.length - 1] - 10, 697 | _horizontalYList[_horizontalYList.length - 1])); 698 | } else { 699 | for (ChartModel data in viewDataList) { 700 | if (data.leftStartX <= _verticalXList[i] && 701 | data.rightEndX >= _verticalXList[i]) { 702 | String dateTime = dateFormat(data.timestamp); 703 | _drawText( 704 | canvas, 705 | dateTime, 706 | scaleTextColor, 707 | Offset(_verticalXList[i] - 10, 708 | _horizontalYList[_horizontalYList.length - 1])); 709 | break; 710 | } 711 | } 712 | } 713 | } 714 | } 715 | 716 | /// draw ordinate scale text 717 | void _drawOrdinateText(Canvas canvas) { 718 | // text start x point 719 | double _rightX = _verticalXList[_verticalXList.length - 1] + dp2px(1.0); 720 | 721 | /// price scale text 722 | // max price 723 | String _maxPriceText = formatDataNum(_topPrice); 724 | _drawText(canvas, _maxPriceText, scaleTextColor, 725 | Offset(_rightX, _horizontalYList[0])); 726 | // min price 727 | String _minPriceText = formatDataNum(_botPrice); 728 | _drawText( 729 | canvas, 730 | _minPriceText, 731 | scaleTextColor, 732 | Offset( 733 | _rightX, _priceChartBottom - _getTextBounds(_minPriceText).height)); 734 | // average price 735 | if (!isShowSubview) { 736 | double avgPrice = (_topPrice - _botPrice) / 4; 737 | for (int i = 0; i < 3; i++) { 738 | String price = formatDataNum(_topPrice - avgPrice * (i + 1)); 739 | _drawText( 740 | canvas, 741 | price, 742 | scaleTextColor, 743 | Offset(_rightX, 744 | _horizontalYList[i + 1] - _getTextBounds(price).height / 2)); 745 | } 746 | } else { 747 | double avgPrice = (_topPrice - _botPrice) / 3; 748 | for (int i = 0; i < 2; i++) { 749 | String price = formatDataNum(_topPrice - avgPrice * (i + 1)); 750 | _drawText( 751 | canvas, 752 | price, 753 | scaleTextColor, 754 | Offset(_rightX, 755 | _horizontalYList[i + 1] - _getTextBounds(price).height / 2)); 756 | } 757 | String topSubviewText = ""; 758 | String centerSubviewText = ""; 759 | String botSubviewText = ""; 760 | if (subviewType == 0) { 761 | if (_maxMACD > 0 && _minMACD < 0) { 762 | topSubviewText = "${setPrecision(_maxMACD, 2)}"; 763 | centerSubviewText = "${setPrecision((_maxMACD - _minMACD) / 2, 2)}"; 764 | botSubviewText = "${setPrecision(_minMACD, 2)}"; 765 | } else if (_maxMACD <= 0) { 766 | topSubviewText = "0.0"; 767 | centerSubviewText = "${setPrecision((_minMACD - _maxMACD) / 2, 2)}"; 768 | botSubviewText = "${setPrecision(_minMACD, 2)}"; 769 | } else if (_minMACD >= 0) { 770 | topSubviewText = "${setPrecision(_maxMACD, 2)}"; 771 | centerSubviewText = "${setPrecision((_maxMACD - _minMACD) / 2, 2)}"; 772 | botSubviewText = "0"; 773 | } 774 | } else if (subviewType == 1) { 775 | topSubviewText = "${formatDataNum(_maxK)}"; 776 | centerSubviewText = "${formatDataNum(_maxK / 2)}"; 777 | botSubviewText = "0.0"; 778 | } else if (subviewType == 2) { 779 | topSubviewText = "100.0"; 780 | centerSubviewText = "50.0"; 781 | botSubviewText = "0.0"; 782 | } 783 | _drawText( 784 | canvas, 785 | topSubviewText, 786 | scaleTextColor, 787 | Offset(_verticalXList[_verticalXList.length - 1], 788 | _horizontalYList[_horizontalYList.length - 2])); 789 | _drawText( 790 | canvas, 791 | centerSubviewText, 792 | scaleTextColor, 793 | Offset( 794 | _verticalXList[_verticalXList.length - 1], 795 | _horizontalYList[_horizontalYList.length - 1] - 796 | _verticalSpace / 2 - 797 | _getTextBounds(centerSubviewText).height / 2 + 798 | dp2px(5.0))); 799 | _drawText( 800 | canvas, 801 | botSubviewText, 802 | scaleTextColor, 803 | Offset( 804 | _verticalXList[_verticalXList.length - 1], 805 | _horizontalYList[_horizontalYList.length - 1] - 806 | _getTextBounds(centerSubviewText).height)); 807 | } 808 | 809 | /// volume scale text 810 | // max volume 811 | String _maxVolumeText = setPrecision(_maxVolume, 2); 812 | _drawText( 813 | canvas, 814 | _maxVolumeText, 815 | scaleTextColor, 816 | Offset( 817 | _rightX, 818 | _priceChartBottom, 819 | )); 820 | // middle volume 821 | String _middleVolume = setPrecision(_maxVolume / 2, 2); 822 | _drawText( 823 | canvas, 824 | _middleVolume, 825 | scaleTextColor, 826 | Offset( 827 | _rightX, 828 | _volumeChartBottom - 829 | _verticalSpace / 2 - 830 | _getTextBounds(_middleVolume).height / 2 + 831 | dp2px(5.0))); 832 | // bottom volume 833 | String _bottomVolume = "0.00"; 834 | _drawText( 835 | canvas, 836 | _bottomVolume, 837 | scaleTextColor, 838 | Offset(_rightX, 839 | _volumeChartBottom - _getTextBounds(_bottomVolume).height)); 840 | } 841 | 842 | /// draw top text 843 | void _drawTopText(Canvas canvas) { 844 | if (lastData == null) { 845 | return; 846 | } 847 | String _indexTopTextOne; 848 | String _indexTopTextTwo; 849 | String _indexTopTextThree; 850 | switch (viewType) { 851 | case 0: 852 | if (lastData.priceMA5 != null) { 853 | _indexTopTextOne = "MA5:${formatDataNum(lastData.priceMA5)}"; 854 | } 855 | if (lastData.priceMA10 != null) { 856 | _indexTopTextTwo = "MA10:${formatDataNum(lastData.priceMA10)}"; 857 | } 858 | if (lastData.priceMA30 != null) { 859 | _indexTopTextThree = "MA30:${formatDataNum(lastData.priceMA30)}"; 860 | } 861 | break; 862 | case 1: 863 | if (lastData.bollMB != null) { 864 | _indexTopTextOne = "BOLL:${formatDataNum(lastData.bollMB)}"; 865 | } 866 | if (lastData.bollUP != null) { 867 | _indexTopTextTwo = "UB:${formatDataNum(lastData.bollUP)}"; 868 | } 869 | if (lastData.bollDN != null) { 870 | _indexTopTextThree = "LB:${formatDataNum(lastData.bollDN)}"; 871 | } 872 | break; 873 | case 2: 874 | break; 875 | } 876 | if (lastData.priceMA5 != null) { 877 | _drawText( 878 | canvas, 879 | _indexTopTextOne, 880 | ma5Color, 881 | Offset(_leftStart, 882 | _topStart - _getTextBounds(_indexTopTextOne).height - 1)); 883 | } 884 | if (lastData.priceMA10 != null) { 885 | _drawText( 886 | canvas, 887 | _indexTopTextTwo, 888 | ma10Color, 889 | Offset( 890 | _leftStart + _getTextBounds(_indexTopTextOne).width + dp2px(5.0), 891 | _topStart - _getTextBounds(_indexTopTextOne).height - 1)); 892 | } 893 | if (lastData.priceMA30 != null) { 894 | _drawText( 895 | canvas, 896 | _indexTopTextThree, 897 | ma30Color, 898 | Offset( 899 | _leftStart + 900 | _getTextBounds(_indexTopTextOne).width + 901 | _getTextBounds(_indexTopTextTwo).width + 902 | dp2px(10.0), 903 | _topStart - _getTextBounds(_indexTopTextOne).height - 1)); 904 | } 905 | } 906 | 907 | /// draw volume text 908 | void _drawVolumeText(Canvas canvas) { 909 | if (lastData == null) { 910 | return; 911 | } 912 | String _volumeText = ""; 913 | String _volumeMA5 = ""; 914 | String _volumeMA10 = ""; 915 | if (lastData.volume != null) { 916 | _volumeText = "VOL:${formatDataNum(lastData.volume)}"; 917 | _drawText(canvas, _volumeText, ma30Color, 918 | Offset(_verticalXList[0], _priceChartBottom)); 919 | } 920 | if (lastData.volumeMA5 != null) { 921 | _volumeMA5 = "MA5:${formatDataNum(lastData.volumeMA5)}"; 922 | _drawText( 923 | canvas, 924 | _volumeMA5, 925 | ma5Color, 926 | Offset( 927 | _verticalXList[0] + 928 | _getTextBounds(_volumeText).width + 929 | dp2px(5.0), 930 | _priceChartBottom)); 931 | } 932 | if (lastData.volumeMA10 != null) { 933 | _volumeMA10 = "MA10:${formatDataNum(lastData.volumeMA10)}"; 934 | _drawText( 935 | canvas, 936 | _volumeMA10, 937 | ma10Color, 938 | Offset( 939 | _verticalXList[0] + 940 | _getTextBounds(_volumeText).width + 941 | _getTextBounds(_volumeMA5).width + 942 | dp2px(10.0), 943 | _priceChartBottom)); 944 | } 945 | 946 | String titleText = ""; 947 | String firstText = ""; 948 | String secondText = ""; 949 | String thirdText = ""; 950 | 951 | if (isShowSubview && subviewType == 0) { 952 | titleText = "MACD(12,26,9)"; 953 | if (lastData.macd != null) { 954 | firstText = "MACD:" + "${formatDataNum(lastData.macd)}"; 955 | } 956 | if (lastData.dif != null) { 957 | secondText = "DIF:" + "${formatDataNum(lastData.dif)}"; 958 | } 959 | if (lastData.dea != null) { 960 | thirdText = "DEA:" + "${formatDataNum(lastData.dea)}"; 961 | } 962 | } else if (isShowSubview && subviewType == 1) { 963 | titleText = "KDJ(9,3,3)"; 964 | if (lastData.k != null) { 965 | firstText = "K:" + setPrecision(lastData.k, 2); 966 | } 967 | if (lastData.d != null) { 968 | secondText = "D:" + setPrecision(lastData.d, 2); 969 | } 970 | if (lastData.j != null) { 971 | thirdText = "J:" + setPrecision(lastData.j, 2); 972 | } 973 | } else if (isShowSubview && subviewType == 2) { 974 | titleText = "RSI(6,12,24)"; 975 | if (lastData.rs1 != null) { 976 | firstText = "RSI1:" + setPrecision(lastData.rs1, 2); 977 | } 978 | if (lastData.rs2 != null) { 979 | secondText = "RSI2:" + setPrecision(lastData.rs2, 2); 980 | } 981 | if (lastData.rs3 != null) { 982 | thirdText = "RSI3:" + setPrecision(lastData.rs3, 2); 983 | } 984 | } 985 | _drawText(canvas, titleText, scaleTextColor, 986 | Offset(_verticalXList[0], _horizontalYList[4])); 987 | 988 | if (lastData.macd != null || lastData.k != null || lastData.rs1 != null) { 989 | _drawText( 990 | canvas, 991 | firstText, 992 | ma5Color, 993 | Offset(_verticalXList[0] + _getTextBounds(titleText).width + dp2px(5), 994 | _horizontalYList[4])); 995 | } 996 | if (lastData.dif != null || lastData.d != null || lastData.rs2 != null) { 997 | _drawText( 998 | canvas, 999 | secondText, 1000 | ma10Color, 1001 | Offset( 1002 | _verticalXList[0] + 1003 | _getTextBounds(titleText).width + 1004 | _getTextBounds(firstText).width + 1005 | dp2px(10), 1006 | _horizontalYList[4])); 1007 | } 1008 | if (lastData.dea != null || lastData.j != null || lastData.rs3 != null) { 1009 | _drawText( 1010 | canvas, 1011 | thirdText, 1012 | ma30Color, 1013 | Offset( 1014 | _verticalXList[0] + 1015 | _getTextBounds(titleText).width + 1016 | _getTextBounds(firstText).width + 1017 | _getTextBounds(secondText).width + 1018 | dp2px(15), 1019 | _horizontalYList[4])); 1020 | } 1021 | } 1022 | 1023 | /// draw cross line 1024 | void _drawCrossHairLine(Canvas canvas) { 1025 | if (lastData == null || isShowDetails == false) { 1026 | return; 1027 | } 1028 | // vertical line 1029 | resetPaintStyle(color: scaleLineColor); 1030 | canvas.drawLine( 1031 | Offset( 1032 | lastData.leftStartX + _perPriceRectWidth / 2, _horizontalYList[0]), 1033 | Offset(lastData.leftStartX + _perPriceRectWidth / 2, 1034 | _horizontalYList[_horizontalYList.length - 1]), 1035 | _paint); 1036 | // horizontal line 1037 | resetPaintStyle(color: scaleLineColor); 1038 | double moveY = lastData.closeY; 1039 | 1040 | if (moveY < _horizontalYList[0]) { 1041 | moveY = _horizontalYList[0]; 1042 | } else if (moveY > _priceChartBottom) { 1043 | moveY = _priceChartBottom; 1044 | } 1045 | 1046 | canvas.drawLine(Offset(_verticalXList[0], moveY), 1047 | Offset(_verticalXList[_verticalXList.length - 1], moveY), _paint); 1048 | // bottom label 1049 | Rect bottomRect = Rect.fromLTRB( 1050 | lastData.leftStartX + _perPriceRectWidth / 2 - dp2px(15), 1051 | _bottomEnd - 20, 1052 | lastData.leftStartX + _perPriceRectWidth / 2 + dp2px(15), 1053 | _bottomEnd); 1054 | resetPaintStyle(color: Colors.black, paintingStyle: PaintingStyle.fill); 1055 | canvas.drawRect(bottomRect, _paint); 1056 | // bottom text 1057 | String moveTime = dateFormat(lastData.timestamp); 1058 | _drawText( 1059 | canvas, 1060 | moveTime, 1061 | Colors.white, 1062 | Offset( 1063 | lastData.leftStartX + 1064 | dp2px(1.0) + 1065 | _perPriceRectWidth / 2 - 1066 | _getTextBounds(moveTime).width / 2, 1067 | _bottomEnd - 15)); 1068 | // left label 1069 | String movePrice = setPrecision(lastData.closePrice, 2); 1070 | Rect leftRect = Rect.fromLTRB( 1071 | _verticalXList[_verticalXList.length - 1], 1072 | moveY + _getTextBounds(movePrice).height, 1073 | _rightEnd, 1074 | moveY - _getTextBounds(movePrice).height); 1075 | canvas.drawRect(leftRect, _paint); 1076 | // left text 1077 | _drawText( 1078 | canvas, 1079 | movePrice, 1080 | Colors.white, 1081 | Offset(_verticalXList[_verticalXList.length - 1] + dp2px(1.0), 1082 | moveY - _getTextBounds(movePrice).height / 2)); 1083 | } 1084 | 1085 | /// draw details 1086 | void _drawDetails(Canvas canvas) { 1087 | if (lastData == null || !isShowDetails) { 1088 | return; 1089 | } 1090 | Color detailTextColor = Colors.white; 1091 | double rectWidth = 120; 1092 | double _detailRectHeight = 128; 1093 | if (lastData.leftStartX + _perPriceRectWidth / 2 <= 1094 | _verticalXList[_verticalXList.length - 1] / 2) { 1095 | // right 1096 | Rect rightRect = Rect.fromLTRB( 1097 | _verticalXList[_verticalXList.length - 1] - rectWidth, 1098 | _horizontalYList[0], 1099 | _verticalXList[_verticalXList.length - 1], 1100 | _horizontalYList[0] + _detailRectHeight); 1101 | canvas.drawRect(rightRect, _paint); 1102 | // rect line 1103 | resetPaintStyle(color: scaleLineColor); 1104 | canvas.drawLine( 1105 | Offset( 1106 | _verticalXList[_verticalXList.length - 1], _horizontalYList[0]), 1107 | Offset(_verticalXList[_verticalXList.length - 1], 1108 | _horizontalYList[0] + _detailRectHeight), 1109 | _paint); 1110 | canvas.drawLine( 1111 | Offset( 1112 | _verticalXList[_verticalXList.length - 1], _horizontalYList[0]), 1113 | Offset(_verticalXList[_verticalXList.length - 1] - rectWidth, 1114 | _horizontalYList[0]), 1115 | _paint); 1116 | canvas.drawLine( 1117 | Offset(_verticalXList[_verticalXList.length - 1] - rectWidth, 1118 | _horizontalYList[0]), 1119 | Offset(_verticalXList[_verticalXList.length - 1] - rectWidth, 1120 | _horizontalYList[0] + _detailRectHeight), 1121 | _paint); 1122 | canvas.drawLine( 1123 | Offset(_verticalXList[_verticalXList.length - 1], 1124 | _horizontalYList[0] + _detailRectHeight), 1125 | Offset(_verticalXList[_verticalXList.length - 1] - rectWidth, 1126 | _horizontalYList[0] + _detailRectHeight), 1127 | _paint); 1128 | // detail title 1129 | for (int i = 0; i < detailTitleCN.length; i++) { 1130 | _drawText( 1131 | canvas, 1132 | detailTitleCN[i], 1133 | detailTextColor, 1134 | Offset(_verticalXList[_verticalXList.length - 1] - rectWidth + 3, 1135 | _horizontalYList[0] + _detailRectHeight / 8 * i)); 1136 | } 1137 | // detail data 1138 | double upDownAmount = lastData.closePrice - lastData.openPrice; 1139 | for (int i = 0; i < detailDataList.length; i++) { 1140 | if (i == 5 || i == 6) { 1141 | if (upDownAmount > 0) { 1142 | detailTextColor = riseColor; 1143 | } else { 1144 | detailTextColor = fallColor; 1145 | } 1146 | } else { 1147 | detailTextColor = Colors.white; 1148 | } 1149 | _drawText( 1150 | canvas, 1151 | detailDataList[i], 1152 | detailTextColor, 1153 | Offset( 1154 | _verticalXList[_verticalXList.length - 1] - 1155 | _getTextBounds(detailDataList[i]).width - 1156 | 3, 1157 | _horizontalYList[0] + _detailRectHeight / 8 * i)); 1158 | } 1159 | } else { 1160 | // left 1161 | Rect leftRect = Rect.fromLTRB( 1162 | _verticalXList[0], 1163 | _horizontalYList[0], 1164 | _verticalXList[0] + rectWidth, 1165 | _horizontalYList[0] + _detailRectHeight); 1166 | canvas.drawRect(leftRect, _paint); 1167 | // rect line 1168 | resetPaintStyle(color: scaleLineColor); 1169 | canvas.drawLine( 1170 | Offset(_verticalXList[0], _horizontalYList[0]), 1171 | Offset(_verticalXList[0], _horizontalYList[0] + _detailRectHeight), 1172 | _paint); 1173 | canvas.drawLine(Offset(_verticalXList[0], _horizontalYList[0]), 1174 | Offset(_verticalXList[0] + rectWidth, _horizontalYList[0]), _paint); 1175 | canvas.drawLine( 1176 | Offset(_verticalXList[0] + rectWidth, _horizontalYList[0]), 1177 | Offset(_verticalXList[0] + rectWidth, 1178 | _horizontalYList[0] + _detailRectHeight), 1179 | _paint); 1180 | canvas.drawLine( 1181 | Offset(_verticalXList[0], _horizontalYList[0] + _detailRectHeight), 1182 | Offset(_verticalXList[0] + rectWidth, 1183 | _horizontalYList[0] + _detailRectHeight), 1184 | _paint); 1185 | // detail title 1186 | double upDownAmount = lastData.closePrice - lastData.openPrice; 1187 | for (int i = 0; i < detailTitleCN.length; i++) { 1188 | _drawText( 1189 | canvas, 1190 | detailTitleCN[i], 1191 | detailTextColor, 1192 | Offset(_verticalXList[0] + 3, 1193 | _horizontalYList[0] + _detailRectHeight / 8 * i)); 1194 | } 1195 | // detail data 1196 | for (int i = 0; i < detailDataList.length; i++) { 1197 | if (i == 5 || i == 6) { 1198 | if (upDownAmount > 0) { 1199 | detailTextColor = riseColor; 1200 | } else { 1201 | detailTextColor = fallColor; 1202 | } 1203 | } else { 1204 | detailTextColor = Colors.white; 1205 | } 1206 | _drawText( 1207 | canvas, 1208 | detailDataList[i], 1209 | detailTextColor, 1210 | Offset( 1211 | _verticalXList[0] + 1212 | rectWidth - 1213 | _getTextBounds(detailDataList[i]).width - 1214 | 3, 1215 | _horizontalYList[0] + _detailRectHeight / 8 * i)); 1216 | } 1217 | } 1218 | } 1219 | 1220 | /// draw text 1221 | void _drawText(Canvas canvas, String text, Color textColor, Offset offset) { 1222 | TextPainter _textPainter = TextPainter( 1223 | text: TextSpan( 1224 | text: text, 1225 | style: TextStyle( 1226 | color: textColor, 1227 | fontSize: 10.0, 1228 | fontWeight: FontWeight.normal, 1229 | ), 1230 | ), 1231 | maxLines: 1, 1232 | textDirection: TextDirection.ltr, 1233 | ); 1234 | _textPainter.layout(); 1235 | _textPainter.paint(canvas, offset); 1236 | } 1237 | 1238 | ///draw style 1239 | void resetPaintStyle( 1240 | {@required Color color, 1241 | double strokeWidth, 1242 | PaintingStyle paintingStyle}) { 1243 | _paint 1244 | ..color = color 1245 | ..strokeWidth = strokeWidth ?? 1.0 1246 | ..isAntiAlias = true 1247 | ..style = paintingStyle ?? PaintingStyle.stroke; 1248 | } 1249 | 1250 | /// precision 1251 | String setPrecision(double num, int scale) { 1252 | return num.toStringAsFixed(scale); 1253 | } 1254 | 1255 | String formatDataNum(double num) { 1256 | if (num < 1) { 1257 | return setPrecision(num, 6); 1258 | } else if (num < 10) { 1259 | return setPrecision(num, 5); 1260 | } else if (num < 100) { 1261 | return setPrecision(num, 4); 1262 | } else { 1263 | return setPrecision(num, 2); 1264 | } 1265 | } 1266 | 1267 | /// date format 1268 | String dateFormat(int timestamp) { 1269 | List dateList = 1270 | DateTime.fromMillisecondsSinceEpoch(timestamp).toString().split(" "); 1271 | List date = dateList[0].toString().split("-"); 1272 | List time = dateList[1].toString().split(":"); 1273 | String format = "${date[1]}-${date[2]} ${time[0]}:${time[1]}"; 1274 | return format; 1275 | } 1276 | 1277 | /// size of text 1278 | Size _getTextBounds(String text, {double fontSize}) { 1279 | TextPainter _textPainter = TextPainter( 1280 | text: TextSpan( 1281 | text: text, 1282 | style: TextStyle( 1283 | fontSize: fontSize ?? 10.0, 1284 | ), 1285 | ), 1286 | textDirection: TextDirection.ltr); 1287 | _textPainter.layout(); 1288 | return Size(_textPainter.width, _textPainter.height); 1289 | } 1290 | 1291 | /// dp to px 1292 | double dp2px(double dp) { 1293 | double scale = window.devicePixelRatio; 1294 | return dp * scale; 1295 | } 1296 | 1297 | double px2dp(double px) { 1298 | double scale = window.devicePixelRatio; 1299 | return px / scale; 1300 | } 1301 | 1302 | @override 1303 | bool shouldRepaint(CustomPainter oldDelegate) { 1304 | // TODO: implement shouldRepaint 1305 | return true; 1306 | } 1307 | } 1308 | -------------------------------------------------------------------------------- /lib/chart/chart_utils.dart: -------------------------------------------------------------------------------- 1 | class ChartUtils { 2 | /// date format 3 | String dateFormat(int timestamp, {bool year}) { 4 | List dateList = 5 | DateTime.fromMillisecondsSinceEpoch(timestamp).toString().split(" "); 6 | List date = dateList[0].toString().split("-"); 7 | List time = dateList[1].toString().split(":"); 8 | String format = "${date[1]}-${date[2]} ${time[0]}:${time[1]}"; 9 | if (year ?? false) { 10 | format = "${date[0]}-${date[1]}-${date[2]} ${time[0]}:${time[1]}"; 11 | } 12 | return format; 13 | } 14 | 15 | /// precision 16 | String setPrecision(double num, int scale) { 17 | return num.toStringAsFixed(scale); 18 | } 19 | 20 | String formatDataNum(double num) { 21 | if (num < 1) { 22 | return setPrecision(num, 6); 23 | } else if (num < 10) { 24 | return setPrecision(num, 5); 25 | } else if (num < 100) { 26 | return setPrecision(num, 4); 27 | } else { 28 | return setPrecision(num, 2); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/chart/kline_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'chart_calculator.dart'; 6 | import 'chart_model.dart'; 7 | import 'chart_painter.dart'; 8 | import 'chart_utils.dart'; 9 | 10 | class KlineView extends StatefulWidget { 11 | KlineView( 12 | {this.dataList, 13 | this.isShowSubview: false, 14 | this.viewType: 0, 15 | this.subviewType: 0, 16 | this.currentDataType}); 17 | 18 | final List dataList; 19 | final bool isShowSubview; 20 | final int viewType; 21 | final int subviewType; 22 | final String currentDataType; 23 | 24 | @override 25 | _KlineViewState createState() => _KlineViewState(); 26 | } 27 | 28 | class _KlineViewState extends State { 29 | int _startDataNum = 0; 30 | int _maxViewDataNum = 50; 31 | int _viewDataMin = 10; 32 | int _viewDataMax = 100; 33 | double _velocityX; 34 | bool _isShowDetail = false; 35 | ChartModel _lastData; 36 | ChartCalculator _chartCalculator = ChartCalculator(); 37 | ChartUtils _chartUtils = ChartUtils(); 38 | List _totalDataList = List(); 39 | List _endDataList = List(); 40 | List _viewDataList = List(); 41 | List _detailDataList = List(); 42 | String _currentData; 43 | 44 | @override 45 | void didUpdateWidget(KlineView oldWidget) { 46 | super.didUpdateWidget(oldWidget); 47 | if (_startDataNum >= _totalDataList.length - _maxViewDataNum) { 48 | initDataList(); 49 | _currentData = widget.currentDataType; 50 | } else { 51 | if (_currentData != widget.currentDataType) { 52 | initDataList(); 53 | } 54 | } 55 | } 56 | 57 | /// init data list 58 | Future initDataList() async { 59 | _totalDataList.clear(); 60 | _totalDataList.addAll(widget.dataList); 61 | _startDataNum = _totalDataList.length - _maxViewDataNum; 62 | _calculateIndex(_totalDataList, false); 63 | setState(() { 64 | _resetViewData(); 65 | }); 66 | } 67 | 68 | /// add one data 69 | Future addSingleData() async { 70 | _endDataList.clear(); 71 | int startIndex; 72 | if (_totalDataList.length >= _maxViewDataNum) { 73 | startIndex = _totalDataList.length - _maxViewDataNum; 74 | } else { 75 | startIndex = 0; 76 | } 77 | _endDataList 78 | .addAll(_totalDataList.sublist(startIndex, _totalDataList.length)); 79 | _endDataList.add(widget.dataList[widget.dataList.length - 1]); 80 | _calculateIndex(_endDataList, true); 81 | _totalDataList.add(_endDataList[_endDataList.length - 1]); 82 | if (_totalDataList.length >= _maxViewDataNum && 83 | _startDataNum == _totalDataList.length - _maxViewDataNum - 1) { 84 | setState(() { 85 | _startDataNum++; 86 | _resetViewData(); 87 | }); 88 | } else { 89 | setState(() { 90 | _resetViewData(); 91 | }); 92 | } 93 | } 94 | 95 | /// calculate index 96 | Future _calculateIndex(List dataList, bool isEndData) async { 97 | _chartCalculator.calculateMa(dataList, isEndData); 98 | _chartCalculator.calculateBoll(dataList, 26, 2, isEndData); 99 | _chartCalculator.calculateMACD(dataList, 12, 26, 9, isEndData); 100 | _chartCalculator.calculateKDJ(dataList, 9, 3, 3, isEndData); 101 | _chartCalculator.calculateRSI(dataList, 6, 12, 24, isEndData); 102 | } 103 | 104 | /// display data 105 | void _resetViewData() { 106 | _viewDataList.clear(); 107 | int _currentViewDataNum = min(_maxViewDataNum, _totalDataList.length); 108 | if (_startDataNum >= 0) { 109 | for (int i = 0; i < _currentViewDataNum; i++) { 110 | if (i + _startDataNum < _totalDataList.length) { 111 | _viewDataList.add(_totalDataList[i + _startDataNum]); 112 | } 113 | } 114 | } else { 115 | for (int i = 0; i < _currentViewDataNum; i++) { 116 | _viewDataList.add(_totalDataList[i]); 117 | } 118 | } 119 | if (_viewDataList.length > 0 && !_isShowDetail) { 120 | _lastData = _viewDataList[_viewDataList.length - 1]; 121 | } else if (_viewDataList.isEmpty) { 122 | _lastData = null; 123 | } 124 | } 125 | 126 | /// get click data 127 | void _getClickData(double clickX) { 128 | if (_isShowDetail) { 129 | _detailDataList.clear(); 130 | for (int i = 0; i < _viewDataList.length; i++) { 131 | if (_viewDataList[i].leftStartX <= clickX && 132 | _viewDataList[i].rightEndX >= clickX) { 133 | _lastData = _viewDataList[i]; 134 | _detailDataList 135 | .add(_chartUtils.dateFormat(_lastData.timestamp, year: true)); 136 | _detailDataList.add(_lastData.openPrice.toString()); 137 | _detailDataList.add(_lastData.maxPrice.toString()); 138 | _detailDataList.add(_lastData.closePrice.toString()); 139 | _detailDataList.add(_lastData.minPrice.toString()); 140 | double upDownAmount = _lastData.closePrice - _lastData.openPrice; 141 | String upDownRate = _chartUtils.setPrecision( 142 | upDownAmount / _lastData.openPrice * 100, 2); 143 | if (upDownAmount > 0) { 144 | _detailDataList.add("+" + _chartUtils.formatDataNum(upDownAmount)); 145 | _detailDataList.add("+" + upDownRate + "%"); 146 | } else { 147 | _detailDataList.add(_chartUtils.formatDataNum(upDownAmount)); 148 | _detailDataList.add(upDownRate + "%"); 149 | } 150 | _detailDataList.add(_chartUtils.formatDataNum(_lastData.volume)); 151 | break; 152 | } else { 153 | _lastData = null; 154 | } 155 | } 156 | } else { 157 | _lastData = _viewDataList[_viewDataList.length - 1]; 158 | } 159 | } 160 | 161 | /// tap down 162 | void _onTapDown(TapDownDetails details) { 163 | double moveX = details.globalPosition.dx; 164 | if (_viewDataList[0].leftStartX <= moveX && 165 | _viewDataList[_viewDataList.length - 1].rightEndX >= moveX) { 166 | setState(() { 167 | _isShowDetail = true; 168 | _getClickData(moveX); 169 | }); 170 | } 171 | } 172 | 173 | /// long press move 174 | void _onLongPress(LongPressMoveUpdateDetails details) { 175 | double moveX = details.globalPosition.dx; 176 | if (_viewDataList[0].leftStartX <= moveX && 177 | _viewDataList[_viewDataList.length - 1].rightEndX >= moveX) { 178 | setState(() { 179 | _isShowDetail = true; 180 | _getClickData(moveX); 181 | }); 182 | } 183 | } 184 | 185 | /// scale 186 | void _onScaleUpdate(ScaleUpdateDetails details) { 187 | if (details.scale > 1) { 188 | if (_maxViewDataNum <= _viewDataMin) { 189 | _maxViewDataNum = _viewDataMin; 190 | } else if (_viewDataList.length < _maxViewDataNum) { 191 | _maxViewDataNum -= 2; 192 | _startDataNum = _totalDataList.length - _maxViewDataNum; 193 | } else { 194 | _maxViewDataNum -= 2; 195 | _startDataNum += 1; 196 | } 197 | } else if (details.scale < 1) { 198 | if (_maxViewDataNum >= _viewDataMax) { 199 | _maxViewDataNum = _viewDataMax; 200 | } else if (_startDataNum + _maxViewDataNum >= _totalDataList.length) { 201 | _maxViewDataNum += 2; 202 | _startDataNum = _totalDataList.length - _maxViewDataNum; 203 | } else if (_startDataNum <= 0) { 204 | _startDataNum = 0; 205 | _maxViewDataNum += 2; 206 | } else { 207 | _maxViewDataNum += 2; 208 | _startDataNum -= 1; 209 | } 210 | } 211 | setState(() { 212 | _isShowDetail = false; 213 | _resetViewData(); 214 | }); 215 | } 216 | 217 | /// horizontal gesture 218 | void _moveHorizontal(DragUpdateDetails details) { 219 | double _distanceX = details.delta.dx * -1; 220 | if ((_startDataNum == 0 && _distanceX < 0) || 221 | (_startDataNum == _totalDataList.length - _maxViewDataNum && 222 | _distanceX > 0) || 223 | _startDataNum < 0 || 224 | _viewDataList.length < _maxViewDataNum) { 225 | if (_isShowDetail) { 226 | setState(() { 227 | _isShowDetail = false; 228 | if (_viewDataList.isNotEmpty) { 229 | _lastData = _viewDataList[_viewDataList.length - 1]; 230 | } 231 | }); 232 | } 233 | } else { 234 | setState(() { 235 | _isShowDetail = false; 236 | if (_distanceX.abs() > 1) { 237 | _moveData(_distanceX); 238 | } 239 | }); 240 | } 241 | } 242 | 243 | /// move data 244 | void _moveData(double distanceX) { 245 | if (_maxViewDataNum < 50) { 246 | _setSpeed(distanceX, 10); 247 | } else { 248 | _setSpeed(distanceX, 3.5); 249 | } 250 | if (_startDataNum < 0) { 251 | _startDataNum = 0; 252 | } 253 | if (_startDataNum > _totalDataList.length - _maxViewDataNum) { 254 | _startDataNum = _totalDataList.length - _maxViewDataNum; 255 | } 256 | _resetViewData(); 257 | } 258 | 259 | /// move speed 260 | void _setSpeed(double distanceX, double num) { 261 | // print(distanceX); 262 | if (distanceX.abs() > 1 && distanceX.abs() < 2) { 263 | _startDataNum += (distanceX * 10 - (distanceX * 10 ~/ 2) * 2).round(); 264 | } else if (distanceX.abs() < 10) { 265 | _startDataNum += (distanceX - (distanceX ~/ 2) * 2).toInt(); 266 | } else { 267 | _startDataNum += distanceX ~/ num; 268 | } 269 | } 270 | 271 | /// move velocity 272 | void _moveVelocity(DragEndDetails details) { 273 | if (_startDataNum > 0 && 274 | _startDataNum < _totalDataList.length - _maxViewDataNum) { 275 | if (details.velocity.pixelsPerSecond.dx > 6000) { 276 | _velocityX = 8000; 277 | } else if (details.velocity.pixelsPerSecond.dx < -6000) { 278 | _velocityX = -8000; 279 | } else { 280 | _velocityX = details.velocity.pixelsPerSecond.dx; 281 | } 282 | _moveAnimation(); 283 | } 284 | } 285 | 286 | /// move animation 287 | void _moveAnimation() { 288 | if (_velocityX < -200) { 289 | if (_velocityX < -6000) { 290 | _startDataNum += 6; 291 | } else if (_velocityX < -4000) { 292 | _startDataNum += 5; 293 | } else if (_velocityX < -2500) { 294 | _startDataNum += 4; 295 | } else if (_velocityX < -1000) { 296 | _startDataNum += 3; 297 | } else { 298 | _startDataNum++; 299 | } 300 | _velocityX += 200; 301 | if (_startDataNum > _totalDataList.length - _maxViewDataNum) { 302 | _startDataNum = _totalDataList.length - _maxViewDataNum; 303 | } 304 | } else if (_velocityX > 200) { 305 | if (_velocityX > 6000) { 306 | _startDataNum -= 6; 307 | } else if (_velocityX > 4000) { 308 | _startDataNum -= 5; 309 | } else if (_velocityX > 2500) { 310 | _startDataNum -= 4; 311 | } else if (_velocityX > 1000) { 312 | _startDataNum -= 3; 313 | } else { 314 | _startDataNum--; 315 | } 316 | _velocityX -= 200; 317 | if (_startDataNum < 0) { 318 | _startDataNum = 0; 319 | } 320 | } 321 | // reset view data 322 | setState(() { 323 | _resetViewData(); 324 | }); 325 | // stop when velocity less than 200 326 | if (_velocityX.abs() > 200) { 327 | // recursion and delayed 15 milliseconds 328 | Future.delayed(Duration(milliseconds: 15), () => _moveAnimation()); 329 | } 330 | } 331 | 332 | /// painter 333 | CustomPaint _klineView() { 334 | return CustomPaint( 335 | painter: ChartPainter( 336 | viewDataList: _viewDataList, 337 | maxViewDataNum: _maxViewDataNum, 338 | lastData: _lastData, 339 | detailDataList: _detailDataList, 340 | isShowDetails: _isShowDetail, 341 | isShowSubview: widget.isShowSubview, 342 | viewType: widget.viewType, 343 | subviewType: widget.subviewType, 344 | )); 345 | } 346 | 347 | /// build kline chart 348 | @override 349 | Widget build(BuildContext context) { 350 | /// gestures 351 | return GestureDetector( 352 | onTapDown: _onTapDown, 353 | onLongPressMoveUpdate: _onLongPress, 354 | onHorizontalDragUpdate: _moveHorizontal, 355 | onHorizontalDragEnd: _moveVelocity, 356 | onScaleUpdate: _onScaleUpdate, 357 | child: Container( 358 | color: Color(0xFF101928), 359 | width: MediaQuery.of(context).size.width, 360 | height: 368.0, 361 | child: _klineView(), 362 | ), 363 | ); 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /lib/depth/depth_model.dart: -------------------------------------------------------------------------------- 1 | class DepthModel implements Comparable { 2 | double price; 3 | double volume; 4 | int tradeType; 5 | double x; 6 | double y; 7 | 8 | DepthModel(double price, double volume) { 9 | this.price = price; 10 | this.volume = volume; 11 | } 12 | 13 | void setPrice(double price) { 14 | this.price = price; 15 | } 16 | 17 | void setVolume(double volume) { 18 | this.volume = volume; 19 | } 20 | 21 | void setX(double x) { 22 | this.x = x; 23 | } 24 | 25 | void setY(double y) { 26 | this.y = y; 27 | } 28 | 29 | @override 30 | int compareTo(DepthModel other) { 31 | double diff = this.price - other.price; 32 | if (diff > 0) { 33 | return 1; 34 | } else if (diff < 0) { 35 | return -1; 36 | } else { 37 | return 0; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/depth/depth_painter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'depth_model.dart'; 5 | 6 | class DepthPainter extends CustomPainter { 7 | DepthPainter({ 8 | this.buyDataList, 9 | this.sellDataList, 10 | this.pricePrecision: 4, 11 | }); 12 | final List buyDataList; 13 | final List sellDataList; 14 | final int pricePrecision; 15 | 16 | /// 17 | Path _path = Path(); 18 | Paint _paint = Paint(); 19 | 20 | /// colors 21 | Color _textColor = Colors.grey; 22 | Color _buyLineColor = Color(0xFF2BB8AB); 23 | Color _buyBackgroundColor = Color(0x662BB8AB); 24 | Color _sellLineColor = Color(0xFFFF5442); 25 | Color _sellBackgroundColor = Color(0x66FF5442); 26 | 27 | /// 28 | double _maxVolume; 29 | double _avgVolumeSpace; 30 | double _avgOrinateSpace; 31 | String _rightPriceText; 32 | String _leftPriceText; 33 | 34 | /// 35 | double _bottomEnd; 36 | 37 | void _setLayout(Size size) { 38 | _bottomEnd = size.height - 10; 39 | // buy 40 | double maxBuyVolume; 41 | double minBuyVolume; 42 | if (buyDataList.isNotEmpty) { 43 | maxBuyVolume = buyDataList[0].volume; 44 | minBuyVolume = buyDataList[buyDataList.length - 1].volume; 45 | } else { 46 | maxBuyVolume = minBuyVolume = 0; 47 | } 48 | // sell 49 | double maxSellVolume; 50 | double minSellVolume; 51 | if (sellDataList.isNotEmpty) { 52 | maxSellVolume = sellDataList[sellDataList.length - 1].volume; 53 | minSellVolume = sellDataList[0].volume; 54 | } else { 55 | maxSellVolume = minSellVolume = 0; 56 | } 57 | _maxVolume = max(maxBuyVolume, maxSellVolume); 58 | double minVolume = min(minBuyVolume, minSellVolume); 59 | 60 | if (buyDataList.isNotEmpty) { 61 | _leftPriceText = setPrecision(buyDataList[0].price, pricePrecision); 62 | } else if (sellDataList.isNotEmpty) { 63 | _leftPriceText = setPrecision(buyDataList[0].price, pricePrecision); 64 | } else { 65 | _leftPriceText = "0.0"; 66 | } 67 | 68 | if (sellDataList.isNotEmpty) { 69 | _rightPriceText = setPrecision( 70 | sellDataList[sellDataList.length - 1].price, pricePrecision); 71 | } else if (buyDataList.isNotEmpty) { 72 | _rightPriceText = setPrecision( 73 | buyDataList[buyDataList.length - 1].price, pricePrecision); 74 | } else { 75 | _rightPriceText = "0.0"; 76 | } 77 | 78 | double perHeight = _bottomEnd / (_maxVolume - minVolume); 79 | double perWidth = size.width / (buyDataList.length + sellDataList.length); 80 | _avgVolumeSpace = _maxVolume / 5; 81 | _avgOrinateSpace = _bottomEnd / 5; 82 | // buy 83 | for (int i = 0; i < buyDataList.length; i++) { 84 | buyDataList[i].setX(perWidth * i); 85 | buyDataList[i].setY((_maxVolume - buyDataList[i].volume) * perHeight); 86 | } 87 | // sell 88 | for (int i = sellDataList.length - 1; i >= 0; i--) { 89 | sellDataList[i] 90 | .setX(size.width - perWidth * (sellDataList.length - 1 - i)); 91 | sellDataList[i].setY((_maxVolume - sellDataList[i].volume) * perHeight); 92 | } 93 | } 94 | 95 | @override 96 | void paint(Canvas canvas, Size size) { 97 | if (buyDataList.isEmpty && sellDataList.isEmpty) { 98 | return; 99 | } 100 | _setLayout(size); 101 | _drawDepthTitle(canvas, size); 102 | _drawLineAndBackground(canvas, size); 103 | _drawCoordinateText(canvas, size); 104 | } 105 | 106 | void _drawDepthTitle(Canvas canvas, Size size) { 107 | // buy 108 | String buyText = "买盘"; 109 | double textWidth = _getTextBounds(buyText).width; 110 | Rect buyRect = Rect.fromLTRB(size.width / 2 - textWidth, 12, 111 | size.width / 2 - textWidth - 10, textWidth + 2); 112 | _restPainter(_buyLineColor, 1, paintingStyle: PaintingStyle.fill); 113 | canvas.drawRect(buyRect, _paint); 114 | _drawText( 115 | canvas, buyText, _textColor, Offset(size.width / 2 - textWidth, 10)); 116 | // sell 117 | String sellText = "卖盘"; 118 | Rect sellRect = Rect.fromLTRB(size.width / 2 + textWidth, 12, 119 | size.width / 2 + textWidth - 10, textWidth + 2); 120 | _restPainter(_sellLineColor, 1, paintingStyle: PaintingStyle.fill); 121 | canvas.drawRect(sellRect, _paint); 122 | _drawText( 123 | canvas, sellText, _textColor, Offset(size.width / 2 + textWidth, 10)); 124 | } 125 | 126 | void _drawLineAndBackground(Canvas canvas, Size size) { 127 | // buy background 128 | if (buyDataList.isNotEmpty) { 129 | _path.reset(); 130 | for (int i = 0; i < buyDataList.length; i++) { 131 | if (i == 0) { 132 | _path.moveTo(buyDataList[i].x, buyDataList[i].y); 133 | } else { 134 | _path.lineTo(buyDataList[i].x, buyDataList[i].y); 135 | } 136 | } 137 | if (buyDataList.isNotEmpty && 138 | buyDataList[buyDataList.length - 1].y < _bottomEnd.toInt()) { 139 | _path.lineTo(buyDataList[buyDataList.length - 1].x, _bottomEnd); 140 | } 141 | _path.lineTo(0, _bottomEnd); 142 | _path.close(); 143 | _restPainter(_buyBackgroundColor, 1, paintingStyle: PaintingStyle.fill); 144 | canvas.drawPath(_path, _paint); 145 | // buy line 146 | _path.reset(); 147 | for (int i = 0; i < buyDataList.length; i++) { 148 | if (i == 0) { 149 | _path.moveTo(buyDataList[i].x, buyDataList[i].y); 150 | } else { 151 | _path.lineTo(buyDataList[i].x, buyDataList[i].y); 152 | } 153 | } 154 | _restPainter(_buyLineColor, 1); 155 | canvas.drawPath(_path, _paint); 156 | } 157 | // sell background 158 | if (sellDataList.isNotEmpty) { 159 | _path.reset(); 160 | for (int i = sellDataList.length - 1; i >= 0; i--) { 161 | if (i == sellDataList.length - 1) { 162 | _path.moveTo(sellDataList[i].x, sellDataList[i].y); 163 | } else { 164 | _path.lineTo(sellDataList[i].x, sellDataList[i].y); 165 | } 166 | } 167 | if (sellDataList.isNotEmpty && sellDataList[0].y < _bottomEnd) { 168 | _path.lineTo(sellDataList[0].x, _bottomEnd); 169 | } 170 | _path.lineTo(size.width, _bottomEnd); 171 | _path.close(); 172 | _restPainter(_sellBackgroundColor, 1, paintingStyle: PaintingStyle.fill); 173 | canvas.drawPath(_path, _paint); 174 | // sell line 175 | _path.reset(); 176 | for (int i = 0; i < sellDataList.length; i++) { 177 | if (i == 0) { 178 | _path.moveTo(sellDataList[i].x, sellDataList[i].y); 179 | } else { 180 | _path.lineTo(sellDataList[i].x, sellDataList[i].y); 181 | } 182 | } 183 | _restPainter(_sellLineColor, 1); 184 | canvas.drawPath(_path, _paint); 185 | } 186 | } 187 | 188 | /// scale text 189 | void _drawCoordinateText(Canvas canvas, Size size) { 190 | _drawText(canvas, _leftPriceText, _textColor, Offset(0, _bottomEnd)); 191 | _drawText(canvas, _rightPriceText, _textColor, 192 | Offset(size.width - _getTextBounds(_rightPriceText).width, _bottomEnd)); 193 | _drawText( 194 | canvas, 195 | formatDataNum( 196 | (double.parse(_rightPriceText) - double.parse(_leftPriceText)) / 2), 197 | _textColor, 198 | Offset(size.width / 2 - _getTextBounds("0").width, _bottomEnd)); 199 | 200 | for (int i = 0; i < 5; i++) { 201 | String ordinateStr = formatDataNum(_maxVolume - i * _avgVolumeSpace); 202 | _drawText( 203 | canvas, 204 | ordinateStr, 205 | _textColor, 206 | Offset(size.width - _getTextBounds(ordinateStr).width, 207 | _getTextBounds(ordinateStr).height + i * _avgOrinateSpace)); 208 | } 209 | } 210 | 211 | /// draw text 212 | void _drawText(Canvas canvas, String text, Color textColor, Offset offset) { 213 | TextPainter _textPainter = TextPainter( 214 | text: TextSpan( 215 | text: text, 216 | style: TextStyle( 217 | color: textColor, 218 | fontSize: 10.0, 219 | fontWeight: FontWeight.normal, 220 | ), 221 | ), 222 | maxLines: 1, 223 | textDirection: TextDirection.ltr, 224 | ); 225 | _textPainter.layout(); 226 | _textPainter.paint(canvas, offset); 227 | } 228 | 229 | /// size of text 230 | Size _getTextBounds(String text, {double fontSize}) { 231 | TextPainter _textPainter = TextPainter( 232 | text: TextSpan( 233 | text: text, 234 | style: TextStyle( 235 | fontSize: fontSize ?? 10.0, 236 | ), 237 | ), 238 | textDirection: TextDirection.ltr); 239 | _textPainter.layout(); 240 | return Size(_textPainter.width, _textPainter.height); 241 | } 242 | 243 | /// precision 244 | String setPrecision(double num, int scale) { 245 | return num.toStringAsFixed(scale); 246 | } 247 | 248 | String formatDataNum(double num) { 249 | if (num < 1) { 250 | return setPrecision(num, 6); 251 | } else if (num < 10) { 252 | return setPrecision(num, 5); 253 | } else if (num < 100) { 254 | return setPrecision(num, 4); 255 | } else { 256 | return setPrecision(num, 2); 257 | } 258 | } 259 | 260 | void _restPainter(Color color, double strokeWidth, 261 | {PaintingStyle paintingStyle}) { 262 | _paint 263 | ..color = color 264 | ..isAntiAlias = true 265 | ..strokeWidth = strokeWidth 266 | ..style = paintingStyle ?? PaintingStyle.stroke; 267 | } 268 | 269 | @override 270 | bool shouldRepaint(CustomPainter oldDelegate) { 271 | return true; 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /lib/depth/depth_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'depth_model.dart'; 4 | import 'depth_painter.dart'; 5 | 6 | class DepthView extends StatefulWidget { 7 | DepthView( 8 | this.bidsList, 9 | this.asksList 10 | ); 11 | final List bidsList; 12 | final List asksList; 13 | @override 14 | _DepthViewState createState() => _DepthViewState(); 15 | } 16 | 17 | class _DepthViewState extends State { 18 | List _buyDataList = List(); 19 | List _sellDataList = List(); 20 | 21 | @override 22 | void didUpdateWidget(DepthView oldWidget) { 23 | super.didUpdateWidget(oldWidget); 24 | setState(() { 25 | setBuyDataList(widget.bidsList); 26 | setSellDataList(widget.asksList); 27 | }); 28 | } 29 | void setBuyDataList(List bidsList) { 30 | _buyDataList.clear(); 31 | _buyDataList.addAll(bidsList); 32 | _buyDataList.sort(); 33 | for (int i = _buyDataList.length - 1; i >= 0; i--) { 34 | if (i < _buyDataList.length - 1) { 35 | _buyDataList[i].setVolume(_buyDataList[i].volume + _buyDataList[i + 1].volume); 36 | } 37 | } 38 | } 39 | void setSellDataList(List asksList) { 40 | _sellDataList.clear(); 41 | _sellDataList.addAll(asksList); 42 | _sellDataList.sort(); 43 | for (int i = 0; i < _sellDataList.length; i++) { 44 | if (i > 0) { 45 | _sellDataList[i].setVolume(_sellDataList[i].volume + _sellDataList[i - 1].volume); 46 | } 47 | } 48 | } 49 | CustomPaint depthView() { 50 | return CustomPaint(painter: DepthPainter(buyDataList:_buyDataList, sellDataList: _sellDataList)); 51 | } 52 | @override 53 | Widget build(BuildContext context) { 54 | return Container( 55 | color: Color(0xFF131e30), 56 | height: 200, 57 | width: MediaQuery.of(context).size.width, 58 | child: depthView(), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/dio_util.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:dio/dio.dart'; 3 | import 'package:flutter/material.dart'; 4 | class DioUtil { 5 | static Dio dio = Dio(); 6 | 7 | 8 | /// get 9 | static Future get(String url) async { 10 | dio.options.baseUrl = "https://www.peppa.me"; 11 | try { 12 | Response response = await dio.get(url); 13 | return response.data; 14 | } catch(error) { 15 | debugPrint(error.toString()); 16 | } finally { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /lib/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:kchart/chart/chart_model.dart'; 3 | import 'package:kchart/chart/kline_view.dart'; 4 | import 'package:kchart/depth/depth_view.dart'; 5 | import 'package:kchart/kline_datas.dart'; 6 | 7 | import 'depth/depth_model.dart'; 8 | import 'dio_util.dart'; 9 | import 'dio_util.dart'; 10 | 11 | class Example extends StatefulWidget { 12 | @override 13 | _ExampleState createState() => _ExampleState(); 14 | } 15 | 16 | class _ExampleState extends State with TickerProviderStateMixin { 17 | List _timeIndex = [ 18 | "1min", 19 | "5min", 20 | "15min", 21 | "30min", 22 | "60min", 23 | "1day", 24 | "1week", 25 | "1mon" 26 | ]; 27 | List dataList = List(); 28 | Color text = Color(0xFF6d88a5); 29 | TabController _tabController; 30 | bool _isShowMenu = false; 31 | bool _isShowView = false; 32 | bool _isShowSubview = false; 33 | int _viewTypeIndex = 0; 34 | int _subviewTypeIndex = 0; 35 | 36 | /// k线实时请求,防止刷新之后返回到最右边 37 | String _currentDataType; 38 | 39 | /// 深度图 40 | List _bidsList = List(); 41 | List _asksList = List(); 42 | 43 | /// 44 | List lines = List(); 45 | 46 | @override 47 | void initState() { 48 | super.initState(); 49 | _tabController = TabController(length: 8, vsync: this, initialIndex: 0); 50 | getKDataList(0); 51 | getDepthList(); 52 | } 53 | 54 | @override 55 | void dispose() { 56 | super.dispose(); 57 | _tabController.dispose(); 58 | } 59 | 60 | /// 最后一条数据需要实时展示的话 61 | /// 时间不相同就更新 62 | // int oldTime = 0; 63 | // List lastKlineData = List(); 64 | // Future updateLastData() async { 65 | // Map parsedData = await DioUtil.get("https://-----------------------"); 66 | // lastKlineData.add(parsedData); 67 | // int currentTime = parsedData["timestamp"]; 68 | // if (currentTime != oldTime) { 69 | // oldTime = parsedData["timestamp"]; 70 | // getKDataList(0); 71 | // } else { 72 | // if (dataList.isNotEmpty && lastKlineData.isNotEmpty) { 73 | // dataList.last.openPrice = getKlineDataList(lastKlineData)[0].openPrice; 74 | // dataList.last.closePrice = 75 | // getKlineDataList(lastKlineData)[0].closePrice; 76 | // dataList.last.maxPrice = getKlineDataList(lastKlineData)[0].maxPrice; 77 | // dataList.last.minPrice = getKlineDataList(lastKlineData)[0].minPrice; 78 | // dataList.last.volume = getKlineDataList(lastKlineData)[0].volume; 79 | // } 80 | // } 81 | // } 82 | 83 | /// kline request 请求网路数据 84 | Future getKDataList(int index) async { 85 | // 网路数据 86 | Map data = await DioUtil.get("/m/kline/btcusdt/${_timeIndex[index]}/1000"); 87 | lines.addAll(data["data"]["lines"].reversed); 88 | dataList = getKlineDataList(lines); 89 | _currentDataType = _timeIndex[index] + "btcusdt"; 90 | setState(() {}); 91 | } 92 | 93 | // k线返回数据模型 94 | List getKlineDataList(List data) { 95 | List kDataList = List(); 96 | for (int i = 0; i < data.length; i++) { 97 | int timestamp = data[i][0].toInt(); 98 | //timestamp 99 | double openPrice = data[i][1].toDouble(); 100 | // open 101 | double closePrice = data[i][4].toDouble(); 102 | // close 103 | double maxPrice = data[i][2].toDouble(); 104 | // max 105 | double minPrice = data[i][3].toDouble(); 106 | // min 107 | double volume = data[i][5].toDouble(); 108 | if (volume > 0) { 109 | kDataList.add(ChartModel( 110 | timestamp, openPrice, closePrice, maxPrice, minPrice, volume)); 111 | } 112 | } 113 | return kDataList; 114 | } 115 | 116 | ///depth request 117 | Future getDepthList() async { 118 | Map data = await DioUtil.get("/m/depth/btcusdt"); 119 | 120 | setState(() { 121 | _bidsList = data["bids"]; 122 | _asksList = data["asks"]; 123 | }); 124 | } 125 | 126 | List depthList(List dataList) { 127 | List depthList = List(); 128 | int length = 0; 129 | if (dataList.length > 12) { 130 | length = 12; 131 | } else { 132 | length = dataList.length; 133 | } 134 | for (int i = 0; i < length; i++) { 135 | double price = dataList[i][0].toDouble(); 136 | double volume = dataList[i][1].toDouble(); 137 | depthList.add(DepthModel(price, volume)); 138 | } 139 | return depthList; 140 | } 141 | 142 | void viewType(int type) { 143 | switch (type) { 144 | case 0: 145 | _viewTypeIndex = 0; 146 | break; 147 | case 1: 148 | _viewTypeIndex = 1; 149 | break; 150 | case 2: 151 | _viewTypeIndex = 2; 152 | break; 153 | } 154 | } 155 | 156 | void subviewType(int type) { 157 | switch (type) { 158 | case 0: 159 | _subviewTypeIndex = 0; 160 | break; 161 | case 1: 162 | _subviewTypeIndex = 1; 163 | break; 164 | case 2: 165 | _subviewTypeIndex = 2; 166 | break; 167 | } 168 | } 169 | 170 | @override 171 | Widget build(BuildContext context) { 172 | return Scaffold( 173 | backgroundColor: Color(0xFF131e30), 174 | appBar: AppBar( 175 | title: Text("BTC/USDT"), 176 | ), 177 | body: SingleChildScrollView( 178 | child: Column( 179 | children: [ 180 | Row( 181 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 182 | children: [ 183 | Container( 184 | width: MediaQuery.of(context).size.width - 100, 185 | child: TabBar( 186 | isScrollable: true, 187 | controller: _tabController, 188 | labelStyle: TextStyle(fontSize: 10), 189 | tabs: [ 190 | Tab(text: "1分"), 191 | Tab(text: "5分"), 192 | Tab(text: "15分"), 193 | Tab(text: "30分"), 194 | Tab(text: "1小时"), 195 | Tab(text: "1日"), 196 | Tab(text: "1周"), 197 | Tab(text: "1月"), 198 | ], 199 | onTap: (index) { 200 | setState(() { 201 | getKDataList(index); 202 | }); 203 | }, 204 | ), 205 | ), 206 | Container( 207 | child: FlatButton( 208 | onPressed: () { 209 | setState(() { 210 | if (_isShowMenu) { 211 | _isShowMenu = false; 212 | } else { 213 | _isShowMenu = true; 214 | } 215 | }); 216 | }, 217 | child: Icon( 218 | Icons.menu, 219 | color: text, 220 | ), 221 | ), 222 | ) 223 | ], 224 | ), 225 | _isShowMenu 226 | ? Container( 227 | height: 100, 228 | child: Column( 229 | children: [ 230 | Row( 231 | children: [ 232 | Text( 233 | "主图", 234 | style: TextStyle(color: text), 235 | ), 236 | SizedBox( 237 | width: 20, 238 | ), 239 | Text("|", style: TextStyle(color: Colors.white)), 240 | SizedBox( 241 | width: 20, 242 | ), 243 | InkWell( 244 | child: Text( 245 | "MA", 246 | style: TextStyle(color: text), 247 | ), 248 | onTap: () { 249 | setState(() { 250 | _isShowView = true; 251 | viewType(0); 252 | }); 253 | }, 254 | ), 255 | SizedBox( 256 | width: 20, 257 | ), 258 | InkWell( 259 | child: Text( 260 | "BOLL", 261 | style: TextStyle(color: text), 262 | ), 263 | onTap: () { 264 | setState(() { 265 | _isShowView = true; 266 | viewType(1); 267 | }); 268 | }, 269 | ), 270 | IconButton( 271 | iconSize: 16, 272 | icon: _isShowView 273 | ? Icon(Icons.visibility, color: Colors.grey) 274 | : Icon(Icons.visibility_off, 275 | color: Colors.grey), 276 | onPressed: () { 277 | setState(() { 278 | viewType(2); 279 | if (_isShowView) { 280 | _isShowView = false; 281 | } 282 | }); 283 | }, 284 | ) 285 | ], 286 | ), 287 | Row( 288 | children: [ 289 | Text( 290 | "副图", 291 | style: TextStyle(color: text), 292 | ), 293 | SizedBox( 294 | width: 20, 295 | ), 296 | Text("|", style: TextStyle(color: Colors.white)), 297 | SizedBox( 298 | width: 20, 299 | ), 300 | InkWell( 301 | child: Text( 302 | "MACD", 303 | style: TextStyle(color: text), 304 | ), 305 | onTap: () { 306 | setState(() { 307 | setState(() { 308 | _isShowSubview = true; 309 | subviewType(0); 310 | }); 311 | }); 312 | }, 313 | ), 314 | SizedBox( 315 | width: 20, 316 | ), 317 | InkWell( 318 | child: Text( 319 | "KDJ", 320 | style: TextStyle(color: text), 321 | ), 322 | onTap: () { 323 | setState(() { 324 | _isShowSubview = true; 325 | subviewType(1); 326 | }); 327 | }, 328 | ), 329 | SizedBox( 330 | width: 20, 331 | ), 332 | InkWell( 333 | child: Text( 334 | "RSI", 335 | style: TextStyle(color: text), 336 | ), 337 | onTap: () { 338 | setState(() { 339 | _isShowSubview = true; 340 | subviewType(2); 341 | }); 342 | }, 343 | ), 344 | IconButton( 345 | iconSize: 16, 346 | icon: _isShowSubview 347 | ? Icon(Icons.visibility, color: Colors.grey) 348 | : Icon(Icons.visibility_off, 349 | color: Colors.grey), 350 | onPressed: () { 351 | setState(() { 352 | if (_isShowSubview) { 353 | _isShowSubview = false; 354 | } 355 | }); 356 | }, 357 | ) 358 | ], 359 | ), 360 | ], 361 | ), 362 | ) 363 | : Container(), 364 | Container( 365 | child: KlineView( 366 | dataList: dataList, 367 | currentDataType: _currentDataType, 368 | isShowSubview: _isShowSubview, 369 | viewType: _viewTypeIndex, 370 | subviewType: _subviewTypeIndex, 371 | )), 372 | DepthView(depthList(_bidsList), depthList(_asksList)), 373 | ], 374 | ), 375 | )); 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /lib/generated/i18n.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | // ignore_for_file: non_constant_identifier_names 7 | // ignore_for_file: camel_case_types 8 | // ignore_for_file: prefer_single_quotes 9 | 10 | // This file is automatically generated. DO NOT EDIT, all your changes would be lost. 11 | class S implements WidgetsLocalizations { 12 | const S(); 13 | 14 | static S current; 15 | 16 | static const GeneratedLocalizationsDelegate delegate = 17 | GeneratedLocalizationsDelegate(); 18 | 19 | static S of(BuildContext context) => Localizations.of(context, S); 20 | 21 | @override 22 | TextDirection get textDirection => TextDirection.ltr; 23 | 24 | } 25 | 26 | class $en extends S { 27 | const $en(); 28 | } 29 | 30 | class GeneratedLocalizationsDelegate extends LocalizationsDelegate { 31 | const GeneratedLocalizationsDelegate(); 32 | 33 | List get supportedLocales { 34 | return const [ 35 | Locale("en", ""), 36 | ]; 37 | } 38 | 39 | LocaleListResolutionCallback listResolution({Locale fallback, bool withCountry = true}) { 40 | return (List locales, Iterable supported) { 41 | if (locales == null || locales.isEmpty) { 42 | return fallback ?? supported.first; 43 | } else { 44 | return _resolve(locales.first, fallback, supported, withCountry); 45 | } 46 | }; 47 | } 48 | 49 | LocaleResolutionCallback resolution({Locale fallback, bool withCountry = true}) { 50 | return (Locale locale, Iterable supported) { 51 | return _resolve(locale, fallback, supported, withCountry); 52 | }; 53 | } 54 | 55 | @override 56 | Future load(Locale locale) { 57 | final String lang = getLang(locale); 58 | if (lang != null) { 59 | switch (lang) { 60 | case "en": 61 | S.current = const $en(); 62 | return SynchronousFuture(S.current); 63 | default: 64 | // NO-OP. 65 | } 66 | } 67 | S.current = const S(); 68 | return SynchronousFuture(S.current); 69 | } 70 | 71 | @override 72 | bool isSupported(Locale locale) => _isSupported(locale, true); 73 | 74 | @override 75 | bool shouldReload(GeneratedLocalizationsDelegate old) => false; 76 | 77 | /// 78 | /// Internal method to resolve a locale from a list of locales. 79 | /// 80 | Locale _resolve(Locale locale, Locale fallback, Iterable supported, bool withCountry) { 81 | if (locale == null || !_isSupported(locale, withCountry)) { 82 | return fallback ?? supported.first; 83 | } 84 | 85 | final Locale languageLocale = Locale(locale.languageCode, ""); 86 | if (supported.contains(locale)) { 87 | return locale; 88 | } else if (supported.contains(languageLocale)) { 89 | return languageLocale; 90 | } else { 91 | final Locale fallbackLocale = fallback ?? supported.first; 92 | return fallbackLocale; 93 | } 94 | } 95 | 96 | /// 97 | /// Returns true if the specified locale is supported, false otherwise. 98 | /// 99 | bool _isSupported(Locale locale, bool withCountry) { 100 | if (locale != null) { 101 | for (Locale supportedLocale in supportedLocales) { 102 | // Language must always match both locales. 103 | if (supportedLocale.languageCode != locale.languageCode) { 104 | continue; 105 | } 106 | 107 | // If country code matches, return this locale. 108 | if (supportedLocale.countryCode == locale.countryCode) { 109 | return true; 110 | } 111 | 112 | // If no country requirement is requested, check if this locale has no country. 113 | if (true != withCountry && (supportedLocale.countryCode == null || supportedLocale.countryCode.isEmpty)) { 114 | return true; 115 | } 116 | } 117 | } 118 | return false; 119 | } 120 | } 121 | 122 | String getLang(Locale l) => l == null 123 | ? null 124 | : l.countryCode != null && l.countryCode.isEmpty 125 | ? l.languageCode 126 | : l.toString(); 127 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:kchart/example.dart'; 3 | 4 | void main() => runApp(MyApp()); 5 | 6 | class MyApp extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return MaterialApp( 10 | debugShowCheckedModeBanner: false, 11 | title: "Kchart", 12 | home: Example(), 13 | ); 14 | } 15 | } -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.5.0-nullsafety.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0-nullsafety.1" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.0-nullsafety.3" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.2.0-nullsafety.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0-nullsafety.1" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0-nullsafety.3" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "0.1.2" 53 | dio: 54 | dependency: "direct main" 55 | description: 56 | name: dio 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "3.0.9" 60 | fake_async: 61 | dependency: transitive 62 | description: 63 | name: fake_async 64 | url: "https://pub.flutter-io.cn" 65 | source: hosted 66 | version: "1.2.0-nullsafety.1" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | http_parser: 78 | dependency: transitive 79 | description: 80 | name: http_parser 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "3.1.4" 84 | matcher: 85 | dependency: transitive 86 | description: 87 | name: matcher 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "0.12.10-nullsafety.1" 91 | meta: 92 | dependency: transitive 93 | description: 94 | name: meta 95 | url: "https://pub.flutter-io.cn" 96 | source: hosted 97 | version: "1.3.0-nullsafety.3" 98 | path: 99 | dependency: transitive 100 | description: 101 | name: path 102 | url: "https://pub.flutter-io.cn" 103 | source: hosted 104 | version: "1.8.0-nullsafety.1" 105 | sky_engine: 106 | dependency: transitive 107 | description: flutter 108 | source: sdk 109 | version: "0.0.99" 110 | source_span: 111 | dependency: transitive 112 | description: 113 | name: source_span 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "1.8.0-nullsafety.2" 117 | stack_trace: 118 | dependency: transitive 119 | description: 120 | name: stack_trace 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.10.0-nullsafety.1" 124 | stream_channel: 125 | dependency: transitive 126 | description: 127 | name: stream_channel 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "2.1.0-nullsafety.1" 131 | string_scanner: 132 | dependency: transitive 133 | description: 134 | name: string_scanner 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.1.0-nullsafety.1" 138 | term_glyph: 139 | dependency: transitive 140 | description: 141 | name: term_glyph 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "1.2.0-nullsafety.1" 145 | test_api: 146 | dependency: transitive 147 | description: 148 | name: test_api 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "0.2.19-nullsafety.2" 152 | typed_data: 153 | dependency: transitive 154 | description: 155 | name: typed_data 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "1.3.0-nullsafety.3" 159 | vector_math: 160 | dependency: transitive 161 | description: 162 | name: vector_math 163 | url: "https://pub.flutter-io.cn" 164 | source: hosted 165 | version: "2.1.0-nullsafety.3" 166 | sdks: 167 | dart: ">=2.10.0-110 <2.11.0" 168 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: kchart 2 | description: A new Flutter application. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^0.1.2 26 | dio: ^3.0.9 27 | 28 | dev_dependencies: 29 | flutter_test: 30 | sdk: flutter 31 | 32 | 33 | # For information on the generic Dart part of this file, see the 34 | # following page: https://dart.dev/tools/pub/pubspec 35 | 36 | # The following section is specific to Flutter. 37 | flutter: 38 | 39 | # The following line ensures that the Material Icons font is 40 | # included with your application, so that you can use the icons in 41 | # the material Icons class. 42 | uses-material-design: true 43 | 44 | # To add assets to your application, add an assets section, like this: 45 | # assets: 46 | # - images/a_dot_burr.jpeg 47 | # - images/a_dot_ham.jpeg 48 | 49 | # An image asset can refer to one or more resolution-specific "variants", see 50 | # https://flutter.dev/assets-and-images/#resolution-aware. 51 | 52 | # For details regarding adding assets from package dependencies, see 53 | # https://flutter.dev/assets-and-images/#from-packages 54 | 55 | # To add custom fonts to your application, add a fonts section here, 56 | # in this "flutter" section. Each entry in this list should have a 57 | # "family" key with the font family name, and a "fonts" key with a 58 | # list giving the asset and other descriptors for the font. For 59 | # example: 60 | # fonts: 61 | # - family: Schyler 62 | # fonts: 63 | # - asset: fonts/Schyler-Regular.ttf 64 | # - asset: fonts/Schyler-Italic.ttf 65 | # style: italic 66 | # - family: Trajan Pro 67 | # fonts: 68 | # - asset: fonts/TrajanPro.ttf 69 | # - asset: fonts/TrajanPro_Bold.ttf 70 | # weight: 700 71 | # 72 | # For details regarding fonts from package dependencies, 73 | # see https://flutter.dev/custom-fonts/#from-packages 74 | -------------------------------------------------------------------------------- /res/values/strings_en.arb: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:kchart/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------