├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── flarts │ │ │ │ └── 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 ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata └── 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 ├── example │ ├── lib │ │ ├── example_data.dart │ │ └── main.dart │ └── screenshots │ │ ├── large_spark_example.png │ │ ├── multi_data_example.png │ │ ├── multi_spark_example.png │ │ └── simple_data_example.png ├── flart.dart ├── flart_axis.dart ├── flart_data.dart ├── flart_painter.dart ├── flart_theme.dart └── label_formatter.dart ├── pubspec.lock └── pubspec.yaml /.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 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | /build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/captures/ 34 | **/android/gradlew 35 | **/android/gradlew.bat 36 | **/android/local.properties 37 | **/android/**/GeneratedPluginRegistrant.java 38 | 39 | # iOS/XCode related 40 | **/ios/**/*.mode1v3 41 | **/ios/**/*.mode2v3 42 | **/ios/**/*.moved-aside 43 | **/ios/**/*.pbxuser 44 | **/ios/**/*.perspectivev3 45 | **/ios/**/*sync/ 46 | **/ios/**/.sconsign.dblite 47 | **/ios/**/.tags* 48 | **/ios/**/.vagrant/ 49 | **/ios/**/DerivedData/ 50 | **/ios/**/Icon? 51 | **/ios/**/Pods/ 52 | **/ios/**/.symlinks/ 53 | **/ios/**/profile 54 | **/ios/**/xcuserdata 55 | **/ios/.generated/ 56 | **/ios/Flutter/App.framework 57 | **/ios/Flutter/Flutter.framework 58 | **/ios/Flutter/Generated.xcconfig 59 | **/ios/Flutter/app.flx 60 | **/ios/Flutter/app.zip 61 | **/ios/Flutter/flutter_assets/ 62 | **/ios/ServiceDefinitions.json 63 | **/ios/Runner/GeneratedPluginRegistrant.* 64 | 65 | 66 | # macOS/XCode related 67 | **/macos/**/*.mode1v3 68 | **/macos/**/*.mode2v3 69 | **/macos/**/*.moved-aside 70 | **/macos/**/*.pbxuser 71 | **/macos/**/*.perspectivev3 72 | **/macos/**/*sync/ 73 | **/macos/**/.sconsign.dblite 74 | **/macos/**/.tags* 75 | **/macos/**/.vagrant/ 76 | **/macos/**/DerivedData/ 77 | **/macos/**/Icon? 78 | **/macos/**/Pods/ 79 | **/macos/**/.symlinks/ 80 | **/macos/**/profile 81 | **/macos/**/xcuserdata 82 | **/macos/.generated/ 83 | **/macos/Flutter/App.framework 84 | **/macos/Flutter/Flutter.framework 85 | **/macos/Flutter/Generated.xcconfig 86 | **/macos/Flutter/app.flx 87 | **/macos/Flutter/app.zip 88 | **/macos/Flutter/flutter_assets/ 89 | **/macos/ServiceDefinitions.json 90 | **/macos/Runner/GeneratedPluginRegistrant.* 91 | 92 | # Exceptions to above rules. 93 | !**/ios/**/default.mode1v3 94 | !**/ios/**/default.mode2v3 95 | !**/ios/**/default.pbxuser 96 | !**/ios/**/default.perspectivev3 97 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 98 | -------------------------------------------------------------------------------- /.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: 8661d8aecd626f7f57ccbcb735553edc05a2e713 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.0 2 | 3 | - First public version 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019, Matthew Cliatt (matthewcliatt@gmail.com). 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flarts - Flutter Charts 2 | 3 | [![Pub](https://img.shields.io/pub/v/flarts.svg)](https://pub.dartlang.org/packages/flarts) 4 | 5 | #### Flutter + Charts = Flarts 6 | 7 | Flarts is a graphing/charting package for Flutter. 8 | 9 |

10 | 11 | S&P 500 in red, Dow Jones Index in green 12 |

13 | 14 | ## Features 15 | 16 | - Multiple data sets and plot types on a single Flart 17 | 18 | - Auto-fit 19 | - By default Flarts will size each axis to fit the min and max of the data plotted on that axis. 20 | 21 | - Auto-labelling 22 | - Label values are be interpolated from the type of data on the axis. 23 | 24 | - Direction-agnostic axes 25 | - Your range axes don't have to be vertical and your domain axes don't have to be horizontal. Use what you want. 26 | 27 | - Customization 28 | - Axis Labels 29 | - The label text can be interpolated from the data on that axis, or it can be the index of the label. 30 | - Frequency: Label every gridline, every other gridline, none, or provide your own labels. 31 | - Axes Side/Direction 32 | - Any axis can be vertical or horizontal. 33 | - Any vertical axis can be on the left or right. 34 | - Any horizontal axis can be on the top or bottom. 35 | - Axes Gridlines 36 | - The number of gridlines on each axis can be specified. 37 | - Styling 38 | - Color can be specified for each data plot. 39 | - Styling customization is largely in progress. The first goals are: 40 | - Custom `TextStyle` on labels 41 | - Custom `PaintStyle` for gridlines, the chart background, and the chart border 42 | 43 | - Plot types 44 | - Line 45 | - Bar 46 | - More plot types are currently in development. The first goal is candlesticks (OHLC). 47 | 48 | 49 | ## Examples 50 | 51 | All examples shown below are screenshots from the example app included in the library. 52 | 53 | ### Simple data, simple example 54 | 55 | 56 | 57 | This basic example uses a custom range axis (from 10-25), and doesn't provide a custom domain axis so the chart fits the domain data. 58 | 59 | ----- 60 | 61 | ### Spark chart 62 | 63 | 64 | 65 | The Spark Chart is a sleek pre-styled chart with no labels or gridlines. 66 | 67 | ----- 68 | 69 | ### Multiple data sets and plot type 70 | 71 | 72 | 73 |

74 | This chart plots the price of the S&P 500 and Dow Jones Indices, as well as the volume of the S&P 500 Index, from April 10 2018 to April 10 2019. The S&P price is drawn in red, the Dow Jones price in green, and the S&P volume in blue (the bars). 75 |

76 | 77 | All three data plots share the same domain axis, which is derived from the dates in the price data. 78 | The labels on the left axis are interpolated from the S&P's volume data. 79 | The labels on the right axis are interpolated from the S&P's price data. 80 | 81 | ----- 82 | 83 | -------------------------------------------------------------------------------- /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.example.flarts" 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 | 13 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/flarts/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.flarts 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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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/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 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 16 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 18 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 19 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 20 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXCopyFilesBuildPhase section */ 24 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 25 | isa = PBXCopyFilesBuildPhase; 26 | buildActionMask = 2147483647; 27 | dstPath = ""; 28 | dstSubfolderSpec = 10; 29 | files = ( 30 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 31 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 32 | ); 33 | name = "Embed Frameworks"; 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXCopyFilesBuildPhase section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 40 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 41 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 42 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 43 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 44 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 45 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 46 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 47 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 48 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 49 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 51 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 53 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 62 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | 9740EEB11CF90186004384FC /* Flutter */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 3B80C3931E831B6300D905FE /* App.framework */, 73 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 74 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 75 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 76 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 77 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 78 | ); 79 | name = Flutter; 80 | sourceTree = ""; 81 | }; 82 | 97C146E51CF9000F007C117D = { 83 | isa = PBXGroup; 84 | children = ( 85 | 9740EEB11CF90186004384FC /* Flutter */, 86 | 97C146F01CF9000F007C117D /* Runner */, 87 | 97C146EF1CF9000F007C117D /* Products */, 88 | ); 89 | sourceTree = ""; 90 | }; 91 | 97C146EF1CF9000F007C117D /* Products */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 97C146EE1CF9000F007C117D /* Runner.app */, 95 | ); 96 | name = Products; 97 | sourceTree = ""; 98 | }; 99 | 97C146F01CF9000F007C117D /* Runner */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 103 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 104 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 105 | 97C147021CF9000F007C117D /* Info.plist */, 106 | 97C146F11CF9000F007C117D /* Supporting Files */, 107 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 108 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 109 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 110 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 111 | ); 112 | path = Runner; 113 | sourceTree = ""; 114 | }; 115 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | ); 119 | name = "Supporting Files"; 120 | sourceTree = ""; 121 | }; 122 | /* End PBXGroup section */ 123 | 124 | /* Begin PBXNativeTarget section */ 125 | 97C146ED1CF9000F007C117D /* Runner */ = { 126 | isa = PBXNativeTarget; 127 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 128 | buildPhases = ( 129 | 9740EEB61CF901F6004384FC /* Run Script */, 130 | 97C146EA1CF9000F007C117D /* Sources */, 131 | 97C146EB1CF9000F007C117D /* Frameworks */, 132 | 97C146EC1CF9000F007C117D /* Resources */, 133 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 134 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 135 | ); 136 | buildRules = ( 137 | ); 138 | dependencies = ( 139 | ); 140 | name = Runner; 141 | productName = Runner; 142 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 143 | productType = "com.apple.product-type.application"; 144 | }; 145 | /* End PBXNativeTarget section */ 146 | 147 | /* Begin PBXProject section */ 148 | 97C146E61CF9000F007C117D /* Project object */ = { 149 | isa = PBXProject; 150 | attributes = { 151 | LastUpgradeCheck = 0910; 152 | ORGANIZATIONNAME = "The Chromium Authors"; 153 | TargetAttributes = { 154 | 97C146ED1CF9000F007C117D = { 155 | CreatedOnToolsVersion = 7.3.1; 156 | LastSwiftMigration = 0910; 157 | }; 158 | }; 159 | }; 160 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 161 | compatibilityVersion = "Xcode 3.2"; 162 | developmentRegion = English; 163 | hasScannedForEncodings = 0; 164 | knownRegions = ( 165 | en, 166 | Base, 167 | ); 168 | mainGroup = 97C146E51CF9000F007C117D; 169 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 170 | projectDirPath = ""; 171 | projectRoot = ""; 172 | targets = ( 173 | 97C146ED1CF9000F007C117D /* Runner */, 174 | ); 175 | }; 176 | /* End PBXProject section */ 177 | 178 | /* Begin PBXResourcesBuildPhase section */ 179 | 97C146EC1CF9000F007C117D /* Resources */ = { 180 | isa = PBXResourcesBuildPhase; 181 | buildActionMask = 2147483647; 182 | files = ( 183 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 184 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 185 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 186 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 187 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXResourcesBuildPhase section */ 192 | 193 | /* Begin PBXShellScriptBuildPhase section */ 194 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 195 | isa = PBXShellScriptBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | ); 199 | inputPaths = ( 200 | ); 201 | name = "Thin Binary"; 202 | outputPaths = ( 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | shellPath = /bin/sh; 206 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 207 | }; 208 | 9740EEB61CF901F6004384FC /* Run Script */ = { 209 | isa = PBXShellScriptBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | ); 213 | inputPaths = ( 214 | ); 215 | name = "Run Script"; 216 | outputPaths = ( 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | shellPath = /bin/sh; 220 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 221 | }; 222 | /* End PBXShellScriptBuildPhase section */ 223 | 224 | /* Begin PBXSourcesBuildPhase section */ 225 | 97C146EA1CF9000F007C117D /* Sources */ = { 226 | isa = PBXSourcesBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 230 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | }; 234 | /* End PBXSourcesBuildPhase section */ 235 | 236 | /* Begin PBXVariantGroup section */ 237 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 238 | isa = PBXVariantGroup; 239 | children = ( 240 | 97C146FB1CF9000F007C117D /* Base */, 241 | ); 242 | name = Main.storyboard; 243 | sourceTree = ""; 244 | }; 245 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 246 | isa = PBXVariantGroup; 247 | children = ( 248 | 97C147001CF9000F007C117D /* Base */, 249 | ); 250 | name = LaunchScreen.storyboard; 251 | sourceTree = ""; 252 | }; 253 | /* End PBXVariantGroup section */ 254 | 255 | /* Begin XCBuildConfiguration section */ 256 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 257 | isa = XCBuildConfiguration; 258 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 259 | buildSettings = { 260 | ALWAYS_SEARCH_USER_PATHS = NO; 261 | CLANG_ANALYZER_NONNULL = YES; 262 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 263 | CLANG_CXX_LIBRARY = "libc++"; 264 | CLANG_ENABLE_MODULES = YES; 265 | CLANG_ENABLE_OBJC_ARC = YES; 266 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 267 | CLANG_WARN_BOOL_CONVERSION = YES; 268 | CLANG_WARN_COMMA = YES; 269 | CLANG_WARN_CONSTANT_CONVERSION = YES; 270 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 271 | CLANG_WARN_EMPTY_BODY = YES; 272 | CLANG_WARN_ENUM_CONVERSION = YES; 273 | CLANG_WARN_INFINITE_RECURSION = YES; 274 | CLANG_WARN_INT_CONVERSION = YES; 275 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 279 | CLANG_WARN_STRICT_PROTOTYPES = YES; 280 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 284 | COPY_PHASE_STRIP = NO; 285 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 286 | ENABLE_NS_ASSERTIONS = NO; 287 | ENABLE_STRICT_OBJC_MSGSEND = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu99; 289 | GCC_NO_COMMON_BLOCKS = YES; 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 297 | MTL_ENABLE_DEBUG_INFO = NO; 298 | SDKROOT = iphoneos; 299 | TARGETED_DEVICE_FAMILY = "1,2"; 300 | VALIDATE_PRODUCT = YES; 301 | }; 302 | name = Profile; 303 | }; 304 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 305 | isa = XCBuildConfiguration; 306 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 307 | buildSettings = { 308 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 309 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 310 | DEVELOPMENT_TEAM = S8QB4VV633; 311 | ENABLE_BITCODE = NO; 312 | FRAMEWORK_SEARCH_PATHS = ( 313 | "$(inherited)", 314 | "$(PROJECT_DIR)/Flutter", 315 | ); 316 | INFOPLIST_FILE = Runner/Info.plist; 317 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 318 | LIBRARY_SEARCH_PATHS = ( 319 | "$(inherited)", 320 | "$(PROJECT_DIR)/Flutter", 321 | ); 322 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flarts; 323 | PRODUCT_NAME = "$(TARGET_NAME)"; 324 | SWIFT_VERSION = 4.0; 325 | VERSIONING_SYSTEM = "apple-generic"; 326 | }; 327 | name = Profile; 328 | }; 329 | 97C147031CF9000F007C117D /* Debug */ = { 330 | isa = XCBuildConfiguration; 331 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 332 | buildSettings = { 333 | ALWAYS_SEARCH_USER_PATHS = NO; 334 | CLANG_ANALYZER_NONNULL = YES; 335 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 336 | CLANG_CXX_LIBRARY = "libc++"; 337 | CLANG_ENABLE_MODULES = YES; 338 | CLANG_ENABLE_OBJC_ARC = YES; 339 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 340 | CLANG_WARN_BOOL_CONVERSION = YES; 341 | CLANG_WARN_COMMA = YES; 342 | CLANG_WARN_CONSTANT_CONVERSION = YES; 343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 344 | CLANG_WARN_EMPTY_BODY = YES; 345 | CLANG_WARN_ENUM_CONVERSION = YES; 346 | CLANG_WARN_INFINITE_RECURSION = YES; 347 | CLANG_WARN_INT_CONVERSION = YES; 348 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 349 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 351 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 352 | CLANG_WARN_STRICT_PROTOTYPES = YES; 353 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 354 | CLANG_WARN_UNREACHABLE_CODE = YES; 355 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 356 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 357 | COPY_PHASE_STRIP = NO; 358 | DEBUG_INFORMATION_FORMAT = dwarf; 359 | ENABLE_STRICT_OBJC_MSGSEND = YES; 360 | ENABLE_TESTABILITY = YES; 361 | GCC_C_LANGUAGE_STANDARD = gnu99; 362 | GCC_DYNAMIC_NO_PIC = NO; 363 | GCC_NO_COMMON_BLOCKS = YES; 364 | GCC_OPTIMIZATION_LEVEL = 0; 365 | GCC_PREPROCESSOR_DEFINITIONS = ( 366 | "DEBUG=1", 367 | "$(inherited)", 368 | ); 369 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 370 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 371 | GCC_WARN_UNDECLARED_SELECTOR = YES; 372 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 373 | GCC_WARN_UNUSED_FUNCTION = YES; 374 | GCC_WARN_UNUSED_VARIABLE = YES; 375 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 376 | MTL_ENABLE_DEBUG_INFO = YES; 377 | ONLY_ACTIVE_ARCH = YES; 378 | SDKROOT = iphoneos; 379 | TARGETED_DEVICE_FAMILY = "1,2"; 380 | }; 381 | name = Debug; 382 | }; 383 | 97C147041CF9000F007C117D /* Release */ = { 384 | isa = XCBuildConfiguration; 385 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 386 | buildSettings = { 387 | ALWAYS_SEARCH_USER_PATHS = NO; 388 | CLANG_ANALYZER_NONNULL = YES; 389 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 390 | CLANG_CXX_LIBRARY = "libc++"; 391 | CLANG_ENABLE_MODULES = YES; 392 | CLANG_ENABLE_OBJC_ARC = YES; 393 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 394 | CLANG_WARN_BOOL_CONVERSION = YES; 395 | CLANG_WARN_COMMA = YES; 396 | CLANG_WARN_CONSTANT_CONVERSION = YES; 397 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 398 | CLANG_WARN_EMPTY_BODY = YES; 399 | CLANG_WARN_ENUM_CONVERSION = YES; 400 | CLANG_WARN_INFINITE_RECURSION = YES; 401 | CLANG_WARN_INT_CONVERSION = YES; 402 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 403 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 404 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 405 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 406 | CLANG_WARN_STRICT_PROTOTYPES = YES; 407 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 408 | CLANG_WARN_UNREACHABLE_CODE = YES; 409 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 410 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 411 | COPY_PHASE_STRIP = NO; 412 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 413 | ENABLE_NS_ASSERTIONS = NO; 414 | ENABLE_STRICT_OBJC_MSGSEND = YES; 415 | GCC_C_LANGUAGE_STANDARD = gnu99; 416 | GCC_NO_COMMON_BLOCKS = YES; 417 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 418 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 419 | GCC_WARN_UNDECLARED_SELECTOR = YES; 420 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 421 | GCC_WARN_UNUSED_FUNCTION = YES; 422 | GCC_WARN_UNUSED_VARIABLE = YES; 423 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 424 | MTL_ENABLE_DEBUG_INFO = NO; 425 | SDKROOT = iphoneos; 426 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 427 | TARGETED_DEVICE_FAMILY = "1,2"; 428 | VALIDATE_PRODUCT = YES; 429 | }; 430 | name = Release; 431 | }; 432 | 97C147061CF9000F007C117D /* Debug */ = { 433 | isa = XCBuildConfiguration; 434 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 435 | buildSettings = { 436 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 437 | CLANG_ENABLE_MODULES = YES; 438 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 439 | ENABLE_BITCODE = NO; 440 | FRAMEWORK_SEARCH_PATHS = ( 441 | "$(inherited)", 442 | "$(PROJECT_DIR)/Flutter", 443 | ); 444 | INFOPLIST_FILE = Runner/Info.plist; 445 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 446 | LIBRARY_SEARCH_PATHS = ( 447 | "$(inherited)", 448 | "$(PROJECT_DIR)/Flutter", 449 | ); 450 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flarts; 451 | PRODUCT_NAME = "$(TARGET_NAME)"; 452 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 453 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 454 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 455 | SWIFT_VERSION = 4.0; 456 | VERSIONING_SYSTEM = "apple-generic"; 457 | }; 458 | name = Debug; 459 | }; 460 | 97C147071CF9000F007C117D /* Release */ = { 461 | isa = XCBuildConfiguration; 462 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 463 | buildSettings = { 464 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 465 | CLANG_ENABLE_MODULES = YES; 466 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 467 | ENABLE_BITCODE = NO; 468 | FRAMEWORK_SEARCH_PATHS = ( 469 | "$(inherited)", 470 | "$(PROJECT_DIR)/Flutter", 471 | ); 472 | INFOPLIST_FILE = Runner/Info.plist; 473 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 474 | LIBRARY_SEARCH_PATHS = ( 475 | "$(inherited)", 476 | "$(PROJECT_DIR)/Flutter", 477 | ); 478 | PRODUCT_BUNDLE_IDENTIFIER = com.example.flarts; 479 | PRODUCT_NAME = "$(TARGET_NAME)"; 480 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 481 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 482 | SWIFT_VERSION = 4.0; 483 | VERSIONING_SYSTEM = "apple-generic"; 484 | }; 485 | name = Release; 486 | }; 487 | /* End XCBuildConfiguration section */ 488 | 489 | /* Begin XCConfigurationList section */ 490 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 491 | isa = XCConfigurationList; 492 | buildConfigurations = ( 493 | 97C147031CF9000F007C117D /* Debug */, 494 | 97C147041CF9000F007C117D /* Release */, 495 | 249021D3217E4FDB00AE95B9 /* Profile */, 496 | ); 497 | defaultConfigurationIsVisible = 0; 498 | defaultConfigurationName = Release; 499 | }; 500 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 501 | isa = XCConfigurationList; 502 | buildConfigurations = ( 503 | 97C147061CF9000F007C117D /* Debug */, 504 | 97C147071CF9000F007C117D /* Release */, 505 | 249021D4217E4FDB00AE95B9 /* Profile */, 506 | ); 507 | defaultConfigurationIsVisible = 0; 508 | defaultConfigurationName = Release; 509 | }; 510 | /* End XCConfigurationList section */ 511 | 512 | }; 513 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 514 | } 515 | -------------------------------------------------------------------------------- /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 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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: [UIApplicationLaunchOptionsKey: 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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/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 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flarts 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/example/lib/example_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flarts/flart_data.dart'; 2 | 3 | class StockQuote { 4 | final DateTime timestamp; 5 | final double price; 6 | final int volume; 7 | 8 | StockQuote(this.timestamp, this.price, this.volume); 9 | } 10 | 11 | FlartData get spyPriceData => FlartData( 12 | spyQuotes, 13 | rangeFn: (price, i) => price.price, 14 | domainFn: (price, i) => price.timestamp, 15 | ); 16 | 17 | FlartData get dowPricesData => FlartData( 18 | dowQuotes, 19 | rangeFn: (quote, i) => quote.price, 20 | domainFn: (quote, i) => quote.timestamp, 21 | ); 22 | 23 | FlartData get spyVolumeData => FlartData( 24 | spyQuotes, 25 | rangeFn: (quote, i) => quote.volume, 26 | domainFn: (quote, i) => quote.timestamp, 27 | ); 28 | 29 | // todo: make an example with Price to show how auto-interpolation works. 30 | class Price { 31 | final Day day; 32 | final double price; 33 | 34 | Price(this.day, this.price); 35 | } 36 | 37 | class Day extends Comparable { 38 | int day; 39 | 40 | Day(this.day); 41 | 42 | @override 43 | int compareTo(Day other) => day - other.day; 44 | 45 | @override 46 | String toString() => '$day'; 47 | } 48 | 49 | List get simplyDoubles => 50 | [10.0, 12.0, 14.0, 15.0, 18.0, -4.0, -2.0, 2.0]; 51 | 52 | List get simplyCount => [1, 2, 3, 4, 5, 6, 7, 8]; 53 | 54 | List get spyQuotes => [ 55 | StockQuote(DateTime.parse("2019-04-10"), 288.29, 52463390), 56 | StockQuote(DateTime.parse("2019-04-09"), 287.31, 66072820), 57 | StockQuote(DateTime.parse("2019-04-08"), 288.79, 53462150), 58 | StockQuote(DateTime.parse("2019-04-05"), 288.57, 58454610), 59 | StockQuote(DateTime.parse("2019-04-04"), 287.18, 48868830), 60 | StockQuote(DateTime.parse("2019-04-03"), 286.42, 68027480), 61 | StockQuote(DateTime.parse("2019-04-02"), 285.97, 39939210), 62 | StockQuote(DateTime.parse("2019-04-01"), 285.83, 77289510), 63 | StockQuote(DateTime.parse("2019-03-29"), 282.48, 81882480), 64 | StockQuote(DateTime.parse("2019-03-28"), 280.71, 55987830), 65 | StockQuote(DateTime.parse("2019-03-27"), 279.65, 72092750), 66 | StockQuote(DateTime.parse("2019-03-26"), 281.12, 67884060), 67 | StockQuote(DateTime.parse("2019-03-25"), 279.04, 84898680), 68 | StockQuote(DateTime.parse("2019-03-22"), 279.25, 122400900), 69 | StockQuote(DateTime.parse("2019-03-21"), 284.73, 79394020), 70 | StockQuote(DateTime.parse("2019-03-20"), 281.55, 84349430), 71 | StockQuote(DateTime.parse("2019-03-19"), 282.4, 90060530), 72 | StockQuote(DateTime.parse("2019-03-18"), 282.33, 61813150), 73 | StockQuote(DateTime.parse("2019-03-15"), 281.31, 80558110), 74 | StockQuote(DateTime.parse("2019-03-14"), 281.16, 67354180), 75 | StockQuote(DateTime.parse("2019-03-13"), 281.34, 80111580), 76 | StockQuote(DateTime.parse("2019-03-12"), 279.49, 79432230), 77 | StockQuote(DateTime.parse("2019-03-11"), 278.44, 64752480), 78 | StockQuote(DateTime.parse("2019-03-08"), 274.46, 85634780), 79 | StockQuote(DateTime.parse("2019-03-07"), 275.01, 94692650), 80 | StockQuote(DateTime.parse("2019-03-06"), 277.33, 74824560), 81 | StockQuote(DateTime.parse("2019-03-05"), 279.02, 58813100), 82 | StockQuote(DateTime.parse("2019-03-04"), 279.4, 106181700), 83 | StockQuote(DateTime.parse("2019-03-01"), 280.42, 77586110), 84 | StockQuote(DateTime.parse("2019-02-28"), 278.68, 69048080), 85 | StockQuote(DateTime.parse("2019-02-27"), 279.2, 56131350), 86 | StockQuote(DateTime.parse("2019-02-26"), 279.32, 56491990), 87 | StockQuote(DateTime.parse("2019-02-25"), 279.52, 68642630), 88 | StockQuote(DateTime.parse("2019-02-22"), 279.14, 77132070), 89 | StockQuote(DateTime.parse("2019-02-21"), 277.42, 63984460), 90 | StockQuote(DateTime.parse("2019-02-20"), 278.41, 76479570), 91 | StockQuote(DateTime.parse("2019-02-19"), 277.85, 58802310), 92 | StockQuote(DateTime.parse("2019-02-15"), 277.37, 96744830), 93 | StockQuote(DateTime.parse("2019-02-14"), 274.38, 82409970), 94 | StockQuote(DateTime.parse("2019-02-13"), 274.99, 64930780), 95 | StockQuote(DateTime.parse("2019-02-12"), 274.1, 71990420), 96 | StockQuote(DateTime.parse("2019-02-11"), 270.62, 67807910), 97 | StockQuote(DateTime.parse("2019-02-08"), 270.47, 75742440), 98 | StockQuote(DateTime.parse("2019-02-07"), 270.14, 95030290), 99 | StockQuote(DateTime.parse("2019-02-06"), 272.74, 58009950), 100 | StockQuote(DateTime.parse("2019-02-05"), 273.1, 79447070), 101 | StockQuote(DateTime.parse("2019-02-04"), 271.96, 60610790), 102 | StockQuote(DateTime.parse("2019-02-01"), 270.06, 85738850), 103 | StockQuote(DateTime.parse("2019-01-31"), 269.93, 103347200), 104 | StockQuote(DateTime.parse("2019-01-30"), 267.58, 92069900), 105 | StockQuote(DateTime.parse("2019-01-29"), 263.41, 65024130), 106 | StockQuote(DateTime.parse("2019-01-28"), 263.76, 85027280), 107 | StockQuote(DateTime.parse("2019-01-25"), 265.78, 96838230), 108 | StockQuote(DateTime.parse("2019-01-24"), 263.55, 58643080), 109 | StockQuote(DateTime.parse("2019-01-23"), 263.41, 84305190), 110 | StockQuote(DateTime.parse("2019-01-22"), 262.86, 115253300), 111 | StockQuote(DateTime.parse("2019-01-18"), 266.46, 124175900), 112 | StockQuote(DateTime.parse("2019-01-17"), 262.96, 94873800), 113 | StockQuote(DateTime.parse("2019-01-16"), 260.98, 77489420), 114 | StockQuote(DateTime.parse("2019-01-15"), 260.35, 84892580), 115 | StockQuote(DateTime.parse("2019-01-14"), 257.4, 69913980), 116 | StockQuote(DateTime.parse("2019-01-11"), 258.98, 72812270), 117 | StockQuote(DateTime.parse("2019-01-10"), 258.88, 96462500), 118 | StockQuote(DateTime.parse("2019-01-09"), 257.97, 94640560), 119 | StockQuote(DateTime.parse("2019-01-08"), 256.77, 101804900), 120 | StockQuote(DateTime.parse("2019-01-07"), 254.38, 102307400), 121 | StockQuote(DateTime.parse("2019-01-04"), 252.39, 142444500), 122 | StockQuote(DateTime.parse("2019-01-03"), 244.21, 142029300), 123 | StockQuote(DateTime.parse("2019-01-02"), 250.18, 126041700), 124 | StockQuote(DateTime.parse("2018-12-31"), 249.92, 143399900), 125 | StockQuote(DateTime.parse("2018-12-28"), 247.75, 152931300), 126 | StockQuote(DateTime.parse("2018-12-27"), 248.07, 184664900), 127 | StockQuote(DateTime.parse("2018-12-26"), 246.18, 217567600), 128 | StockQuote(DateTime.parse("2018-12-24"), 234.34, 147311600), 129 | StockQuote(DateTime.parse("2018-12-21"), 240.7, 252779300), 130 | StockQuote(DateTime.parse("2018-12-20"), 247.17, 247438000), 131 | StockQuote(DateTime.parse("2018-12-19"), 251.26, 214312900), 132 | StockQuote(DateTime.parse("2018-12-18"), 255.08, 133073600), 133 | StockQuote(DateTime.parse("2018-12-17"), 255.36, 165088800), 134 | StockQuote(DateTime.parse("2018-12-14"), 260.47, 116727700), 135 | StockQuote(DateTime.parse("2018-12-13"), 265.37, 96168730), 136 | StockQuote(DateTime.parse("2018-12-12"), 265.46, 97670090), 137 | StockQuote(DateTime.parse("2018-12-11"), 264.13, 120632500), 138 | StockQuote(DateTime.parse("2018-12-10"), 264.07, 151022400), 139 | StockQuote(DateTime.parse("2018-12-07"), 263.57, 160872400), 140 | StockQuote(DateTime.parse("2018-12-06"), 269.84, 203716500), 141 | StockQuote(DateTime.parse("2018-12-04"), 270.25, 176092500), 142 | StockQuote(DateTime.parse("2018-12-03"), 279.3, 102782300), 143 | StockQuote(DateTime.parse("2018-11-30"), 275.65, 95107720), 144 | StockQuote(DateTime.parse("2018-11-29"), 273.98, 81616860), 145 | StockQuote(DateTime.parse("2018-11-28"), 274.58, 127124600), 146 | StockQuote(DateTime.parse("2018-11-27"), 268.4, 75273810), 147 | StockQuote(DateTime.parse("2018-11-26"), 267.5, 79705470), 148 | StockQuote(DateTime.parse("2018-11-23"), 263.25, 42807880), 149 | StockQuote(DateTime.parse("2018-11-21"), 265.02, 73868530), 150 | StockQuote(DateTime.parse("2018-11-20"), 264.12, 135506200), 151 | StockQuote(DateTime.parse("2018-11-19"), 269.1, 102555300), 152 | StockQuote(DateTime.parse("2018-11-16"), 273.73, 126601900), 153 | StockQuote(DateTime.parse("2018-11-15"), 273.02, 134732600), 154 | StockQuote(DateTime.parse("2018-11-14"), 270.2, 124389400), 155 | StockQuote(DateTime.parse("2018-11-13"), 272.06, 97769120), 156 | StockQuote(DateTime.parse("2018-11-12"), 272.57, 99129960), 157 | StockQuote(DateTime.parse("2018-11-09"), 277.76, 98751520), 158 | StockQuote(DateTime.parse("2018-11-08"), 280.5, 65315460), 159 | StockQuote(DateTime.parse("2018-11-07"), 281.01, 102473700), 160 | StockQuote(DateTime.parse("2018-11-06"), 275.12, 58983590), 161 | StockQuote(DateTime.parse("2018-11-05"), 273.39, 65439740), 162 | StockQuote(DateTime.parse("2018-11-02"), 271.89, 122439600), 163 | StockQuote(DateTime.parse("2018-11-01"), 273.51, 98741380), 164 | StockQuote(DateTime.parse("2018-10-31"), 270.63, 127608600), 165 | StockQuote(DateTime.parse("2018-10-30"), 267.77, 156591000), 166 | StockQuote(DateTime.parse("2018-10-29"), 263.86, 159000800), 167 | StockQuote(DateTime.parse("2018-10-26"), 265.33, 201334300), 168 | StockQuote(DateTime.parse("2018-10-25"), 270.08, 137407500), 169 | StockQuote(DateTime.parse("2018-10-24"), 265.32, 175529100), 170 | StockQuote(DateTime.parse("2018-10-23"), 273.61, 145303800), 171 | StockQuote(DateTime.parse("2018-10-22"), 275.01, 82135740), 172 | StockQuote(DateTime.parse("2018-10-19"), 276.25, 139420600), 173 | StockQuote(DateTime.parse("2018-10-18"), 276.4, 134193100), 174 | StockQuote(DateTime.parse("2018-10-17"), 280.45, 110264900), 175 | StockQuote(DateTime.parse("2018-10-16"), 280.4, 118066700), 176 | StockQuote(DateTime.parse("2018-10-15"), 274.4, 101950500), 177 | StockQuote(DateTime.parse("2018-10-12"), 275.95, 182875500), 178 | StockQuote(DateTime.parse("2018-10-11"), 272.17, 272817700), 179 | StockQuote(DateTime.parse("2018-10-10"), 278.3, 211986800), 180 | StockQuote(DateTime.parse("2018-10-09"), 287.4, 74119800), 181 | StockQuote(DateTime.parse("2018-10-08"), 287.82, 87632150), 182 | StockQuote(DateTime.parse("2018-10-05"), 287.82, 105802000), 183 | StockQuote(DateTime.parse("2018-10-04"), 289.44, 111364900), 184 | StockQuote(DateTime.parse("2018-10-03"), 291.72, 64562270), 185 | StockQuote(DateTime.parse("2018-10-02"), 291.56, 47142170), 186 | StockQuote(DateTime.parse("2018-10-01"), 291.73, 61989950), 187 | StockQuote(DateTime.parse("2018-09-28"), 290.72, 70041610), 188 | StockQuote(DateTime.parse("2018-09-27"), 290.69, 59169340), 189 | StockQuote(DateTime.parse("2018-09-26"), 289.88, 79534430), 190 | StockQuote(DateTime.parse("2018-09-25"), 290.75, 44311990), 191 | StockQuote(DateTime.parse("2018-09-24"), 291.02, 53235550), 192 | StockQuote(DateTime.parse("2018-09-21"), 291.99, 105393700), 193 | StockQuote(DateTime.parse("2018-09-20"), 293.58, 100066700), 194 | StockQuote(DateTime.parse("2018-09-19"), 291.22, 48973100), 195 | StockQuote(DateTime.parse("2018-09-18"), 290.91, 61839370), 196 | StockQuote(DateTime.parse("2018-09-17"), 289.34, 67988580), 197 | StockQuote(DateTime.parse("2018-09-14"), 290.88, 54931910), 198 | StockQuote(DateTime.parse("2018-09-13"), 290.83, 50918630), 199 | StockQuote(DateTime.parse("2018-09-12"), 289.12, 59732730), 200 | StockQuote(DateTime.parse("2018-09-11"), 289.05, 50392160), 201 | StockQuote(DateTime.parse("2018-09-10"), 288.1, 49916740), 202 | StockQuote(DateTime.parse("2018-09-07"), 287.6, 72113310), 203 | StockQuote(DateTime.parse("2018-09-06"), 288.16, 65822170), 204 | StockQuote(DateTime.parse("2018-09-05"), 289.03, 71962480), 205 | StockQuote(DateTime.parse("2018-09-04"), 289.81, 57127320), 206 | StockQuote(DateTime.parse("2018-08-31"), 290.31, 65239910), 207 | StockQuote(DateTime.parse("2018-08-30"), 290.3, 61132280), 208 | StockQuote(DateTime.parse("2018-08-29"), 291.48, 61428440), 209 | StockQuote(DateTime.parse("2018-08-28"), 289.92, 46825850), 210 | StockQuote(DateTime.parse("2018-08-27"), 289.78, 56960210), 211 | StockQuote(DateTime.parse("2018-08-24"), 287.51, 57455140), 212 | StockQuote(DateTime.parse("2018-08-23"), 285.79, 49159070), 213 | StockQuote(DateTime.parse("2018-08-22"), 286.17, 44860080), 214 | StockQuote(DateTime.parse("2018-08-21"), 286.34, 66735600), 215 | StockQuote(DateTime.parse("2018-08-20"), 285.67, 39617170), 216 | StockQuote(DateTime.parse("2018-08-17"), 285.06, 65486180), 217 | StockQuote(DateTime.parse("2018-08-16"), 284.06, 69735140), 218 | StockQuote(DateTime.parse("2018-08-15"), 281.78, 102770900), 219 | StockQuote(DateTime.parse("2018-08-14"), 283.9, 43699800), 220 | StockQuote(DateTime.parse("2018-08-13"), 282.1, 65603520), 221 | StockQuote(DateTime.parse("2018-08-10"), 283.16, 76861430), 222 | StockQuote(DateTime.parse("2018-08-09"), 285.07, 35577000), 223 | StockQuote(DateTime.parse("2018-08-08"), 285.46, 41980470), 224 | StockQuote(DateTime.parse("2018-08-07"), 285.58, 43055550), 225 | StockQuote(DateTime.parse("2018-08-06"), 284.64, 39089820), 226 | StockQuote(DateTime.parse("2018-08-03"), 283.6, 53903840), 227 | StockQuote(DateTime.parse("2018-08-02"), 282.39, 63362570), 228 | StockQuote(DateTime.parse("2018-08-01"), 280.86, 53750270), 229 | StockQuote(DateTime.parse("2018-07-31"), 281.33, 68048690), 230 | StockQuote(DateTime.parse("2018-07-30"), 279.95, 63129490), 231 | StockQuote(DateTime.parse("2018-07-27"), 281.42, 76745550), 232 | StockQuote(DateTime.parse("2018-07-26"), 283.34, 57764990), 233 | StockQuote(DateTime.parse("2018-07-25"), 284.01, 78136310), 234 | StockQuote(DateTime.parse("2018-07-24"), 281.61, 67830670), 235 | StockQuote(DateTime.parse("2018-07-23"), 280.2, 46971110), 236 | StockQuote(DateTime.parse("2018-07-20"), 279.68, 83120620), 237 | StockQuote(DateTime.parse("2018-07-19"), 280, 61337540), 238 | StockQuote(DateTime.parse("2018-07-18"), 281.06, 44475860), 239 | StockQuote(DateTime.parse("2018-07-17"), 280.47, 52171530), 240 | StockQuote(DateTime.parse("2018-07-16"), 279.34, 48070320), 241 | StockQuote(DateTime.parse("2018-07-13"), 279.59, 48211510), 242 | StockQuote(DateTime.parse("2018-07-12"), 279.37, 59986150), 243 | StockQuote(DateTime.parse("2018-07-11"), 276.86, 76905490), 244 | StockQuote(DateTime.parse("2018-07-10"), 278.9, 51162450), 245 | StockQuote(DateTime.parse("2018-07-09"), 277.9, 49652160), 246 | StockQuote(DateTime.parse("2018-07-06"), 275.42, 66414600), 247 | StockQuote(DateTime.parse("2018-07-05"), 273.11, 56621680), 248 | StockQuote(DateTime.parse("2018-07-03"), 270.9, 42187070), 249 | StockQuote(DateTime.parse("2018-07-02"), 271.86, 63370550), 250 | StockQuote(DateTime.parse("2018-06-29"), 271.28, 97444490), 251 | StockQuote(DateTime.parse("2018-06-28"), 270.89, 76511740), 252 | StockQuote(DateTime.parse("2018-06-27"), 269.35, 104838900), 253 | StockQuote(DateTime.parse("2018-06-26"), 271.6, 68525150), 254 | StockQuote(DateTime.parse("2018-06-25"), 271, 137668700), 255 | StockQuote(DateTime.parse("2018-06-22"), 274.74, 56516380), 256 | StockQuote(DateTime.parse("2018-06-21"), 274.24, 70864990), 257 | StockQuote(DateTime.parse("2018-06-20"), 275.97, 53652060), 258 | StockQuote(DateTime.parse("2018-06-19"), 275.5, 97395160), 259 | StockQuote(DateTime.parse("2018-06-18"), 276.56, 52549580), 260 | StockQuote(DateTime.parse("2018-06-15"), 277.13, 119448800), 261 | StockQuote(DateTime.parse("2018-06-14"), 278.73, 76394330), 262 | StockQuote(DateTime.parse("2018-06-13"), 278.03, 78889800), 263 | StockQuote(DateTime.parse("2018-06-12"), 278.92, 72240090), 264 | StockQuote(DateTime.parse("2018-06-11"), 278.56, 58757260), 265 | StockQuote(DateTime.parse("2018-06-08"), 278.19, 71981550), 266 | StockQuote(DateTime.parse("2018-06-07"), 277.37, 72882070), 267 | StockQuote(DateTime.parse("2018-06-06"), 277.4, 62498610), 268 | StockQuote(DateTime.parse("2018-06-05"), 275.1, 50988090), 269 | StockQuote(DateTime.parse("2018-06-04"), 274.9, 45188240), 270 | StockQuote(DateTime.parse("2018-06-01"), 273.6, 71088010), 271 | StockQuote(DateTime.parse("2018-05-31"), 270.94, 93426970), 272 | StockQuote(DateTime.parse("2018-05-30"), 272.61, 69396900), 273 | StockQuote(DateTime.parse("2018-05-29"), 269.02, 115692100), 274 | StockQuote(DateTime.parse("2018-05-25"), 272.15, 56329040), 275 | StockQuote(DateTime.parse("2018-05-24"), 272.8, 75966080), 276 | StockQuote(DateTime.parse("2018-05-23"), 273.36, 64605540), 277 | StockQuote(DateTime.parse("2018-05-22"), 272.61, 52880880), 278 | StockQuote(DateTime.parse("2018-05-21"), 273.37, 57738110), 279 | StockQuote(DateTime.parse("2018-05-18"), 271.33, 64174990), 280 | StockQuote(DateTime.parse("2018-05-17"), 272.01, 56371150), 281 | StockQuote(DateTime.parse("2018-05-16"), 272.24, 53759700), 282 | StockQuote(DateTime.parse("2018-05-15"), 271.1, 86876380), 283 | StockQuote(DateTime.parse("2018-05-14"), 272.98, 54702570), 284 | StockQuote(DateTime.parse("2018-05-11"), 272.85, 59725680), 285 | StockQuote(DateTime.parse("2018-05-10"), 272.02, 70764000), 286 | StockQuote(DateTime.parse("2018-05-09"), 269.5, 59538590), 287 | StockQuote(DateTime.parse("2018-05-08"), 266.92, 67338050), 288 | StockQuote(DateTime.parse("2018-05-07"), 266.92, 55054350), 289 | StockQuote(DateTime.parse("2018-05-04"), 266.02, 91154240), 290 | StockQuote(DateTime.parse("2018-05-03"), 262.62, 136144700), 291 | StockQuote(DateTime.parse("2018-05-02"), 263.2, 85501550), 292 | StockQuote(DateTime.parse("2018-05-01"), 264.98, 75986700), 293 | StockQuote(DateTime.parse("2018-04-30"), 264.51, 80475460), 294 | StockQuote(DateTime.parse("2018-04-27"), 266.56, 57021920), 295 | StockQuote(DateTime.parse("2018-04-26"), 266.31, 67441210), 296 | StockQuote(DateTime.parse("2018-04-25"), 263.63, 103238300), 297 | StockQuote(DateTime.parse("2018-04-24"), 262.98, 112734000), 298 | StockQuote(DateTime.parse("2018-04-23"), 266.57, 65463180), 299 | StockQuote(DateTime.parse("2018-04-20"), 266.61, 99469420), 300 | StockQuote(DateTime.parse("2018-04-19"), 268.89, 77570630), 301 | StockQuote(DateTime.parse("2018-04-18"), 270.39, 57073780), 302 | StockQuote(DateTime.parse("2018-04-17"), 270.19, 64011070), 303 | StockQuote(DateTime.parse("2018-04-16"), 267.33, 62762920), 304 | StockQuote(DateTime.parse("2018-04-13"), 265.15, 84883690), 305 | StockQuote(DateTime.parse("2018-04-12"), 265.93, 68430410), 306 | StockQuote(DateTime.parse("2018-04-11"), 263.76, 91002710), 307 | StockQuote(DateTime.parse("2018-04-10"), 265.15, 105267300), 308 | ]; 309 | 310 | 311 | List dowQuotes = [ 312 | StockQuote(DateTime.parse("2019-04-10"), 26157.16, 0), 313 | StockQuote(DateTime.parse("2019-04-09"), 26150.58, 0), 314 | StockQuote(DateTime.parse("2019-04-08"), 26341.02, 0), 315 | StockQuote(DateTime.parse("2019-04-05"), 26424.99, 0), 316 | StockQuote(DateTime.parse("2019-04-04"), 26384.63, 0), 317 | StockQuote(DateTime.parse("2019-04-03"), 26218.13, 0), 318 | StockQuote(DateTime.parse("2019-04-02"), 26179.13, 0), 319 | StockQuote(DateTime.parse("2019-04-01"), 26258.42, 0), 320 | StockQuote(DateTime.parse("2019-03-29"), 25928.68, 0), 321 | StockQuote(DateTime.parse("2019-03-28"), 25717.46, 0), 322 | StockQuote(DateTime.parse("2019-03-27"), 25625.59, 0), 323 | StockQuote(DateTime.parse("2019-03-26"), 25657.73, 0), 324 | StockQuote(DateTime.parse("2019-03-25"), 25516.83, 0), 325 | StockQuote(DateTime.parse("2019-03-22"), 25502.32, 0), 326 | StockQuote(DateTime.parse("2019-03-21"), 25962.51, 0), 327 | StockQuote(DateTime.parse("2019-03-20"), 25745.67, 0), 328 | StockQuote(DateTime.parse("2019-03-19"), 25887.38, 0), 329 | StockQuote(DateTime.parse("2019-03-18"), 25914.1, 0), 330 | StockQuote(DateTime.parse("2019-03-15"), 25848.87, 0), 331 | StockQuote(DateTime.parse("2019-03-14"), 25709.94, 0), 332 | StockQuote(DateTime.parse("2019-03-13"), 25702.89, 0), 333 | StockQuote(DateTime.parse("2019-03-12"), 25554.66, 0), 334 | StockQuote(DateTime.parse("2019-03-11"), 25650.88, 0), 335 | StockQuote(DateTime.parse("2019-03-08"), 25450.24, 0), 336 | StockQuote(DateTime.parse("2019-03-07"), 25473.23, 0), 337 | StockQuote(DateTime.parse("2019-03-06"), 25673.46, 0), 338 | StockQuote(DateTime.parse("2019-03-05"), 25806.63, 0), 339 | StockQuote(DateTime.parse("2019-03-04"), 25819.65, 0), 340 | StockQuote(DateTime.parse("2019-03-01"), 26026.32, 0), 341 | StockQuote(DateTime.parse("2019-02-28"), 25916, 0), 342 | StockQuote(DateTime.parse("2019-02-27"), 25985.16, 0), 343 | StockQuote(DateTime.parse("2019-02-26"), 26057.98, 0), 344 | StockQuote(DateTime.parse("2019-02-25"), 26091.95, 0), 345 | StockQuote(DateTime.parse("2019-02-22"), 26031.81, 0), 346 | StockQuote(DateTime.parse("2019-02-21"), 25850.63, 0), 347 | StockQuote(DateTime.parse("2019-02-20"), 25954.44, 0), 348 | StockQuote(DateTime.parse("2019-02-19"), 25891.32, 0), 349 | StockQuote(DateTime.parse("2019-02-15"), 25883.25, 0), 350 | StockQuote(DateTime.parse("2019-02-14"), 25439.39, 0), 351 | StockQuote(DateTime.parse("2019-02-13"), 25543.27, 0), 352 | StockQuote(DateTime.parse("2019-02-12"), 25425.76, 0), 353 | StockQuote(DateTime.parse("2019-02-11"), 25053.11, 0), 354 | StockQuote(DateTime.parse("2019-02-08"), 25106.33, 0), 355 | StockQuote(DateTime.parse("2019-02-07"), 25169.53, 0), 356 | StockQuote(DateTime.parse("2019-02-06"), 25390.3, 0), 357 | StockQuote(DateTime.parse("2019-02-05"), 25411.52, 0), 358 | StockQuote(DateTime.parse("2019-02-04"), 25239.37, 0), 359 | StockQuote(DateTime.parse("2019-02-01"), 25063.89, 0), 360 | StockQuote(DateTime.parse("2019-01-31"), 24999.67, 0), 361 | StockQuote(DateTime.parse("2019-01-30"), 25014.86, 0), 362 | StockQuote(DateTime.parse("2019-01-29"), 24579.96, 0), 363 | StockQuote(DateTime.parse("2019-01-28"), 24528.22, 0), 364 | StockQuote(DateTime.parse("2019-01-25"), 24737.2, 0), 365 | StockQuote(DateTime.parse("2019-01-24"), 24553.24, 0), 366 | StockQuote(DateTime.parse("2019-01-23"), 24575.62, 0), 367 | StockQuote(DateTime.parse("2019-01-22"), 24404.48, 0), 368 | StockQuote(DateTime.parse("2019-01-18"), 24706.35, 0), 369 | StockQuote(DateTime.parse("2019-01-17"), 24370.1, 0), 370 | StockQuote(DateTime.parse("2019-01-16"), 24207.16, 0), 371 | StockQuote(DateTime.parse("2019-01-15"), 24065.59, 0), 372 | StockQuote(DateTime.parse("2019-01-14"), 23909.84, 0), 373 | StockQuote(DateTime.parse("2019-01-11"), 23995.95, 0), 374 | StockQuote(DateTime.parse("2019-01-10"), 24001.92, 0), 375 | StockQuote(DateTime.parse("2019-01-09"), 23879.12, 0), 376 | StockQuote(DateTime.parse("2019-01-08"), 23787.45, 0), 377 | StockQuote(DateTime.parse("2019-01-07"), 23531.35, 0), 378 | StockQuote(DateTime.parse("2019-01-04"), 23433.16, 0), 379 | StockQuote(DateTime.parse("2019-01-03"), 22686.22, 0), 380 | StockQuote(DateTime.parse("2019-01-02"), 23346.24, 0), 381 | StockQuote(DateTime.parse("2018-12-31"), 23327.46, 0), 382 | StockQuote(DateTime.parse("2018-12-28"), 23062.4, 0), 383 | StockQuote(DateTime.parse("2018-12-27"), 23138.82, 0), 384 | StockQuote(DateTime.parse("2018-12-26"), 22878.45, 0), 385 | StockQuote(DateTime.parse("2018-12-24"), 21792.2, 0), 386 | StockQuote(DateTime.parse("2018-12-21"), 22445.37, 0), 387 | StockQuote(DateTime.parse("2018-12-20"), 22859.6, 0), 388 | StockQuote(DateTime.parse("2018-12-19"), 23323.66, 0), 389 | StockQuote(DateTime.parse("2018-12-18"), 23675.64, 0), 390 | StockQuote(DateTime.parse("2018-12-17"), 23592.98, 0), 391 | StockQuote(DateTime.parse("2018-12-14"), 24100.51, 0), 392 | StockQuote(DateTime.parse("2018-12-13"), 24597.38, 0), 393 | StockQuote(DateTime.parse("2018-12-12"), 24527.27, 0), 394 | StockQuote(DateTime.parse("2018-12-11"), 24370.24, 0), 395 | StockQuote(DateTime.parse("2018-12-10"), 24423.26, 0), 396 | StockQuote(DateTime.parse("2018-12-07"), 24388.95, 0), 397 | StockQuote(DateTime.parse("2018-12-06"), 24947.67, 0), 398 | StockQuote(DateTime.parse("2018-12-04"), 25027.07, 0), 399 | StockQuote(DateTime.parse("2018-12-03"), 25826.43, 0), 400 | StockQuote(DateTime.parse("2018-11-30"), 25538.46, 0), 401 | StockQuote(DateTime.parse("2018-11-29"), 25338.84, 0), 402 | StockQuote(DateTime.parse("2018-11-28"), 25366.43, 0), 403 | StockQuote(DateTime.parse("2018-11-27"), 24748.73, 0), 404 | StockQuote(DateTime.parse("2018-11-26"), 24640.24, 0), 405 | StockQuote(DateTime.parse("2018-11-23"), 24285.95, 0), 406 | StockQuote(DateTime.parse("2018-11-21"), 24464.69, 0), 407 | StockQuote(DateTime.parse("2018-11-20"), 24465.64, 0), 408 | StockQuote(DateTime.parse("2018-11-19"), 25017.44, 0), 409 | StockQuote(DateTime.parse("2018-11-16"), 25413.22, 0), 410 | StockQuote(DateTime.parse("2018-11-15"), 25289.27, 0), 411 | StockQuote(DateTime.parse("2018-11-14"), 25080.5, 0), 412 | StockQuote(DateTime.parse("2018-11-13"), 25286.49, 0), 413 | StockQuote(DateTime.parse("2018-11-12"), 25387.18, 0), 414 | StockQuote(DateTime.parse("2018-11-09"), 25989.3, 0), 415 | StockQuote(DateTime.parse("2018-11-08"), 26191.22, 0), 416 | StockQuote(DateTime.parse("2018-11-07"), 26180.3, 0), 417 | StockQuote(DateTime.parse("2018-11-06"), 25635.01, 0), 418 | StockQuote(DateTime.parse("2018-11-05"), 25461.7, 0), 419 | StockQuote(DateTime.parse("2018-11-02"), 25270.83, 0), 420 | StockQuote(DateTime.parse("2018-11-01"), 25380.74, 0), 421 | StockQuote(DateTime.parse("2018-10-31"), 25115.76, 0), 422 | StockQuote(DateTime.parse("2018-10-30"), 24874.64, 0), 423 | StockQuote(DateTime.parse("2018-10-29"), 24442.92, 0), 424 | StockQuote(DateTime.parse("2018-10-26"), 24688.31, 0), 425 | StockQuote(DateTime.parse("2018-10-25"), 24984.55, 0), 426 | StockQuote(DateTime.parse("2018-10-24"), 24583.42, 0), 427 | StockQuote(DateTime.parse("2018-10-23"), 25191.43, 0), 428 | StockQuote(DateTime.parse("2018-10-22"), 25317.41, 0), 429 | StockQuote(DateTime.parse("2018-10-19"), 25444.34, 0), 430 | StockQuote(DateTime.parse("2018-10-18"), 25379.45, 0), 431 | StockQuote(DateTime.parse("2018-10-17"), 25706.68, 0), 432 | StockQuote(DateTime.parse("2018-10-16"), 25798.42, 0), 433 | StockQuote(DateTime.parse("2018-10-15"), 25250.55, 0), 434 | StockQuote(DateTime.parse("2018-10-12"), 25339.99, 0), 435 | StockQuote(DateTime.parse("2018-10-11"), 25052.83, 0), 436 | StockQuote(DateTime.parse("2018-10-10"), 25598.74, 0), 437 | StockQuote(DateTime.parse("2018-10-09"), 26430.57, 0), 438 | StockQuote(DateTime.parse("2018-10-08"), 26486.78, 0), 439 | StockQuote(DateTime.parse("2018-10-05"), 26447.05, 0), 440 | StockQuote(DateTime.parse("2018-10-04"), 26627.48, 0), 441 | StockQuote(DateTime.parse("2018-10-03"), 26828.39, 0), 442 | StockQuote(DateTime.parse("2018-10-02"), 26773.94, 0), 443 | StockQuote(DateTime.parse("2018-10-01"), 26651.21, 0), 444 | StockQuote(DateTime.parse("2018-09-28"), 26458.31, 0), 445 | StockQuote(DateTime.parse("2018-09-27"), 26439.93, 0), 446 | StockQuote(DateTime.parse("2018-09-26"), 26385.28, 0), 447 | StockQuote(DateTime.parse("2018-09-25"), 26492.21, 0), 448 | StockQuote(DateTime.parse("2018-09-24"), 26562.05, 0), 449 | StockQuote(DateTime.parse("2018-09-21"), 26743.5, 0), 450 | StockQuote(DateTime.parse("2018-09-20"), 26656.98, 0), 451 | StockQuote(DateTime.parse("2018-09-19"), 26405.76, 0), 452 | StockQuote(DateTime.parse("2018-09-18"), 26246.96, 0), 453 | StockQuote(DateTime.parse("2018-09-17"), 26062.12, 0), 454 | StockQuote(DateTime.parse("2018-09-14"), 26154.67, 0), 455 | StockQuote(DateTime.parse("2018-09-13"), 26145.99, 0), 456 | StockQuote(DateTime.parse("2018-09-12"), 25998.92, 0), 457 | StockQuote(DateTime.parse("2018-09-11"), 25971.06, 0), 458 | StockQuote(DateTime.parse("2018-09-10"), 25857.07, 0), 459 | StockQuote(DateTime.parse("2018-09-07"), 25916.54, 0), 460 | StockQuote(DateTime.parse("2018-09-06"), 25995.87, 0), 461 | StockQuote(DateTime.parse("2018-09-05"), 25974.99, 0), 462 | StockQuote(DateTime.parse("2018-09-04"), 25952.48, 0), 463 | StockQuote(DateTime.parse("2018-08-31"), 25964.82, 0), 464 | StockQuote(DateTime.parse("2018-08-30"), 25986.92, 0), 465 | StockQuote(DateTime.parse("2018-08-29"), 26124.57, 0), 466 | StockQuote(DateTime.parse("2018-08-28"), 26064.02, 0), 467 | StockQuote(DateTime.parse("2018-08-27"), 26049.64, 0), 468 | StockQuote(DateTime.parse("2018-08-24"), 25790.35, 0), 469 | StockQuote(DateTime.parse("2018-08-23"), 25656.98, 0), 470 | StockQuote(DateTime.parse("2018-08-22"), 25733.6, 0), 471 | StockQuote(DateTime.parse("2018-08-21"), 25822.29, 0), 472 | StockQuote(DateTime.parse("2018-08-20"), 25758.69, 0), 473 | StockQuote(DateTime.parse("2018-08-17"), 25669.32, 0), 474 | StockQuote(DateTime.parse("2018-08-16"), 25558.73, 0), 475 | StockQuote(DateTime.parse("2018-08-15"), 25162.41, 0), 476 | StockQuote(DateTime.parse("2018-08-14"), 25299.92, 0), 477 | StockQuote(DateTime.parse("2018-08-13"), 25187.7, 0), 478 | StockQuote(DateTime.parse("2018-08-10"), 25313.14, 0), 479 | StockQuote(DateTime.parse("2018-08-09"), 25509.23, 0), 480 | StockQuote(DateTime.parse("2018-08-08"), 25583.75, 0), 481 | StockQuote(DateTime.parse("2018-08-07"), 25628.91, 0), 482 | StockQuote(DateTime.parse("2018-08-06"), 25502.18, 0), 483 | StockQuote(DateTime.parse("2018-08-03"), 25462.58, 0), 484 | StockQuote(DateTime.parse("2018-08-02"), 25326.16, 0), 485 | StockQuote(DateTime.parse("2018-08-01"), 25333.82, 0), 486 | StockQuote(DateTime.parse("2018-07-31"), 25415.19, 0), 487 | StockQuote(DateTime.parse("2018-07-30"), 25306.83, 0), 488 | StockQuote(DateTime.parse("2018-07-27"), 25451.06, 0), 489 | StockQuote(DateTime.parse("2018-07-26"), 25527.07, 0), 490 | StockQuote(DateTime.parse("2018-07-25"), 25414.1, 0), 491 | StockQuote(DateTime.parse("2018-07-24"), 25241.94, 0), 492 | StockQuote(DateTime.parse("2018-07-23"), 25044.29, 0), 493 | StockQuote(DateTime.parse("2018-07-20"), 25058.12, 0), 494 | StockQuote(DateTime.parse("2018-07-19"), 25064.5, 0), 495 | StockQuote(DateTime.parse("2018-07-18"), 25199.29, 0), 496 | StockQuote(DateTime.parse("2018-07-17"), 25119.89, 0), 497 | StockQuote(DateTime.parse("2018-07-16"), 25064.36, 0), 498 | StockQuote(DateTime.parse("2018-07-13"), 25019.41, 0), 499 | StockQuote(DateTime.parse("2018-07-12"), 24924.89, 0), 500 | StockQuote(DateTime.parse("2018-07-11"), 24700.45, 0), 501 | StockQuote(DateTime.parse("2018-07-10"), 24919.66, 0), 502 | StockQuote(DateTime.parse("2018-07-09"), 24776.59, 0), 503 | StockQuote(DateTime.parse("2018-07-06"), 24456.48, 0), 504 | StockQuote(DateTime.parse("2018-07-05"), 24356.74, 0), 505 | StockQuote(DateTime.parse("2018-07-03"), 24174.82, 0), 506 | StockQuote(DateTime.parse("2018-07-02"), 24307.18, 0), 507 | StockQuote(DateTime.parse("2018-06-29"), 24271.41, 0), 508 | StockQuote(DateTime.parse("2018-06-28"), 24216.05, 0), 509 | StockQuote(DateTime.parse("2018-06-27"), 24117.59, 0), 510 | StockQuote(DateTime.parse("2018-06-26"), 24283.11, 0), 511 | StockQuote(DateTime.parse("2018-06-25"), 24252.8, 0), 512 | StockQuote(DateTime.parse("2018-06-22"), 24580.89, 0), 513 | StockQuote(DateTime.parse("2018-06-21"), 24461.7, 0), 514 | StockQuote(DateTime.parse("2018-06-20"), 24657.8, 0), 515 | StockQuote(DateTime.parse("2018-06-19"), 24700.21, 0), 516 | StockQuote(DateTime.parse("2018-06-18"), 24987.47, 0), 517 | StockQuote(DateTime.parse("2018-06-15"), 25090.48, 0), 518 | StockQuote(DateTime.parse("2018-06-14"), 25175.31, 0), 519 | StockQuote(DateTime.parse("2018-06-13"), 25201.2, 0), 520 | StockQuote(DateTime.parse("2018-06-12"), 25320.73, 0), 521 | StockQuote(DateTime.parse("2018-06-11"), 25322.31, 0), 522 | StockQuote(DateTime.parse("2018-06-08"), 25316.53, 0), 523 | StockQuote(DateTime.parse("2018-06-07"), 25241.41, 0), 524 | StockQuote(DateTime.parse("2018-06-06"), 25146.39, 0), 525 | StockQuote(DateTime.parse("2018-06-05"), 24799.98, 0), 526 | StockQuote(DateTime.parse("2018-06-04"), 24813.69, 0), 527 | StockQuote(DateTime.parse("2018-06-01"), 24635.21, 0), 528 | StockQuote(DateTime.parse("2018-05-31"), 24415.84, 0), 529 | StockQuote(DateTime.parse("2018-05-30"), 24667.78, 0), 530 | StockQuote(DateTime.parse("2018-05-29"), 24361.45, 0), 531 | StockQuote(DateTime.parse("2018-05-25"), 24753.09, 0), 532 | StockQuote(DateTime.parse("2018-05-24"), 24811.76, 0), 533 | StockQuote(DateTime.parse("2018-05-23"), 24886.81, 0), 534 | StockQuote(DateTime.parse("2018-05-22"), 24834.41, 0), 535 | StockQuote(DateTime.parse("2018-05-21"), 25013.29, 0), 536 | StockQuote(DateTime.parse("2018-05-18"), 24715.09, 0), 537 | StockQuote(DateTime.parse("2018-05-17"), 24713.98, 0), 538 | StockQuote(DateTime.parse("2018-05-16"), 24768.93, 0), 539 | StockQuote(DateTime.parse("2018-05-15"), 24706.41, 0), 540 | StockQuote(DateTime.parse("2018-05-14"), 24899.41, 0), 541 | StockQuote(DateTime.parse("2018-05-11"), 24831.17, 0), 542 | StockQuote(DateTime.parse("2018-05-10"), 24739.53, 0), 543 | StockQuote(DateTime.parse("2018-05-09"), 24542.54, 0), 544 | StockQuote(DateTime.parse("2018-05-08"), 24360.21, 0), 545 | StockQuote(DateTime.parse("2018-05-07"), 24357.32, 0), 546 | StockQuote(DateTime.parse("2018-05-04"), 24262.51, 0), 547 | StockQuote(DateTime.parse("2018-05-03"), 23930.15, 0), 548 | StockQuote(DateTime.parse("2018-05-02"), 23924.98, 0), 549 | StockQuote(DateTime.parse("2018-05-01"), 24099.05, 0), 550 | StockQuote(DateTime.parse("2018-04-30"), 24163.15, 0), 551 | StockQuote(DateTime.parse("2018-04-27"), 24311.19, 0), 552 | StockQuote(DateTime.parse("2018-04-26"), 24322.34, 0), 553 | StockQuote(DateTime.parse("2018-04-25"), 24083.83, 0), 554 | StockQuote(DateTime.parse("2018-04-24"), 24024.13, 0), 555 | StockQuote(DateTime.parse("2018-04-23"), 24448.69, 0), 556 | StockQuote(DateTime.parse("2018-04-20"), 24462.94, 0), 557 | StockQuote(DateTime.parse("2018-04-19"), 24664.89, 0), 558 | StockQuote(DateTime.parse("2018-04-18"), 24748.07, 0), 559 | StockQuote(DateTime.parse("2018-04-17"), 24786.63, 0), 560 | StockQuote(DateTime.parse("2018-04-16"), 24573.04, 0), 561 | StockQuote(DateTime.parse("2018-04-13"), 24360.14, 0), 562 | StockQuote(DateTime.parse("2018-04-12"), 24483.05, 0), 563 | StockQuote(DateTime.parse("2018-04-11"), 24189.45, 0), 564 | StockQuote(DateTime.parse("2018-04-10"), 24408, 0), 565 | ]; 566 | -------------------------------------------------------------------------------- /lib/example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flarts/flart.dart'; 4 | import 'package:flarts/flart_axis.dart'; 5 | import 'package:flarts/flart_data.dart'; 6 | 7 | import 'example_data.dart'; 8 | 9 | void main() => runApp(MyApp()); 10 | 11 | class MyApp extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return MaterialApp( 15 | title: 'Flart Examples', 16 | theme: ThemeData( 17 | brightness: Brightness.dark, 18 | ), 19 | home: FlartExamplePage(), 20 | ); 21 | } 22 | } 23 | 24 | @immutable 25 | class FlartExamplePage extends StatefulWidget { 26 | final Map examples = { 27 | 'Simple Data': SimpleDataExample(), 28 | 'Large Spark': LargeSparkExample(), 29 | 'Sparks': SparkExample(), 30 | 'Multi Data': MultiDataExample(), 31 | }; 32 | 33 | @override 34 | State createState() => FlartExamplePageState(); 35 | } 36 | 37 | class SimpleDataExample extends StatelessWidget { 38 | @override 39 | Widget build(BuildContext context) { 40 | return Padding( 41 | padding: const EdgeInsets.all(8.0), 42 | child: LayoutBuilder( 43 | builder: (context, constraints) => Flart( 44 | constraints.biggest, 45 | [ 46 | flartDataFromIterables( 47 | simplyCount, 48 | simplyDoubles, 49 | rangeAxis: FlartAxis( 50 | direction: Axis.vertical, 51 | minValue: -25, // min value on range axis. 52 | maxValue: 25, // max value on range axis. 53 | labelConfig: AxisLabelConfig( 54 | frequency: AxisLabelFrequency.perGridline, 55 | text: AxisLabelTextSource.interpolateFromDataType, 56 | ), 57 | numGridlines: 4, 58 | ), 59 | ), 60 | ], 61 | ), 62 | ), 63 | ); 64 | } 65 | } 66 | 67 | class LargeSparkExample extends StatelessWidget { 68 | @override 69 | Widget build(BuildContext context) { 70 | return Padding( 71 | padding: const EdgeInsets.all(8.0), 72 | child: LayoutBuilder( 73 | builder: (context, constraints) => Flart.spark( 74 | constraints.biggest, 75 | [ 76 | FlartData( 77 | // This was an easy way to put the first n days on the 78 | // first spark, the next n on the next spark, and so on. 79 | spyQuotes, 80 | rangeFn: (price, i) => price.price, 81 | domainFn: (price, i) => price.timestamp, 82 | plotType: PlotType.line, 83 | ), 84 | ], 85 | ), 86 | ), 87 | ); 88 | } 89 | } 90 | 91 | class SparkExample extends StatelessWidget { 92 | final sparkCharts = []; 93 | final sparkLength = 40; 94 | final numSparkCharts = 4; 95 | 96 | SparkExample() { 97 | for (var i = 0; i < numSparkCharts; i++) { 98 | sparkCharts.add( 99 | Expanded( 100 | child: Padding( 101 | padding: const EdgeInsets.all(8.0), 102 | child: LayoutBuilder( 103 | builder: (context, constraints) => Flart.spark( 104 | constraints.biggest, 105 | [ 106 | FlartData( 107 | // This was an easy way to put the first n days on the 108 | // first spark, the next n on the next spark, and so on. 109 | spyQuotes.sublist( 110 | i * sparkLength, (i + 1) * sparkLength), 111 | rangeFn: (price, i) => price.price, 112 | domainFn: (price, i) => price.timestamp, 113 | plotType: PlotType.line, 114 | ), 115 | ], 116 | ), 117 | ), 118 | ), 119 | ), 120 | ); 121 | } 122 | } 123 | 124 | @override 125 | Widget build(BuildContext context) { 126 | return Container( 127 | child: Column( 128 | children: sparkCharts, 129 | ), 130 | ); 131 | } 132 | } 133 | 134 | class MultiDataExample extends StatelessWidget { 135 | @override 136 | Widget build(BuildContext context) { 137 | return Padding( 138 | padding: const EdgeInsets.all(8.0), 139 | child: LayoutBuilder( 140 | builder: (context, constraints) => Flart( 141 | constraints.biggest, 142 | [ 143 | // DJI prices 144 | FlartData( 145 | dowQuotes, 146 | customColor: Colors.green, 147 | rangeFn: (quote, i) => quote.price, 148 | domainFn: (quote, i) => quote.timestamp, 149 | domainAxisId: 'dates', 150 | rangeAxis: FlartAxis( 151 | direction: Axis.vertical, 152 | minValue: dowPricesData.minRange, 153 | maxValue: dowPricesData.maxRange, 154 | side: Side.left, 155 | labelConfig: AxisLabelConfig( 156 | frequency: AxisLabelFrequency.none, 157 | ), 158 | numGridlines: 0, 159 | ), 160 | ), 161 | // SPY prices 162 | FlartData( 163 | spyQuotes, 164 | customColor: Colors.redAccent, 165 | rangeFn: (price, i) => price.price, 166 | domainFn: (price, i) => price.timestamp, 167 | domainAxisId: 'dates', 168 | rangeAxis: FlartAxis( 169 | direction: Axis.vertical, 170 | minValue: spyPriceData.minRange, 171 | maxValue: spyPriceData.maxRange, 172 | labelConfig: AxisLabelConfig( 173 | frequency: AxisLabelFrequency.perGridline, 174 | text: AxisLabelTextSource.interpolateFromDataType, 175 | ), 176 | numGridlines: 6, 177 | ), 178 | ), 179 | // SPY volume 180 | FlartData( 181 | spyQuotes, 182 | layer: 0, 183 | plotType: PlotType.bar, 184 | customColor: Color(0xAA4488aa), 185 | rangeFn: (price, i) => price.volume, 186 | domainFn: (price, i) => price.timestamp, 187 | domainAxisId: 'dates', 188 | rangeAxis: FlartAxis( 189 | direction: Axis.vertical, 190 | minValue: spyVolumeData.minRange, 191 | maxValue: spyVolumeData.maxRange, 192 | side: Side.left, 193 | labelConfig: AxisLabelConfig( 194 | frequency: AxisLabelFrequency.perGridline, 195 | text: AxisLabelTextSource.interpolateFromDataType, 196 | ), 197 | numGridlines: 6, 198 | ), 199 | ), 200 | ], 201 | sharedAxes: [ 202 | FlartAxis( 203 | direction: Axis.horizontal, 204 | minValue: spyPriceData.minDomain, 205 | maxValue: spyPriceData.maxDomain, 206 | id: 'dates', 207 | labelConfig: AxisLabelConfig( 208 | frequency: AxisLabelFrequency.perGridline, 209 | text: AxisLabelTextSource.interpolateFromDataType, 210 | ), 211 | numGridlines: 8, 212 | ), 213 | ], 214 | ), 215 | ), 216 | ); 217 | } 218 | } 219 | 220 | class FlartExamplePageState extends State { 221 | String selectedExample; 222 | bool rowMode; 223 | 224 | @override 225 | void initState() { 226 | selectedExample = widget.examples.keys.first; 227 | rowMode = false; 228 | 229 | super.initState(); 230 | } 231 | 232 | void onSelectExample(String example) { 233 | setState(() => selectedExample = example); 234 | } 235 | 236 | void onUiButtonPress() { 237 | setState(() => rowMode = !rowMode); 238 | } 239 | 240 | @override 241 | Widget build(BuildContext context) { 242 | final controls = [ 243 | DropdownButton( 244 | value: selectedExample, 245 | items: widget.examples.keys 246 | .map>((key) => DropdownMenuItem( 247 | value: key, 248 | child: Text(key), 249 | )) 250 | .toList(), 251 | onChanged: onSelectExample, 252 | ), 253 | RaisedButton( 254 | child: Text('ui side'), 255 | onPressed: onUiButtonPress, 256 | ), 257 | ]; 258 | 259 | final contents = [ 260 | Container( 261 | child: rowMode 262 | ? Row( 263 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 264 | children: controls, 265 | ) 266 | : Column( 267 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 268 | children: controls, 269 | ), 270 | ), 271 | Expanded( 272 | child: Padding( 273 | padding: const EdgeInsets.all(8.0), 274 | child: selectedExample != null 275 | ? widget.examples[selectedExample] 276 | : Container(), 277 | ), 278 | ), 279 | ]; 280 | 281 | return Scaffold( 282 | appBar: AppBar( 283 | title: Text('Flart Examples', style: Theme.of(context).textTheme.title), 284 | ), 285 | body: Container( 286 | child: rowMode 287 | ? Column( 288 | children: contents, 289 | ) 290 | : Row( 291 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 292 | children: contents, 293 | ), 294 | ), 295 | ); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /lib/example/screenshots/large_spark_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/lib/example/screenshots/large_spark_example.png -------------------------------------------------------------------------------- /lib/example/screenshots/multi_data_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/lib/example/screenshots/multi_data_example.png -------------------------------------------------------------------------------- /lib/example/screenshots/multi_spark_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/lib/example/screenshots/multi_spark_example.png -------------------------------------------------------------------------------- /lib/example/screenshots/simple_data_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtcliatt/Flarts/977c32b8bc557d59c3d065f541138d2b0b1c686b/lib/example/screenshots/simple_data_example.png -------------------------------------------------------------------------------- /lib/flart.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flarts/flart_axis.dart'; 4 | import 'package:flarts/flart_data.dart'; 5 | import 'package:flarts/flart_painter.dart'; 6 | import 'package:flarts/flart_theme.dart'; 7 | 8 | // todo: memoize everything that can be memoized. 9 | // todo: bevel the edges of the chart border. 10 | // todo: use [canvas.clipRect()] to prevent drawing over the rest of the screen. 11 | // todo: make sure repainting happens when/if it should. 12 | 13 | /// Flart, a Flutter chart. 14 | /// 15 | /// todo: write this dartdoc. 16 | class Flart extends StatelessWidget { 17 | final List _dataList; 18 | final List _axes; 19 | final FlartStyle _style; 20 | final Size _size; 21 | 22 | Flart( 23 | this._size, 24 | this._dataList, { 25 | List sharedAxes = const [], 26 | FlartStyle style, 27 | }) : _style = style ?? FlartStyle(), 28 | _axes = [] { 29 | final Map shared = {}; 30 | 31 | sharedAxes.forEach((axis) => shared[axis.id] = axis); 32 | 33 | // Make sure all referenced axes were provided. 34 | _dataList.forEach((data) { 35 | if (data.domainAxisId != null && !shared.containsKey(data.domainAxisId)) { 36 | throw ArgumentError( 37 | 'Axis for referenced ID not provided: ${data.domainAxisId}'); 38 | } 39 | 40 | if (data.rangeAxisId != null && !shared.containsKey(data.rangeAxisId)) { 41 | throw ArgumentError( 42 | 'Axis for referenced ID not provided: ${data.rangeAxisId}'); 43 | } 44 | }); 45 | 46 | _dataList.forEach((data) { 47 | if (data.domainAxisId != null) { 48 | data.domainAxis = shared[data.domainAxisId]; 49 | } else { 50 | if (data.domainAxis == null) { 51 | // If no axis or ID was provided, make a simple one with no labels. 52 | data.domainAxis = FlartAxis( 53 | direction: 54 | Axis.horizontal, 55 | minValue:data.minDomain, 56 | maxValue: data.maxDomain, 57 | // todo: move defaults like these into the theme. 58 | labelConfig: AxisLabelConfig( 59 | frequency: AxisLabelFrequency.none, 60 | ), 61 | numGridlines: 4, 62 | ); 63 | } 64 | 65 | _axes.add(data.domainAxis); 66 | } 67 | 68 | if (data.rangeAxisId != null) { 69 | data.rangeAxis = shared[data.rangeAxisId]; 70 | } else { 71 | if (data.rangeAxis == null) { 72 | // If no axis or ID was provided, make a simple one with no labels. 73 | data.rangeAxis = FlartAxis( 74 | direction: Axis.horizontal, 75 | minValue:data.minDomain, 76 | maxValue: data.maxDomain, 77 | labelConfig: AxisLabelConfig( 78 | frequency: AxisLabelFrequency.none, 79 | ), 80 | numGridlines: 4, 81 | ); 82 | } 83 | 84 | _axes.add(data.rangeAxis); 85 | } 86 | }); 87 | 88 | shared.forEach((_, axis) => _axes.add(axis)); 89 | 90 | _dataList.forEach( 91 | (data) => _verifyDataAxesDirections(data.domainAxis, data.rangeAxis)); 92 | } 93 | 94 | /// Verifies that the given axes have valid directions to make 95 | /// plotting data on the two axes possible. 96 | void _verifyDataAxesDirections(FlartAxis domain, FlartAxis range) { 97 | if (domain.direction == null || 98 | range.direction == null || 99 | domain.direction == range.direction) { 100 | throw ArgumentError( 101 | 'Data can only be plotted on axes with different directions.' 102 | 'Axes directions were: ${domain.direction}, ${range.direction})'); 103 | } 104 | } 105 | 106 | // A sleek line chart with no labels or gridlines. 107 | factory Flart.spark(Size size, List dataList) { 108 | final style = FlartStyle( 109 | borderStyle: Paint() 110 | ..color = Colors.black54 111 | ..style = PaintingStyle.stroke, 112 | backgroundPaint: Paint() 113 | ..color = Colors.black54 114 | ..style = PaintingStyle.fill, 115 | ); 116 | 117 | for (final data in dataList) { 118 | data.customColor = Colors.redAccent; 119 | 120 | data.domainAxis = FlartAxis( 121 | direction:Axis.horizontal, 122 | minValue:dataList.first.minDomain, 123 | maxValue:dataList.first.maxDomain, 124 | labelConfig: AxisLabelConfig( 125 | frequency: AxisLabelFrequency.none, 126 | ), 127 | ); 128 | 129 | data.rangeAxis = FlartAxis( 130 | direction:Axis.vertical, 131 | minValue:dataList.first.minRange, 132 | maxValue:dataList.first.maxRange, 133 | labelConfig: AxisLabelConfig( 134 | frequency: AxisLabelFrequency.none, 135 | ), 136 | ); 137 | } 138 | 139 | return Flart( 140 | size, 141 | dataList, 142 | style: style, 143 | ); 144 | } 145 | 146 | @override 147 | Widget build(BuildContext context) { 148 | return CustomPaint( 149 | size: _size, 150 | painter: FlartPainter( 151 | dataList: _dataList, 152 | style: _style, 153 | axes: _axes, 154 | ), 155 | ); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /lib/flart_axis.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flarts/flart_data.dart'; 4 | import 'package:flarts/label_formatter.dart'; 5 | 6 | class AxisLabel { 7 | final double normalizedDistanceAlongAxis; 8 | final String text; 9 | final TextStyle style; 10 | 11 | AxisLabel(this.normalizedDistanceAlongAxis, this.text, {this.style}); 12 | } 13 | 14 | enum AxisLabelFrequency { 15 | perGridline, 16 | everyOtherGridline, 17 | none, 18 | } 19 | 20 | enum AxisLabelTextSource { 21 | interpolateFromDataType, 22 | useLabelIndex, 23 | } 24 | 25 | class AxisLabelConfig { 26 | AxisLabelFrequency frequency; 27 | AxisLabelTextSource text; 28 | 29 | AxisLabelConfig({this.frequency, this.text}); 30 | } 31 | 32 | class Gridline { 33 | final double normalizedDistanceAlongAxis; 34 | 35 | Gridline(this.normalizedDistanceAlongAxis); 36 | } 37 | 38 | enum Side { left, top, right, bottom } 39 | 40 | /// An axis spanning [minValue] to [maxValue], with an orientation of [direction]. 41 | class FlartAxis { 42 | final Axis direction; 43 | final Side side; 44 | 45 | final double range; 46 | final T minValue; 47 | final T maxValue; 48 | 49 | final List gridlines = []; 50 | final List labels = []; 51 | 52 | final String id; 53 | 54 | FlartAxis({ 55 | @required this.direction, 56 | @required this.minValue, 57 | @required this.maxValue, 58 | this.id, 59 | Side side, 60 | AxisLabelConfig labelConfig, 61 | int numGridlines = 0, 62 | Map customLabels, 63 | List additionalCustomGridLines, 64 | }) : assert(direction != null), 65 | assert(maxValue != null), 66 | assert(minValue != null), 67 | this.side = side != null 68 | ? side 69 | : direction == Axis.vertical ? Side.right : Side.bottom, 70 | range = 71 | distanceFnForType(minValue.runtimeType)(minValue, maxValue).abs() { 72 | final labelToString = 73 | LabelFormatter.labelToStringForType(minValue.runtimeType); 74 | int numLabels; 75 | 76 | if (labelConfig.frequency == AxisLabelFrequency.perGridline) { 77 | numLabels = numGridlines; 78 | } else if (labelConfig.frequency == AxisLabelFrequency.everyOtherGridline) { 79 | numLabels = (numGridlines / 2).floor(); 80 | } else if (labelConfig.frequency == AxisLabelFrequency.none) { 81 | numLabels = 0; 82 | } else { 83 | numLabels = 0; 84 | } 85 | 86 | if (additionalCustomGridLines != null) { 87 | additionalCustomGridLines.forEach((position) { 88 | final norm = position / range; 89 | gridlines.add(Gridline(norm)); 90 | }); 91 | } 92 | 93 | // todo: Use better splits for labels so they make more sense. 94 | if (numGridlines > 0) { 95 | final spacingPerLine = range / (numGridlines + 1); 96 | for (var i = 1; i < numGridlines + 1; i++) { 97 | final distance = i * spacingPerLine; 98 | final normDistance = distance / range; 99 | gridlines.add(Gridline(normDistance)); 100 | } 101 | } 102 | 103 | if (numLabels > 0) { 104 | for (var i = 1; i < numLabels + 1; i++) { 105 | final normDistance = i / (numLabels + 1); 106 | final label = 107 | labelConfig.text == AxisLabelTextSource.interpolateFromDataType 108 | ? labelToString( 109 | interpolate(minValue, other: maxValue, skew: normDistance)) 110 | : '$i'; 111 | 112 | labels.add(AxisLabel(normDistance, label)); 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lib/flart_data.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:flarts/flart_axis.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | /// T is the object type which holds the data to be charted. 7 | /// D is the domain type of the data (e.g., dates, days, numbers, etc) 8 | /// R is the range type of the data (e.g., prices, numbers, etc.) 9 | typedef DomainFn = D Function(T, int); 10 | typedef RangeFn = R Function(T, double); 11 | 12 | // todo: consider adding a ColorFn rather than using a single color per data. 13 | // typedef ColorFn = Color Function(T, int); 14 | 15 | /// A function that returns the measurable distance between any two instances 16 | /// of type [T]. 17 | /// 18 | /// For [num]s, the distance is the difference between the two values. 19 | /// For [DateTime]s, the distance is the milliseconds between the two times. 20 | typedef DistanceFn = double Function(T, T); 21 | 22 | /// Returns a function that returns the distance between two given instances 23 | /// of type [type]. 24 | DistanceFn distanceFnForType(Type type) { 25 | Map distanceFnMap = { 26 | DateTime: (a, b) => a.difference(b).inMilliseconds.toDouble(), 27 | int: (a, b) => (a - b).toDouble(), 28 | double: (a, b) => (a - b).toDouble(), 29 | }; 30 | 31 | if (distanceFnMap.containsKey(type)) return distanceFnMap[type]; 32 | 33 | return (a, b) => a.compareTo(b).toDouble(); 34 | } 35 | 36 | /// A function that returns the measurement of a given instance of type [T]. 37 | /// 38 | /// For numbers, their measurement is simply their value. 39 | /// For dates, their measurement is milliseconds since the Unix Epoch. 40 | typedef MeasureFn = double Function(T); 41 | 42 | /// Returns a function that returns the measurement of a given type [T]. 43 | MeasureFn measureFnForType(Type type) { 44 | Map measureFnMap = { 45 | DateTime: (date) => date.millisecondsSinceEpoch.toDouble(), 46 | int: (val) => val.toDouble(), 47 | double: (val) => val, 48 | }; 49 | 50 | if (measureFnMap.containsKey(type)) return measureFnMap[type]; 51 | 52 | print('Asked to measure unknown type: $type, defaulting to cast to double'); 53 | return (val) => val as double; 54 | } 55 | 56 | /// A function that returns the minimum possible non-negative value of [T]. 57 | /// 58 | /// For example, for [num]s, this is 0. And for [DateTime]s, it is 0 59 | /// milliseconds past the Unix Epoch. 60 | typedef MinValueFn = T Function(); 61 | 62 | /// Returns a function that returns the minimum possible non-negative value of 63 | /// an instance of type [type]. 64 | MinValueFn minValueOfType(Type type) { 65 | Map valueFnMap = { 66 | DateTime: () => DateTime.fromMillisecondsSinceEpoch(0), 67 | int: () => 0, 68 | double: () => 0.0, 69 | }; 70 | 71 | if (valueFnMap.containsKey(type)) return valueFnMap[type]; 72 | 73 | print('Asked for minValueFn of unknown type: $type, defaulting to () => 0.0'); 74 | return () => 0.0; 75 | } 76 | 77 | Comparable interpolate(T t, 78 | {T other, double skew = 0.5}) { 79 | final double aUnits = measureFnForType(t.runtimeType)(t); 80 | final double bUnits = 81 | other == null ? 0 : measureFnForType(other.runtimeType)(other); 82 | 83 | final T smaller = aUnits < bUnits ? t : other; 84 | final units = (aUnits - bUnits).abs() * skew; 85 | 86 | if (t is DateTime) { 87 | return (smaller as DateTime).add(Duration(milliseconds: units.toInt())); 88 | } else if (t is num) { 89 | return (smaller as num) + units; 90 | } else { 91 | throw UnimplementedError('interpolation not implemented for type $T'); 92 | } 93 | } 94 | 95 | enum PlotType { 96 | line, 97 | bar, 98 | } 99 | 100 | /// A data set of type [T] with a domain of type [D] and a range of type [R]. 101 | /// 102 | /// [domainFn] and [rangeFn] should accept an instance of type [T], and return 103 | /// an instance of type [D] and [R], respectively. 104 | /// 105 | /// Example: 106 | /// Given a class representing the price of a stock on a specific date: 107 | /// 108 | /// ``` 109 | /// class StockPrice { 110 | /// DateTime date; 111 | /// double price; 112 | /// } 113 | /// ``` 114 | /// 115 | /// The type of data is [StockPrice], (which will be [T]). 116 | /// The domain type ([D]) is [DateTime], and the range type ([R]) is [double]. 117 | /// 118 | /// Defining the [domainFn] and [rangeFn] is trivial in this case: 119 | /// 120 | /// `domainFn: (StockPrice stockPrice) => stockPrice.date` 121 | /// `rangeFn: (StockPrice stockPrice) => stockPrice.price` 122 | class FlartData { 123 | final PlotType plotType; 124 | final List rawData; 125 | final int layer; 126 | 127 | final List computedDomain = []; 128 | final List computedRange = []; 129 | 130 | final DomainFn domainFn; 131 | final RangeFn rangeFn; 132 | 133 | final String domainAxisId; 134 | final String rangeAxisId; 135 | 136 | final SplayTreeMap> computedData; 137 | 138 | FlartAxis domainAxis; 139 | FlartAxis rangeAxis; 140 | 141 | DistanceFn domainDistanceFn; 142 | DistanceFn rangeDistanceFn; 143 | 144 | List normalizedDomain; 145 | List normalizedRange; 146 | 147 | double minRangeDistance = 0; 148 | double maxRangeDistance = 0; 149 | double maxDomainDistance = 0; 150 | 151 | D maxDomain; 152 | D minDomain; 153 | R maxRange; 154 | R minRange; 155 | 156 | static List _colors = [ 157 | Colors.blue, 158 | Colors.redAccent, 159 | Colors.green, 160 | ]; 161 | 162 | static int _nextColorIndex = 0; 163 | 164 | Color customColor; 165 | Color _color; 166 | 167 | Color get color { 168 | if (_color == null) { 169 | _color = customColor ?? _colors[_nextColorIndex++ % _colors.length]; 170 | } 171 | 172 | return _color; 173 | } 174 | 175 | // todo: lazily compute the data similar to Flutter's TextPainter.layout? 176 | FlartData( 177 | this.rawData, { 178 | this.layer = 1, 179 | this.plotType = PlotType.line, 180 | this.domainFn, 181 | this.rangeFn, 182 | this.domainAxisId, 183 | this.rangeAxisId, 184 | this.domainAxis, 185 | this.rangeAxis, 186 | this.customColor, 187 | }) : computedData = SplayTreeMap() { 188 | final firstDomain = domainFn(rawData.first, 0); 189 | final firstRange = rangeFn(rawData.first, 0); 190 | maxRange = minRange = firstRange; 191 | maxDomain = minDomain = firstDomain; 192 | computedDomain.add(firstDomain); 193 | computedRange.add(firstRange); 194 | 195 | domainDistanceFn = 196 | distanceFnForType(domainFn(rawData.first, 0).runtimeType); 197 | rangeDistanceFn = distanceFnForType(rangeFn(rawData.first, 0).runtimeType); 198 | 199 | for (var i = 1; i < rawData.length; i++) { 200 | final domain = domainFn(rawData[i], i); 201 | final range = rangeFn(rawData[i], i.toDouble()); 202 | computedDomain.add(domain); 203 | computedRange.add(range); 204 | 205 | final rangeDistance = rangeDistanceFn(range, minRange); 206 | final rangeDistanceToMin = rangeDistanceFn(range, minRange); 207 | final domainDistance = domainDistanceFn(domain, minDomain); 208 | 209 | if (rangeDistanceToMin < 0) { 210 | minRangeDistance = rangeDistance; 211 | maxRangeDistance = rangeDistanceFn(maxRange, range); 212 | minRange = range; 213 | } else if (rangeDistance > maxRangeDistance) { 214 | maxRangeDistance = rangeDistance; 215 | maxRange = range; 216 | } 217 | 218 | if (domainDistance < 0) { 219 | maxDomainDistance = domainDistanceFn(domain, maxDomain); 220 | minDomain = domain; 221 | } else if (domainDistance > maxDomainDistance) { 222 | maxDomainDistance = domainDistance; 223 | maxDomain = domain; 224 | } 225 | } 226 | 227 | normalizedDomain = normalizeComparable(computedDomain); 228 | normalizedRange = normalizeComparable(computedRange); 229 | 230 | for (var i = 0; i < rawData.length; i++) { 231 | computedData.putIfAbsent( 232 | computedDomain[i], 233 | () => ComputedFlartDatum(computedDomain[i], computedRange[i], 234 | normalizedDomain[i], normalizedRange[i]), 235 | ); 236 | } 237 | } 238 | 239 | static List normalizeComparable(List data) { 240 | final compareFn = distanceFnForType(data.first.runtimeType); 241 | final normalizedDistances = []; 242 | 243 | double lowestLow = 0; 244 | double highestHigh = 0; 245 | int indexOfLowest = 0; 246 | 247 | for (var i = 1; i < data.length; i++) { 248 | final distance = compareFn(data[i], data.first); 249 | 250 | if (distance < lowestLow) { 251 | lowestLow = distance; 252 | indexOfLowest = i; 253 | } 254 | 255 | if (distance > highestHigh) highestHigh = distance; 256 | } 257 | 258 | final range = (highestHigh - lowestLow); 259 | 260 | for (var i = 0; i < data.length; i++) { 261 | final distance = compareFn(data[i], data[indexOfLowest]); 262 | normalizedDistances.add(distance / range); 263 | } 264 | 265 | return normalizedDistances; 266 | } 267 | } 268 | 269 | class ComputedFlartDatum { 270 | final D domain; 271 | final R range; 272 | final double normalizedDomain; 273 | final double normalizedRange; 274 | 275 | ComputedFlartDatum( 276 | this.domain, 277 | this.range, 278 | this.normalizedDomain, 279 | this.normalizedRange, 280 | ); 281 | } 282 | 283 | FlartData, D, R> 284 | flartDataFromIterables( 285 | List domain, 286 | List range, { 287 | String domainAxisId, 288 | String rangeAxisId, 289 | FlartAxis domainAxis, 290 | FlartAxis rangeAxis, 291 | }) { 292 | final Map data = Map.fromIterables(domain, range); 293 | 294 | return FlartData, D, R>( 295 | data.entries.toList(), 296 | rangeFn: (e, i) => e.value, 297 | domainFn: (e, i) => e.key, 298 | domainAxis: domainAxis, 299 | rangeAxis: rangeAxis, 300 | domainAxisId: domainAxisId, 301 | rangeAxisId: rangeAxisId, 302 | ); 303 | } 304 | -------------------------------------------------------------------------------- /lib/flart_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flarts/flart_axis.dart'; 4 | import 'package:flarts/flart_data.dart'; 5 | import 'package:flarts/flart_theme.dart'; 6 | 7 | class FlartPainter extends CustomPainter { 8 | final List dataList; 9 | final List axes; 10 | final FlartStyle style; 11 | 12 | Size chartSize; 13 | Offset chartTopLeft; 14 | Offset chartBottomRight; 15 | 16 | Map textPainterCache = {}; 17 | 18 | FlartPainter({this.dataList, this.axes, FlartStyle style}) 19 | : this.style = style ?? FlartStyle(); 20 | 21 | @override 22 | void paint(Canvas canvas, Size size) { 23 | canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); 24 | 25 | _drawBackground(canvas, size); 26 | 27 | final labels = _calculateLabelAreas(axes, style.labelPadding); 28 | chartSize = Size( 29 | size.width - labels.horizontalArea, size.height - labels.verticalArea); 30 | chartTopLeft = Offset(labels.leftArea, labels.topArea); 31 | chartBottomRight = 32 | Offset(size.width - labels.rightArea, size.height - labels.bottomArea); 33 | 34 | _drawGridlines(canvas, axes); 35 | _drawData(canvas); 36 | _drawFlartBorder(canvas); 37 | _drawLabels(canvas, axes); 38 | } 39 | 40 | void _drawBackground(Canvas canvas, Size size) { 41 | canvas.drawRect( 42 | Rect.fromLTWH(0, 0, size.width, size.height), style.backgroundPaint); 43 | } 44 | 45 | void _drawGridlines(Canvas canvas, List axes) { 46 | Set horizontalLines = Set(); 47 | Set verticalLines = Set(); 48 | 49 | axes.forEach((axis) { 50 | if (axis.direction == Axis.horizontal) { 51 | axis.gridlines.forEach((gridline) { 52 | horizontalLines 53 | .add(_axisToScreenX(axis, gridline.normalizedDistanceAlongAxis)); 54 | }); 55 | } else { 56 | axis.gridlines.forEach((gridline) { 57 | verticalLines 58 | .add(_axisToScreenY(axis, gridline.normalizedDistanceAlongAxis)); 59 | }); 60 | } 61 | }); 62 | 63 | horizontalLines.forEach((x) => canvas.drawLine( 64 | Offset(x, chartTopLeft.dy), 65 | Offset(x, chartBottomRight.dy), 66 | style.gridlinePaint, 67 | )); 68 | 69 | verticalLines.forEach((y) => canvas.drawLine( 70 | Offset(chartTopLeft.dx, y), 71 | Offset(chartBottomRight.dx, y), 72 | style.gridlinePaint, 73 | )); 74 | } 75 | 76 | /// Draws the labels of each of the given [axes]. 77 | void _drawLabels(Canvas canvas, List axes) { 78 | final visited = []; 79 | 80 | void drawLabels(List labels, Function xFn, Function yFn) { 81 | labels.forEach((label) { 82 | final painter = _painterForText(label.text); 83 | final x = xFn(label, painter.width, painter.height); 84 | final y = yFn(label, painter.width, painter.height); 85 | 86 | if (visited.contains(Offset(x, y))) return; 87 | 88 | painter.paint(canvas, Offset(x, y)); 89 | }); 90 | } 91 | 92 | // The only thing that changes when labelling each side is determining the 93 | // x and y of the labels. We can just specify those functions to use the 94 | // method for drawing all labels. 95 | axes.forEach((axis) { 96 | Function xFn; 97 | Function yFn; 98 | 99 | if (axis.side == Side.left) { 100 | xFn = (_, width, __) => chartTopLeft.dx - style.labelPadding - width; 101 | yFn = (label, _, height) => 102 | _axisToScreenY(axis, label.normalizedDistanceAlongAxis) - 103 | height / 2; 104 | } else if (axis.side == Side.right) { 105 | xFn = (_, width, __) => chartBottomRight.dx + style.labelPadding; 106 | yFn = (label, _, height) => 107 | _axisToScreenY(axis, label.normalizedDistanceAlongAxis) - 108 | height / 2; 109 | } else if (axis.side == Side.top) { 110 | xFn = (label, width, _) => 111 | _axisToScreenX(axis, label.normalizedDistanceAlongAxis) - width / 2; 112 | yFn = 113 | (label, _, height) => chartTopLeft.dy - style.labelPadding - height; 114 | } else { 115 | xFn = (label, width, _) => 116 | _axisToScreenX(axis, label.normalizedDistanceAlongAxis) - width / 2; 117 | yFn = (label, _, __) => chartBottomRight.dy + style.labelPadding; 118 | } 119 | 120 | drawLabels(axis.labels, xFn, yFn); 121 | }); 122 | } 123 | 124 | /// Paints a border around the chart area. 125 | void _drawFlartBorder(Canvas canvas) { 126 | final halfStroke = style.borderStyle.strokeWidth / 2; 127 | 128 | final path = Path() 129 | ..moveTo(chartTopLeft.dx + halfStroke, chartTopLeft.dy + halfStroke) 130 | ..lineTo(chartTopLeft.dx + halfStroke, chartBottomRight.dy - halfStroke) 131 | ..lineTo(chartBottomRight.dx, chartBottomRight.dy - halfStroke) 132 | ..lineTo(chartBottomRight.dx, chartTopLeft.dy + halfStroke) 133 | ..lineTo(chartTopLeft.dx + halfStroke, chartTopLeft.dy + halfStroke); 134 | 135 | canvas.drawPath(path, style.borderStyle); 136 | } 137 | 138 | /// Draws all data by sending each set to the appropriate drawing method 139 | /// for that data's [PlotType]. 140 | void _drawData(Canvas canvas) { 141 | final Map> layerMap = {}; 142 | dataList.forEach((data) { 143 | if (layerMap.containsKey(data.layer)) 144 | layerMap[data.layer].add(data); 145 | else 146 | layerMap[data.layer] = [data]; 147 | }); 148 | 149 | final sortedKeys = layerMap.keys.toList()..sort(); 150 | 151 | for (final key in sortedKeys) { 152 | final layer = layerMap[key]; 153 | 154 | for (final data in layer) { 155 | switch (data.plotType) { 156 | case PlotType.line: 157 | _drawDataAsLine(canvas, data); 158 | break; 159 | case PlotType.bar: 160 | _drawDataAsBar(canvas, data); 161 | break; 162 | } 163 | } 164 | } 165 | } 166 | 167 | void _drawDataAsBar(Canvas canvas, FlartData data) { 168 | // todo: see about other/better ways to handle thin bars. 169 | var barWidth = chartSize.width / data.maxDomainDistance; 170 | barWidth = barWidth < 1.0 ? 1.0 : barWidth; 171 | final rangeDistanceFn = distanceFnForType(data.minRange.runtimeType); 172 | final domainDistanceFn = distanceFnForType(data.minDomain.runtimeType); 173 | 174 | final rects = []; 175 | 176 | final dataKeys = data.computedData.keys.toList(); 177 | 178 | for (var i = 0; i < dataKeys.length; i++) { 179 | final datum = data.computedData[dataKeys[i]]; 180 | final rangeDistToMin = 181 | rangeDistanceFn(datum.range, data.rangeAxis.minValue); 182 | final domainDistToMin = 183 | domainDistanceFn(datum.domain, data.domainAxis.minValue); 184 | final normAxisDomain = domainDistToMin / data.domainAxis.range; 185 | final normAxisRange = rangeDistToMin / data.rangeAxis.range; 186 | final x = _axisToScreenX(data.domainAxis, normAxisDomain); 187 | final y = _axisToScreenY(data.rangeAxis, normAxisRange); 188 | 189 | rects.add(Rect.fromLTRB(x, y, x + barWidth, chartBottomRight.dy)); 190 | } 191 | 192 | var paint = style.barPaint; 193 | 194 | if (data.color != null) { 195 | paint = Paint() 196 | ..color = data.color 197 | ..style = style.barPaint.style 198 | ..strokeWidth = style.barPaint.strokeWidth 199 | ..strokeJoin = style.barPaint.strokeJoin; 200 | } 201 | 202 | for (final rect in rects) { 203 | canvas.drawRect(rect, paint); 204 | } 205 | } 206 | 207 | void _drawDataAsLine(Canvas canvas, FlartData data) { 208 | final points = []; 209 | final rangeDistanceFn = distanceFnForType(data.minRange.runtimeType); 210 | final domainDistanceFn = distanceFnForType(data.minDomain.runtimeType); 211 | 212 | for (final datumKey in data.computedData.keys) { 213 | final datum = data.computedData[datumKey]; 214 | 215 | // todo: double check this late-night logic. is all this necessary? 216 | final rangeDistToMin = 217 | rangeDistanceFn(datum.range, data.rangeAxis.minValue); 218 | final domainDistToMin = 219 | domainDistanceFn(datum.domain, data.domainAxis.minValue); 220 | final normAxisDomain = domainDistToMin / data.domainAxis.range; 221 | final normAxisRange = rangeDistToMin / data.rangeAxis.range; 222 | 223 | final x = _axisToScreenX(data.domainAxis, normAxisDomain); 224 | final y = _axisToScreenY(data.rangeAxis, normAxisRange); 225 | 226 | if (data.domainAxis.direction == Axis.horizontal) { 227 | points.add(Offset(x, y)); 228 | } else { 229 | final axisX = 230 | chartBottomRight.dx - _normToAxis(data.rangeAxis, normAxisRange); 231 | final axisY = 232 | chartBottomRight.dy - _normToAxis(data.domainAxis, normAxisDomain); 233 | 234 | points.add(Offset(axisX, axisY)); 235 | } 236 | } 237 | 238 | _drawLine(canvas, points, data.color); 239 | } 240 | 241 | /// Draws the given [points] to the canvas in one path with the given [color]. 242 | void _drawLine(Canvas canvas, List points, Color color) { 243 | final path = Path()..moveTo(points.first.dx, points.first.dy); 244 | 245 | for (var i = 1; i < points.length; i++) { 246 | path.lineTo(points[i].dx, points[i].dy); 247 | } 248 | 249 | var paint = style.linePaint; 250 | 251 | if (color != null) { 252 | paint = Paint() 253 | ..color = color 254 | ..style = style.linePaint.style 255 | ..strokeWidth = style.linePaint.strokeWidth 256 | ..strokeJoin = style.linePaint.strokeJoin; 257 | } 258 | 259 | canvas.drawPath(path, paint); 260 | } 261 | 262 | double _normToAxis(FlartAxis axis, double norm) => 263 | axis.direction == Axis.horizontal 264 | ? norm * chartSize.width 265 | : norm * chartSize.height; 266 | 267 | double _axisToScreenX(FlartAxis axis, double normalizedX) => 268 | chartTopLeft.dx + normalizedX * chartSize.width; 269 | 270 | double _axisToScreenY(FlartAxis axis, double normalizedY) => 271 | chartBottomRight.dy - normalizedY * chartSize.height; 272 | 273 | /// Finds the required minimum size for the labels on each side of the chart. 274 | LabelAreaInfo _calculateLabelAreas( 275 | List axes, double labelPadding) { 276 | final Function max = (a, b) => a > b ? a : b; 277 | final areaInfo = LabelAreaInfo(padding: labelPadding); 278 | 279 | axes.forEach((axis) { 280 | axis.labels.forEach((label) { 281 | final painter = _painterForText(label.text, textStyle: label.style); 282 | 283 | if (axis.side == Side.top) { 284 | areaInfo.maxTop = max(painter.height, areaInfo.maxTop); 285 | } else if (axis.side == Side.left) { 286 | areaInfo.maxLeft = max(painter.width, areaInfo.maxLeft); 287 | } else if (axis.side == Side.right) { 288 | areaInfo.maxRight = max(painter.width, areaInfo.maxRight); 289 | } else { 290 | areaInfo.maxBottom = max(painter.height, areaInfo.maxBottom); 291 | } 292 | }); 293 | }); 294 | 295 | return areaInfo; 296 | } 297 | 298 | /// Returns a [TextPainter] for the provided [text]. 299 | /// 300 | /// Also calls [layout()] on the painter before returning so that it's already 301 | /// done and the callers don't have to worry about that responsibility. 302 | TextPainter _painterForText(String text, {TextStyle textStyle}) { 303 | if (textPainterCache.containsKey(text)) return textPainterCache[text]; 304 | 305 | final painter = TextPainter( 306 | text: TextSpan(text: text, style: textStyle ?? style.labelTextStyle), 307 | textAlign: TextAlign.left, 308 | textDirection: TextDirection.ltr); 309 | painter.layout(); 310 | 311 | textPainterCache[text] = painter; 312 | return painter; 313 | } 314 | 315 | @override 316 | bool shouldRepaint(CustomPainter oldDelegate) => false; 317 | } 318 | 319 | /// Holds information about the size of the label areas around the chart. 320 | class LabelAreaInfo { 321 | double maxTop; 322 | double maxLeft; 323 | double maxRight; 324 | double maxBottom; 325 | double padding; 326 | 327 | double padOrZero(n) => n == 0 ? 0 : n + 2 * padding; 328 | 329 | double get topArea => padOrZero(maxTop); 330 | double get leftArea => padOrZero(maxLeft); 331 | double get rightArea => padOrZero(maxRight); 332 | double get bottomArea => padOrZero(maxBottom); 333 | 334 | double get horizontalArea => leftArea + rightArea; 335 | double get verticalArea => topArea + bottomArea; 336 | 337 | LabelAreaInfo({ 338 | this.padding = 0, 339 | this.maxTop = 0, 340 | this.maxLeft = 0, 341 | this.maxRight = 0, 342 | this.maxBottom = 0, 343 | }); 344 | 345 | @override 346 | String toString() => 'left: $maxLeft, right: $maxRight ' 347 | 'top: $maxTop, bottom: $maxBottom'; 348 | } 349 | -------------------------------------------------------------------------------- /lib/flart_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Paint flartBackgroundPaint() => Paint() 4 | ..color = Colors.black26 5 | ..style = PaintingStyle.fill; 6 | 7 | Paint flartGridLinePaint() => Paint() 8 | ..color = Colors.white30 9 | ..style = PaintingStyle.fill 10 | ..strokeWidth = 1; 11 | 12 | Paint flartLabelPaint() => Paint() 13 | ..color = Colors.white70 14 | ..style = PaintingStyle.fill; 15 | 16 | Paint flartLabelBorderPaint() => Paint() 17 | ..color = Colors.greenAccent 18 | ..style = PaintingStyle.stroke 19 | ..strokeWidth = 1; 20 | 21 | Paint lineStylePaint() => Paint() 22 | ..style = PaintingStyle.stroke 23 | ..color = Colors.blue 24 | ..strokeWidth = 2 25 | ..strokeJoin = StrokeJoin.bevel; 26 | 27 | Paint barPaint() => Paint() 28 | ..style = PaintingStyle.fill 29 | ..color = Colors.grey 30 | ..strokeJoin = StrokeJoin.bevel; 31 | 32 | TextStyle labelTextStyle() => TextStyle(fontSize: 14.0); 33 | 34 | // todo: write a copyWith() to make life easier in other places. 35 | // todo: add themes. 36 | class FlartStyle { 37 | final Paint backgroundPaint; 38 | final Paint borderStyle; 39 | final Paint gridlinePaint; 40 | 41 | final double labelPadding; 42 | final TextStyle labelTextStyle; 43 | 44 | final Paint linePaint; 45 | final Paint barPaint; 46 | 47 | FlartStyle({ 48 | Paint backgroundPaint, 49 | Paint borderStyle, 50 | Paint gridlinePaint, 51 | Paint labelPaint, 52 | Paint barPaint, 53 | Paint linePaint, 54 | TextStyle labelTextStyle, 55 | double labelPadding, 56 | }) : this.backgroundPaint = backgroundPaint ?? 57 | (Paint() 58 | ..color = Colors.black26 59 | ..style = PaintingStyle.fill), 60 | this.borderStyle = borderStyle ?? 61 | (Paint() 62 | ..color = Colors.greenAccent 63 | ..style = PaintingStyle.stroke 64 | ..strokeWidth = 1 65 | ..strokeJoin = StrokeJoin.bevel), 66 | this.gridlinePaint = gridlinePaint ?? 67 | (Paint() 68 | ..color = Colors.white30 69 | ..style = PaintingStyle.fill 70 | ..strokeWidth = 1), 71 | this.linePaint = linePaint ?? 72 | (Paint() 73 | ..style = PaintingStyle.stroke 74 | ..color = Colors.blue 75 | ..strokeWidth = 2 76 | ..strokeJoin = StrokeJoin.bevel), 77 | this.barPaint = linePaint ?? 78 | (Paint() 79 | ..style = PaintingStyle.fill 80 | ..color = Colors.grey 81 | ..strokeJoin = StrokeJoin.bevel), 82 | this.labelTextStyle = labelTextStyle ?? TextStyle(fontSize: 14.0), 83 | this.labelPadding = labelPadding ?? 4.0; 84 | } 85 | -------------------------------------------------------------------------------- /lib/label_formatter.dart: -------------------------------------------------------------------------------- 1 | 2 | class LabelFormatter { 3 | 4 | static const Map shorthandMonthMap = { 5 | DateTime.january: 'Jan', 6 | DateTime.february: 'Feb', 7 | DateTime.march: 'Mar', 8 | DateTime.april: 'Apr', 9 | DateTime.may: 'May', 10 | DateTime.june: 'June', 11 | DateTime.july: 'July', 12 | DateTime.august: 'Aug', 13 | DateTime.september: 'Sep', 14 | DateTime.october: 'Oct', 15 | DateTime.november: 'Nov', 16 | DateTime.december: 'Dec', 17 | }; 18 | 19 | static Function labelToStringForType(Type type) { 20 | switch (type) { 21 | case DateTime: 22 | return formatDate; 23 | case int: 24 | case double: 25 | return _formatNumber; 26 | default: 27 | return (any) => any.toString(); 28 | } 29 | } 30 | 31 | static String formatDate(DateTime date) { 32 | return '${shorthandMonthMap[date.month]} \'${_twoDigitYear(date.year)}'; 33 | } 34 | 35 | static String _twoDigitYear(int year) { 36 | final fullYear = '$year'; 37 | return fullYear.substring(fullYear.length - 2); 38 | } 39 | 40 | static String _formatLargeNumber(num n) { 41 | final tenThousand = 10000; 42 | final oneMillion = 1000000; 43 | final oneBillion = 1000000000; 44 | 45 | if (n > oneBillion) { 46 | final billions = n / oneBillion; 47 | return '${_formatNumber(billions)}b'; 48 | } 49 | 50 | if (n > oneMillion) { 51 | final millions = n / oneMillion; 52 | return '${_formatNumber(millions)}m'; 53 | } 54 | 55 | if (n > tenThousand) { 56 | final thousands = n / 1000; 57 | return '${_formatNumber(thousands)}k'; 58 | } 59 | 60 | return '$n'; 61 | } 62 | 63 | static String _formatNumber(num n) { 64 | if (n > 100000) return _formatLargeNumber(n); 65 | if (n is int) return '$n.00'; 66 | 67 | final toString = '$n'; 68 | final dotIndex = toString.indexOf('.'); 69 | 70 | final lastIndex = 71 | dotIndex + 3 >= toString.length ? toString.length : dotIndex + 3; 72 | 73 | if (dotIndex == toString.length - 1) { 74 | return '${toString}00'; 75 | } else if (dotIndex == toString.length - 2) { 76 | return '${toString}0'; 77 | } else { 78 | return toString.substring(0, lastIndex); 79 | } 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.2.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.0.4" 18 | charcode: 19 | dependency: transitive 20 | description: 21 | name: charcode 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.2" 25 | collection: 26 | dependency: transitive 27 | description: 28 | name: collection 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.14.11" 32 | cupertino_icons: 33 | dependency: "direct main" 34 | description: 35 | name: cupertino_icons 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "0.1.2" 39 | flutter: 40 | dependency: "direct main" 41 | description: flutter 42 | source: sdk 43 | version: "0.0.0" 44 | flutter_test: 45 | dependency: "direct dev" 46 | description: flutter 47 | source: sdk 48 | version: "0.0.0" 49 | matcher: 50 | dependency: transitive 51 | description: 52 | name: matcher 53 | url: "https://pub.dartlang.org" 54 | source: hosted 55 | version: "0.12.5" 56 | meta: 57 | dependency: transitive 58 | description: 59 | name: meta 60 | url: "https://pub.dartlang.org" 61 | source: hosted 62 | version: "1.1.6" 63 | path: 64 | dependency: transitive 65 | description: 66 | name: path 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "1.6.2" 70 | pedantic: 71 | dependency: transitive 72 | description: 73 | name: pedantic 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "1.5.0" 77 | quiver: 78 | dependency: transitive 79 | description: 80 | name: quiver 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "2.0.3" 84 | sky_engine: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.99" 89 | source_span: 90 | dependency: transitive 91 | description: 92 | name: source_span 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "1.5.5" 96 | stack_trace: 97 | dependency: transitive 98 | description: 99 | name: stack_trace 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.9.3" 103 | stream_channel: 104 | dependency: transitive 105 | description: 106 | name: stream_channel 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "2.0.0" 110 | string_scanner: 111 | dependency: transitive 112 | description: 113 | name: string_scanner 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.0.4" 117 | term_glyph: 118 | dependency: transitive 119 | description: 120 | name: term_glyph 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.1.0" 124 | test_api: 125 | dependency: transitive 126 | description: 127 | name: test_api 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.2.5" 131 | typed_data: 132 | dependency: transitive 133 | description: 134 | name: typed_data 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.1.6" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "2.0.8" 145 | sdks: 146 | dart: ">=2.2.0 <3.0.0" 147 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flarts 2 | description: Flutter Charts 3 | 4 | author: Matthew Cliatt 5 | homepage: https://github.com/mtcliatt/flarts 6 | repository: https://github.com/mtcliatt/flarts 7 | 8 | version: 0.1.0 9 | 10 | environment: 11 | sdk: ">=2.1.0 <3.0.0" 12 | 13 | dependencies: 14 | flutter: 15 | sdk: flutter 16 | 17 | cupertino_icons: ^0.1.2 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | 23 | flutter: 24 | 25 | uses-material-design: true 26 | --------------------------------------------------------------------------------