├── docs
├── styles
│ ├── logo-styles.css
│ └── jetbrains-mono.css
├── images
│ ├── logo-icon.svg
│ ├── copy-icon.svg
│ ├── footer-go-to-link.svg
│ ├── arrow_down.svg
│ ├── copy-successful-icon.svg
│ ├── go-to-top-icon.svg
│ ├── anchor-copy-button.svg
│ └── docs_logo.svg
├── scripts
│ ├── sourceset_dependencies.js
│ ├── clipboard.js
│ └── navigation-loader.js
└── -kotlin multiplatform charts
│ ├── com.netguru.multiplatform.charts.line
│ ├── -symbol-shape
│ │ ├── -l-i-n-e
│ │ │ ├── name.html
│ │ │ └── ordinal.html
│ │ └── -r-e-c-t-a-n-g-l-e
│ │ │ ├── name.html
│ │ │ └── ordinal.html
│ └── -point-f
│ │ ├── x.html
│ │ └── y.html
│ └── com.netguru.multiplatform.charts.bubble
│ └── -bubble
│ └── update.html
├── assets
├── bar-chart.png
├── pie-chart.png
├── bubble-chart.png
├── dial-chart.png
├── gas-bottle.png
├── line-chart.png
└── readme_netguru_logo.png
├── charts
├── src
│ ├── androidMain
│ │ └── AndroidManifest.xml
│ └── commonMain
│ │ └── kotlin
│ │ └── com
│ │ └── netguru
│ │ └── multiplatform
│ │ └── charts
│ │ ├── grid
│ │ ├── axisscale
│ │ │ ├── XAxisScale.kt
│ │ │ ├── FixedTicksXAxisScale.kt
│ │ │ ├── TimestampXAxisScale.kt
│ │ │ └── YAxisScale.kt
│ │ ├── LineParameters.kt
│ │ ├── GridChartData.kt
│ │ ├── ChartGrid.kt
│ │ ├── GridDefaults.kt
│ │ ├── ChartAxis.kt
│ │ └── GridChartDrawing.kt
│ │ ├── bar
│ │ ├── BarChartBar.kt
│ │ ├── BarChartDefaults.kt
│ │ ├── BarChartEntry.kt
│ │ ├── BarChartCategory.kt
│ │ ├── BarChartColors.kt
│ │ ├── BarChartConfig.kt
│ │ ├── BarChartData.kt
│ │ ├── BarChartWithLegend.kt
│ │ └── BarChartDrawing.kt
│ │ ├── theme
│ │ ├── ChartColors.kt
│ │ ├── Theme.kt
│ │ └── ChartDefaults.kt
│ │ ├── gasbottle
│ │ └── GasBottleColors.kt
│ │ ├── line
│ │ ├── LineChartColors.kt
│ │ ├── LineChartWithLegend.kt
│ │ ├── LineChartSeries.kt
│ │ └── Line.kt
│ │ ├── dial
│ │ ├── DialColors.kt
│ │ ├── DialDefaults.kt
│ │ ├── DialConfig.kt
│ │ └── PercentageDial.kt
│ │ ├── ChartAnimation.kt
│ │ ├── bubble
│ │ ├── Bubble.kt
│ │ ├── Vector.kt
│ │ └── BubbleDefaults.kt
│ │ ├── pie
│ │ ├── PieDefaults.kt
│ │ ├── PieChartConfig.kt
│ │ └── PieChartWithLegend.kt
│ │ ├── Helpers.kt
│ │ └── OverlayInformation.kt
└── build.gradle.kts
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── example-app
├── common
│ ├── src
│ │ ├── androidMain
│ │ │ ├── AndroidManifest.xml
│ │ │ └── kotlin
│ │ │ │ └── com
│ │ │ │ └── netguru
│ │ │ │ └── multiplatform
│ │ │ │ └── charts
│ │ │ │ └── common
│ │ │ │ ├── locale
│ │ │ │ └── setApplicationLocale.kt
│ │ │ │ └── Resources.kt
│ │ ├── commonMain
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── netguru
│ │ │ │ │ └── multiplatform
│ │ │ │ │ └── charts
│ │ │ │ │ └── common
│ │ │ │ │ ├── TimeUtil.kt
│ │ │ │ │ ├── locale
│ │ │ │ │ ├── setApplicationLocale.kt
│ │ │ │ │ └── LocaleProvider.kt
│ │ │ │ │ ├── ButtonUtil.kt
│ │ │ │ │ ├── Resources.kt
│ │ │ │ │ ├── WindowSize.kt
│ │ │ │ │ └── Divider.kt
│ │ │ └── resources
│ │ │ │ ├── font
│ │ │ │ ├── aeonik_bold.otf
│ │ │ │ ├── aeonik_medium.otf
│ │ │ │ └── aeonik_regular.otf
│ │ │ │ ├── drawable
│ │ │ │ ├── netguru_logo_dark.png
│ │ │ │ └── netguru_logo_light.png
│ │ │ │ └── values
│ │ │ │ └── strings_en.xml
│ │ └── desktopMain
│ │ │ └── kotlin
│ │ │ └── com
│ │ │ └── netguru
│ │ │ └── multiplatform
│ │ │ └── charts
│ │ │ └── common
│ │ │ ├── locale
│ │ │ └── setApplicationLocale.kt
│ │ │ └── Resources.kt
│ └── build.gradle.kts
├── application
│ ├── src
│ │ ├── commonMain
│ │ │ └── kotlin
│ │ │ │ └── com
│ │ │ │ └── netguru
│ │ │ │ └── multiplatform
│ │ │ │ └── charts
│ │ │ │ └── application
│ │ │ │ ├── AppAction.kt
│ │ │ │ ├── navigation
│ │ │ │ ├── NavigationAction.kt
│ │ │ │ ├── NavigationState.kt
│ │ │ │ └── NavigationStore.kt
│ │ │ │ ├── home
│ │ │ │ └── HomeMapper.kt
│ │ │ │ ├── ActionDispatcher.kt
│ │ │ │ ├── Application.kt
│ │ │ │ ├── UiWrappers.kt
│ │ │ │ ├── Store.kt
│ │ │ │ └── screen
│ │ │ │ ├── BubbleChartScreen.kt
│ │ │ │ ├── DialChartScreen.kt
│ │ │ │ └── LineChartScreen.kt
│ │ └── androidMain
│ │ │ └── AndroidManifest.xml
│ └── build.gradle.kts
├── android
│ ├── screenshots
│ │ └── debug
│ │ │ ├── Nexus_10_API_31
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieTest_threeCategories.png
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieTest_twoCategories.png
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieWithLegendTest_threeCategories.png
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieWithLegendTest_twoCategories.png
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieTest_twoCategories_withZeroValue.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_0_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_0_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_100_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_150_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_50_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_fiveBars_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_negativeValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_negativeValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_100_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_150_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_50_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_69_UI_no_labels.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_69_UI_no_labels.png
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieWithLegendTest_threeCategories_customUI.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_threeCategories_oneBar_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_negativeValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_minus100_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieWithLegendTest_twoCategories_withZeroValue.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_minus50_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_negativeValues_customUI.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_positiveValues_customUI.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_fiveBars_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_oneBar_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_positiveAndNegativeValues.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_69_UI_custom_colors_and_labels.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_69_UI_custom_colors_and_labels.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_oneBar_positiveValues_customUI.png
│ │ │ └── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_positiveAndNegativeValues_customUI.png
│ │ │ └── Pixel_4a_API_31
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieTest_threeCategories.png
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieTest_twoCategories.png
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieWithLegendTest_threeCategories.png
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieWithLegendTest_twoCategories.png
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieTest_twoCategories_withZeroValue.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_0_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_0_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_100_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_150_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_50_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_fiveBars_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_negativeValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_negativeValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_100_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_150_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_50_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_69_UI_no_labels.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_69_UI_no_labels.png
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieWithLegendTest_threeCategories_customUI.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_threeCategories_oneBar_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_negativeValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_minus100_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.pie.PieWithLegendTest_twoCategories_withZeroValue.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_minus50_UI_default.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_negativeValues_customUI.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_positiveValues_customUI.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_fiveBars_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_oneBar_positiveValues.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_positiveAndNegativeValues.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_69_UI_custom_colors_and_labels.png
│ │ │ ├── com.netguru.multiplatform.charts.dial.PercentageDialTest_value_69_UI_custom_colors_and_labels.png
│ │ │ ├── com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_oneBar_positiveValues_customUI.png
│ │ │ └── com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_positiveAndNegativeValues_customUI.png
│ ├── src
│ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── netguru
│ │ │ │ └── multiplatform
│ │ │ │ └── charts
│ │ │ │ └── android
│ │ │ │ ├── previews
│ │ │ │ ├── HomePreviews.kt
│ │ │ │ ├── LineChartPreview.kt
│ │ │ │ └── BarChartPreview.kt
│ │ │ │ └── MainActivity.kt
│ │ └── androidTest
│ │ │ └── kotlin
│ │ │ └── com
│ │ │ └── netguru
│ │ │ └── multiplatform
│ │ │ └── charts
│ │ │ ├── Util.kt
│ │ │ ├── line
│ │ │ ├── Data.kt
│ │ │ ├── LineWithLegendTest.kt
│ │ │ └── LineTest.kt
│ │ │ ├── bar
│ │ │ ├── Data.kt
│ │ │ └── BarWithLegendTest.kt
│ │ │ ├── pie
│ │ │ ├── PieTest.kt
│ │ │ └── PieWithLegendTest.kt
│ │ │ ├── gasbottle
│ │ │ └── GasBottleTest.kt
│ │ │ └── dial
│ │ │ ├── PercentageDialTest.kt
│ │ │ └── DialTest.kt
│ └── build.gradle.kts
└── desktop
│ ├── src
│ └── jvmMain
│ │ └── kotlin
│ │ └── Main.kt
│ └── build.gradle.kts
├── SECURITY.md
├── .gitignore
├── gradle.properties
├── pull_request_template.md
├── settings.gradle.kts
├── .github
└── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── LICENSE.md
├── gradlew.bat
└── CONTRIBUTING.md
/docs/styles/logo-styles.css:
--------------------------------------------------------------------------------
1 | #logo {
2 | background-image: url(../images/docs_logo.svg);
3 | }
--------------------------------------------------------------------------------
/assets/bar-chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/assets/bar-chart.png
--------------------------------------------------------------------------------
/assets/pie-chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/assets/pie-chart.png
--------------------------------------------------------------------------------
/assets/bubble-chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/assets/bubble-chart.png
--------------------------------------------------------------------------------
/assets/dial-chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/assets/dial-chart.png
--------------------------------------------------------------------------------
/assets/gas-bottle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/assets/gas-bottle.png
--------------------------------------------------------------------------------
/assets/line-chart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/assets/line-chart.png
--------------------------------------------------------------------------------
/charts/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/assets/readme_netguru_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/assets/readme_netguru_logo.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/example-app/common/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/kotlin/com/netguru/multiplatform/charts/common/TimeUtil.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.common
2 |
3 | const val HOUR_IN_MS = 3600000
4 |
--------------------------------------------------------------------------------
/example-app/application/src/commonMain/kotlin/com/netguru/multiplatform/charts/application/AppAction.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.application
2 |
3 | interface AppAction
4 |
--------------------------------------------------------------------------------
/example-app/application/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/resources/font/aeonik_bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/common/src/commonMain/resources/font/aeonik_bold.otf
--------------------------------------------------------------------------------
/docs/images/logo-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/resources/font/aeonik_medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/common/src/commonMain/resources/font/aeonik_medium.otf
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/resources/font/aeonik_regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/common/src/commonMain/resources/font/aeonik_regular.otf
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/resources/drawable/netguru_logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/common/src/commonMain/resources/drawable/netguru_logo_dark.png
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/resources/drawable/netguru_logo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/common/src/commonMain/resources/drawable/netguru_logo_light.png
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Reporting Security Vulnerabilities
2 |
3 | If you find a security issue with the application, please reach out
4 | to [compose.multiplatform.charts@netguru.com](mailto:compose.multiplatform.charts@netguru.com)
5 |
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/kotlin/com/netguru/multiplatform/charts/common/locale/setApplicationLocale.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.common.locale
2 |
3 | internal expect fun setApplicationLocale(localeTag: String)
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | charts/build/
3 | example-app/android/build/
4 | example-app/application/build/
5 | example-app/common/build/
6 | example-app/desktop/build/
7 | modules.xml
8 | local.properties
9 | .gradle
10 | .idea/
11 | .DS_Store
--------------------------------------------------------------------------------
/docs/images/copy-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/images/footer-go-to-link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/grid/axisscale/XAxisScale.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.grid.axisscale
2 |
3 | interface XAxisScale {
4 | val tick: Long
5 | val min: Long
6 | val max: Long
7 | val start: Long
8 | }
9 |
--------------------------------------------------------------------------------
/docs/images/arrow_down.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/images/copy-successful-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/bar/BarChartBar.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bar
2 |
3 | data class BarChartBar(
4 | val width: ClosedFloatingPointRange,
5 | val height: ClosedFloatingPointRange,
6 | val data: BarChartEntry
7 | )
8 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/grid/LineParameters.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.grid
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class LineParameters(
7 | val position: Float,
8 | val value: Number,
9 | )
10 |
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieTest_threeCategories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieTest_threeCategories.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieTest_twoCategories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieTest_twoCategories.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieTest_threeCategories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieTest_threeCategories.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieTest_twoCategories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieTest_twoCategories.png
--------------------------------------------------------------------------------
/docs/images/go-to-top-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_threeCategories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_threeCategories.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_twoCategories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_twoCategories.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_threeCategories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_threeCategories.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_twoCategories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_twoCategories.png
--------------------------------------------------------------------------------
/example-app/common/src/desktopMain/kotlin/com/netguru/multiplatform/charts/common/locale/setApplicationLocale.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.common.locale
2 |
3 | import java.util.Locale
4 |
5 | internal actual fun setApplicationLocale(localeTag: String) {
6 | Locale.setDefault(Locale(localeTag))
7 | }
8 |
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieTest_twoCategories_withZeroValue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieTest_twoCategories_withZeroValue.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieTest_twoCategories_withZeroValue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieTest_twoCategories_withZeroValue.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_0_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_0_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_0_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_0_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_0_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_0_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_100_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_100_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_150_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_150_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_50_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_50_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_0_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_0_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_100_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_100_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_150_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_150_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_50_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_50_UI_default.png
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/resources/values/strings_en.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | KMP Charts examples
4 | Navigation panel logo
5 | Navigation menu
6 |
7 |
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_fiveBars_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_fiveBars_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_negativeValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_negativeValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_negativeValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_negativeValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_100_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_100_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_150_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_150_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_50_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_50_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_69_UI_no_labels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_69_UI_no_labels.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_69_UI_no_labels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_69_UI_no_labels.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_threeCategories_customUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_threeCategories_customUI.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_fiveBars_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_fiveBars_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_negativeValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_negativeValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_negativeValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_negativeValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_100_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_100_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_150_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_150_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_50_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_50_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_69_UI_no_labels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_69_UI_no_labels.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_69_UI_no_labels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_69_UI_no_labels.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_threeCategories_customUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_threeCategories_customUI.png
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/bar/BarChartDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bar
2 |
3 | import androidx.compose.ui.unit.dp
4 |
5 | internal object BarChartDefaults {
6 | val BAR_THICKNESS = 14.dp
7 | val BAR_CORNER_RADIUS = 7.dp
8 | val BAR_HORIZONTAL_SPACING = 4.dp
9 | }
10 |
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_threeCategories_oneBar_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_threeCategories_oneBar_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_negativeValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_negativeValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_minus100_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_minus100_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_twoCategories_withZeroValue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_twoCategories_withZeroValue.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_threeCategories_oneBar_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_threeCategories_oneBar_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_negativeValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_negativeValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_minus100_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_minus100_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_twoCategories_withZeroValue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.pie.PieWithLegendTest_twoCategories_withZeroValue.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_minus50_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_minus50_UI_default.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_minus50_UI_default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_minus50_UI_default.png
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | android.useAndroidX=true
3 | org.gradle.jvmargs=-Xmx3072m -XX:+UseParallelGC
4 | org.gradle.caching=true
5 | org.gradle.configureondemand=true
6 | org.gradle.parallel=true
7 | android.defaults.buildfeatures.buildconfig=true
8 | android.nonTransitiveRClass=false
9 | android.nonFinalResIds=false
10 |
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_negativeValues_customUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_negativeValues_customUI.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_positiveValues_customUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_positiveValues_customUI.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_negativeValues_customUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_negativeValues_customUI.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_positiveValues_customUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_oneBar_positiveValues_customUI.png
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/bar/BarChartEntry.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bar
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.graphics.Color
5 |
6 | @Immutable
7 | data class BarChartEntry(
8 | val x: String,
9 | val y: Float,
10 | val color: Color
11 | )
12 |
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_fiveBars_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_fiveBars_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_oneBar_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_oneBar_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_fiveBars_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_fiveBars_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_oneBar_positiveValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_oneBar_positiveValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_positiveAndNegativeValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_positiveAndNegativeValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_positiveAndNegativeValues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_twoCategories_twoBars_positiveAndNegativeValues.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_69_UI_custom_colors_and_labels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_69_UI_custom_colors_and_labels.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_69_UI_custom_colors_and_labels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_69_UI_custom_colors_and_labels.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_69_UI_custom_colors_and_labels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.DialTest_range_0_100_value_69_UI_custom_colors_and_labels.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_69_UI_custom_colors_and_labels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.dial.PercentageDialTest_value_69_UI_custom_colors_and_labels.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_oneBar_positiveValues_customUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_oneBar_positiveValues_customUI.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_oneBar_positiveValues_customUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarWithLegendTest_oneCategory_oneBar_positiveValues_customUI.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_positiveAndNegativeValues_customUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Nexus_10_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_positiveAndNegativeValues_customUI.png
--------------------------------------------------------------------------------
/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_positiveAndNegativeValues_customUI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/netguru/compose-multiplatform-charts/HEAD/example-app/android/screenshots/debug/Pixel_4a_API_31/com.netguru.multiplatform.charts.bar.BarTest_oneCategory_twoBars_positiveAndNegativeValues_customUI.png
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/grid/GridChartData.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.grid
2 |
3 | import com.netguru.multiplatform.charts.line.LegendItemData
4 |
5 | interface GridChartData {
6 | val minX: Long
7 | val maxX: Long
8 | val minY: Float
9 | val maxY: Float
10 | val legendData: List
11 | }
12 |
--------------------------------------------------------------------------------
/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ### Task
2 |
3 | - [JIRA](https://netguru.atlassian.net/browse/BN-)
4 |
5 | ### Description
6 |
7 |
8 | ### Additional Notes (optional)
9 |
10 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/grid/axisscale/FixedTicksXAxisScale.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.grid.axisscale
2 |
3 | class FixedTicksXAxisScale(
4 | override val min: Long,
5 | override val max: Long,
6 | tickCount: Int
7 | ) : XAxisScale {
8 | override val tick: Long = (max - min) / tickCount
9 | override val start: Long = min
10 | }
11 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/bar/BarChartCategory.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bar
2 |
3 | import androidx.compose.runtime.Immutable
4 |
5 | @Immutable
6 | data class BarChartCategory(
7 | val name: String,
8 | val entries: List
9 | ) {
10 | val minY: Float
11 | get() = entries.minOf { it.y }
12 | val maxY: Float
13 | get() = entries.maxOf { it.y }
14 | }
15 |
--------------------------------------------------------------------------------
/example-app/application/src/commonMain/kotlin/com/netguru/multiplatform/charts/application/navigation/NavigationAction.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.application.navigation
2 |
3 | import com.netguru.multiplatform.charts.application.AppAction
4 |
5 | sealed class NavigationAction : AppAction {
6 |
7 | object ToggleDrawer : NavigationAction()
8 |
9 | data class OpenTab(val tab: NavigationState.Tab) : NavigationAction()
10 | }
11 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | gradlePluginPortal()
5 | mavenCentral()
6 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
7 | }
8 | }
9 | rootProject.name = "compose-multiplatform-charts"
10 |
11 | include(":charts")
12 | include(":example-app:application")
13 | include(":example-app:common")
14 | include(":example-app:android")
15 | include(":example-app:desktop")
16 |
--------------------------------------------------------------------------------
/example-app/application/src/commonMain/kotlin/com/netguru/multiplatform/charts/application/navigation/NavigationState.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.application.navigation
2 |
3 | data class NavigationState(
4 | val isDrawerOpened: Boolean = false,
5 | val currentTab: Tab = Tab.BAR
6 | ) {
7 | enum class Tab {
8 | BAR,
9 | BUBBLE,
10 | DIAL,
11 | GAS_BOTTLE,
12 | LINE,
13 | PIE,
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/theme/ChartColors.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.theme
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.graphics.Color
5 |
6 | @Immutable
7 | data class ChartColors constructor(
8 | val primary: Color,
9 | val surface: Color,
10 | val grid: Color,
11 | val emptyGasBottle: Color,
12 | val fullGasBottle: Color,
13 | val overlayLine: Color,
14 | )
15 |
--------------------------------------------------------------------------------
/docs/scripts/sourceset_dependencies.js:
--------------------------------------------------------------------------------
1 | sourceset_dependencies='{":charts:dokkaHtml/androidAndroidTestRelease":[],":charts:dokkaHtml/androidDebug":[],":charts:dokkaHtml/androidMain":[":charts:dokkaHtml/commonMain"],":charts:dokkaHtml/androidRelease":[],":charts:dokkaHtml/androidTestFixtures":[],":charts:dokkaHtml/androidTestFixturesDebug":[],":charts:dokkaHtml/androidTestFixturesRelease":[],":charts:dokkaHtml/commonMain":[],":charts:dokkaHtml/desktopMain":[":charts:dokkaHtml/commonMain"]}'
2 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/grid/ChartGrid.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.grid
2 |
3 | import androidx.compose.runtime.Immutable
4 | import kotlin.math.abs
5 |
6 | @Immutable
7 | data class ChartGrid(
8 | val verticalLines: List,
9 | val horizontalLines: List,
10 | val zeroPosition: LineParameters,
11 | ) {
12 | val distanceBetweenVerticalLines: Float
13 | get() = abs(verticalLines[1].position - verticalLines[0].position)
14 | }
15 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/bar/BarChartColors.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bar
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.graphics.Color
5 | import com.netguru.multiplatform.charts.theme.ChartColors
6 |
7 | @Immutable
8 | data class BarChartColors(
9 | val grid: Color,
10 | val surface: Color,
11 | )
12 |
13 | val ChartColors.barChartColors
14 | get() = BarChartColors(
15 | grid = grid,
16 | surface = surface,
17 | )
18 |
--------------------------------------------------------------------------------
/example-app/common/src/androidMain/kotlin/com/netguru/multiplatform/charts/common/locale/setApplicationLocale.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.common.locale
2 |
3 | import android.content.res.Resources
4 | import java.util.Locale
5 |
6 | internal actual fun setApplicationLocale(localeTag: String) {
7 | val resources = Resources.getSystem()
8 | val configuration = resources.configuration
9 |
10 | val locale = Locale(localeTag)
11 | configuration.setLocale(locale)
12 | resources.updateConfiguration(configuration, resources.displayMetrics)
13 | }
14 |
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/kotlin/com/netguru/multiplatform/charts/common/ButtonUtil.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.common
2 |
3 | import androidx.compose.material.ButtonDefaults
4 | import androidx.compose.material.ButtonElevation
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.unit.dp
7 |
8 | @Composable
9 | fun ButtonDefaults.noElevation(): ButtonElevation = elevation(
10 | defaultElevation = 0.dp,
11 | pressedElevation = 0.dp,
12 | disabledElevation = 0.dp,
13 | hoveredElevation = 0.dp,
14 | focusedElevation = 0.dp
15 | )
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? What motivated You to create this request?**
11 | A clear and concise description of what the problem is, e.g., I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like to see**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/gasbottle/GasBottleColors.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.gasbottle
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.graphics.Color
5 | import com.netguru.multiplatform.charts.theme.ChartColors
6 |
7 | @Immutable
8 | data class GasBottleColors(
9 | val fullGasBottle: Color,
10 | val emptyGasBottle: Color,
11 | )
12 |
13 | val ChartColors.gasBottleColors
14 | get() = GasBottleColors(
15 | fullGasBottle = fullGasBottle,
16 | emptyGasBottle = emptyGasBottle,
17 | )
18 |
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/kotlin/com/netguru/multiplatform/charts/common/Resources.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.common
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.graphics.painter.Painter
5 | import androidx.compose.ui.text.font.Font
6 | import androidx.compose.ui.text.font.FontStyle
7 | import androidx.compose.ui.text.font.FontWeight
8 |
9 | @Composable
10 | expect fun imageResources(image: String): Painter
11 |
12 | @Composable
13 | expect fun fontResources(
14 | font: String,
15 | weight: FontWeight,
16 | style: FontStyle
17 | ): Font
18 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/line/LineChartColors.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.line
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.graphics.Color
5 | import com.netguru.multiplatform.charts.theme.ChartColors
6 |
7 | @Immutable
8 | data class LineChartColors(
9 | val grid: Color,
10 | val surface: Color,
11 | val overlayLine: Color,
12 | )
13 |
14 | val ChartColors.lineChartColors
15 | get() = LineChartColors(
16 | grid = grid,
17 | surface = surface,
18 | overlayLine = overlayLine,
19 | )
20 |
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/kotlin/com/netguru/multiplatform/charts/common/WindowSize.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.common
2 |
3 | import androidx.compose.ui.unit.Dp
4 | import androidx.compose.ui.unit.dp
5 |
6 | enum class WindowSize {
7 | COMPACT,
8 | MEDIUM,
9 | EXPANDED;
10 |
11 | companion object {
12 | fun basedOnWidth(windowWidth: Dp): WindowSize {
13 | return when {
14 | windowWidth < 600.dp -> COMPACT
15 | windowWidth < 1000.dp -> MEDIUM
16 | else -> EXPANDED
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/dial/DialColors.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.dial
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.graphics.Color
5 | import com.netguru.multiplatform.charts.theme.ChartColors
6 |
7 | @Immutable
8 | data class DialColors(
9 | val progressBarColor: Color,
10 | val progressBarBackgroundColor: Color,
11 | val gridScaleColor: Color,
12 | )
13 |
14 | val ChartColors.dialColors
15 | get() = DialColors(
16 | progressBarColor = primary,
17 | progressBarBackgroundColor = grid,
18 | gridScaleColor = grid
19 | )
20 |
--------------------------------------------------------------------------------
/example-app/application/src/commonMain/kotlin/com/netguru/multiplatform/charts/application/home/HomeMapper.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.application.home
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.netguru.multiplatform.charts.application.navigation.NavigationState
5 |
6 | @Composable
7 | fun NavigationState.Tab.toLabel(): String = when (this) {
8 | NavigationState.Tab.BAR -> "Bar"
9 | NavigationState.Tab.BUBBLE -> "Bubble"
10 | NavigationState.Tab.DIAL -> "Dial"
11 | NavigationState.Tab.GAS_BOTTLE -> "Gas bottle"
12 | NavigationState.Tab.LINE -> "Line"
13 | NavigationState.Tab.PIE -> "Pie"
14 | }
15 |
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/kotlin/com/netguru/multiplatform/charts/common/Divider.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.common
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.height
5 | import androidx.compose.material.Divider
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 |
9 | @Composable
10 | fun HorizontalDivider(modifier: Modifier = Modifier) {
11 | Divider(
12 | modifier = modifier
13 | .height(AppTheme.dimens.borders_thickness)
14 | .fillMaxWidth(),
15 | color = AppTheme.colors.borders
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/example-app/application/src/commonMain/kotlin/com/netguru/multiplatform/charts/application/ActionDispatcher.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.application
2 |
3 | import com.netguru.multiplatform.charts.application.navigation.NavigationAction
4 | import com.netguru.multiplatform.charts.application.navigation.NavigationStore
5 |
6 | class ActionDispatcher {
7 |
8 | private val navigationStore = NavigationStore()
9 |
10 | val navigationState by lazy { navigationStore.state }
11 |
12 | fun dispatch(action: AppAction) {
13 | when (action) {
14 | is NavigationAction -> navigationStore.dispatch(action)
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/example-app/common/src/commonMain/kotlin/com/netguru/multiplatform/charts/common/locale/LocaleProvider.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.common.locale
2 |
3 | import androidx.compose.ui.text.intl.Locale
4 | import kotlinx.coroutines.flow.MutableStateFlow
5 | import kotlinx.coroutines.flow.StateFlow
6 | import kotlinx.coroutines.flow.update
7 |
8 | object LocaleProvider {
9 |
10 | val locale: StateFlow
11 | get() = _locale
12 |
13 | private val _locale = MutableStateFlow(Locale.current.language)
14 |
15 | fun setLocale(localeTag: String) {
16 | _locale.update { localeTag }
17 | setApplicationLocale(localeTag)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example-app/application/src/commonMain/kotlin/com/netguru/multiplatform/charts/application/Application.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.application
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.staticCompositionLocalOf
5 | import com.netguru.multiplatform.charts.application.home.HomeScreen
6 | import com.netguru.multiplatform.charts.common.AppTheme
7 | import com.netguru.multiplatform.charts.common.WindowSize
8 |
9 | val LocalActionDispatcher = staticCompositionLocalOf { ActionDispatcher() }
10 |
11 | @Composable
12 | fun Application(windowSize: WindowSize) {
13 | AppTheme(windowSize) {
14 | HomeScreen()
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: 'Report a bug '
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 |
19 | **Actual behaviour**
20 | A clear and concise description of what happened.
21 |
22 | **Expected behavior**
23 | A clear and concise description of what you expected to happen.
24 |
25 | **Environment:**
26 | Provide environment on which the issue exists, e.g., GooglePixel 5, Android 13
27 |
28 | **Additional context**
29 | Add any other context about the problem here.
30 |
--------------------------------------------------------------------------------
/example-app/common/src/desktopMain/kotlin/com/netguru/multiplatform/charts/common/Resources.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.common
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.graphics.painter.Painter
5 | import androidx.compose.ui.res.painterResource
6 | import androidx.compose.ui.text.font.Font
7 | import androidx.compose.ui.text.font.FontStyle
8 | import androidx.compose.ui.text.font.FontWeight
9 |
10 | @Composable
11 | actual fun imageResources(image: String): Painter = painterResource("drawable/$image")
12 |
13 | actual fun fontResources(
14 | font: String,
15 | weight: FontWeight,
16 | style: FontStyle
17 | ): Font = androidx.compose.ui.text.platform.Font("font/$font", weight, style)
18 |
--------------------------------------------------------------------------------
/docs/images/anchor-copy-button.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.theme
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.staticCompositionLocalOf
5 | import androidx.compose.ui.graphics.Color
6 |
7 | val LocalChartColors = staticCompositionLocalOf {
8 | ChartColors(
9 | primary = Color.Unspecified,
10 | surface = Color.Unspecified,
11 | grid = Color.Unspecified,
12 | emptyGasBottle = Color.Unspecified,
13 | fullGasBottle = Color.Unspecified,
14 | overlayLine = Color.Unspecified,
15 | )
16 | }
17 |
18 | internal object ChartTheme {
19 | val colors: ChartColors
20 | @Composable
21 | get() = LocalChartColors.current
22 | }
23 |
--------------------------------------------------------------------------------
/example-app/desktop/src/jvmMain/kotlin/Main.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.ui.unit.DpSize
2 | import androidx.compose.ui.unit.dp
3 | import androidx.compose.ui.window.Window
4 | import androidx.compose.ui.window.application
5 | import androidx.compose.ui.window.rememberWindowState
6 | import com.netguru.multiplatform.charts.application.Application
7 | import com.netguru.multiplatform.charts.common.AppTheme.strings
8 | import com.netguru.multiplatform.charts.common.WindowSize
9 |
10 | fun main() = application {
11 | val windowState = rememberWindowState(size = DpSize(1050.dp, 950.dp))
12 | Window(
13 | onCloseRequest = ::exitApplication,
14 | state = windowState,
15 | title = strings.app_name
16 | ) {
17 | Application(WindowSize.basedOnWidth(windowState.size.width))
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example-app/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/ChartAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts
2 |
3 | import androidx.compose.animation.core.AnimationSpec
4 | import androidx.compose.animation.core.tween
5 |
6 | sealed class ChartAnimation {
7 | object Disabled : ChartAnimation()
8 |
9 | class Simple(
10 | val animationSpec: () -> AnimationSpec = {
11 | tween(DEFAULT_DURATION, DEFAULT_DELAY)
12 | },
13 | ) : ChartAnimation()
14 |
15 | class Sequenced(
16 | val animationSpec: (dataSeriesIndex: Int) -> AnimationSpec = { index ->
17 | tween(DEFAULT_DURATION, index * DEFAULT_DELAY)
18 | },
19 | ) : ChartAnimation()
20 |
21 | private companion object {
22 | const val DEFAULT_DURATION = 300
23 | const val DEFAULT_DELAY = 100
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example-app/android/src/androidTest/kotlin/com/netguru/multiplatform/charts/Util.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.CompositionLocalProvider
5 | import androidx.compose.ui.test.junit4.ComposeContentTestRule
6 | import com.karumi.shot.ScreenshotTest
7 | import com.netguru.multiplatform.charts.theme.ChartDefaults
8 | import com.netguru.multiplatform.charts.theme.LocalChartColors
9 |
10 | object Util {
11 | fun ScreenshotTest.checkComposable(composeRule: ComposeContentTestRule, contentToCheck: @Composable () -> Unit) {
12 | composeRule.setContent {
13 | CompositionLocalProvider(LocalChartColors provides ChartDefaults.chartColors()) {
14 | contentToCheck()
15 | }
16 | }
17 |
18 | compareScreenshot(composeRule)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/grid/axisscale/TimestampXAxisScale.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.grid.axisscale
2 |
3 | class TimestampXAxisScale(
4 | override val min: Long,
5 | override val max: Long,
6 | val maxTicksCount: Int = 10
7 | ) : XAxisScale {
8 | // find first round hour, greater than min timestamp
9 | override val start: Long = min - min % HOUR_MS + HOUR_MS
10 |
11 | // find period of vertical lines based on maxTicksCount, period can be in round hours ie. 1h, 2h, 3h
12 | private val period: Long = HOUR_MS * ((max - min) / HOUR_MS / maxTicksCount)
13 |
14 | // if period is 0 or less set period to round hour
15 | // avoid division by 0 when app starts and min and max are 0
16 | override val tick: Long = if (period > 0) period else HOUR_MS
17 |
18 | companion object {
19 | const val HOUR_MS = 3600000L
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/bubble/Bubble.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bubble
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.vector.ImageVector
5 | import kotlin.random.Random
6 |
7 | data class Bubble(
8 | val name: String,
9 | val value: Float,
10 | val icon: ImageVector,
11 | val color: Color,
12 | var radius: Float = value
13 | ) {
14 | var position: Vector = Vector(0f, 0f)
15 | var velocity: Vector = Vector(
16 | Random.nextFloat() - 0.5f,
17 | Random.nextFloat() - 0.5f
18 | ).normalize()
19 | private var acceleration: Vector = Vector(0f, 0f)
20 |
21 | fun applyForce(force: Vector) {
22 | acceleration.add(force)
23 | }
24 |
25 | fun update() {
26 | velocity.add(acceleration)
27 | position.add(velocity)
28 | acceleration.mult(0f)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/example-app/application/src/commonMain/kotlin/com/netguru/multiplatform/charts/application/navigation/NavigationStore.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.application.navigation
2 |
3 | import com.netguru.multiplatform.charts.application.Store
4 | import kotlinx.coroutines.CoroutineDispatcher
5 | import kotlinx.coroutines.Dispatchers
6 |
7 | internal class NavigationStore(
8 | dispatcher: CoroutineDispatcher = Dispatchers.Default
9 | ) : Store(NavigationState(), dispatcher) {
10 |
11 | init {
12 | onAction { toggleDrawer() }
13 | onAction { openTab(it.tab) }
14 | }
15 |
16 | private fun toggleDrawer() {
17 | updateState { copy(isDrawerOpened = isDrawerOpened.not()) }
18 | }
19 |
20 | private fun openTab(tab: NavigationState.Tab) {
21 | updateState { copy(currentTab = tab, isDrawerOpened = false) }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example-app/application/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.netguru.multiplatform.charts.extensions.androidMain
2 | import com.netguru.multiplatform.charts.extensions.baseAndroidSetup
3 | import com.netguru.multiplatform.charts.extensions.baseTestSetup
4 | import com.netguru.multiplatform.charts.extensions.commonMain
5 |
6 | baseAndroidSetup()
7 | baseTestSetup()
8 |
9 | plugins {
10 | kotlin("multiplatform")
11 | id("com.android.library")
12 | @Suppress("DSL_SCOPE_VIOLATION")
13 | alias(libs.plugins.kotlin.compose)
14 | }
15 |
16 | kotlin {
17 | androidTarget()
18 | jvm("desktop")
19 |
20 | sourceSets {
21 | commonMain {
22 | dependencies {
23 | api(project(":example-app:common"))
24 | }
25 | }
26 | androidMain {
27 | dependencies {
28 | api(libs.compose.uiTooling)
29 | }
30 | }
31 | }
32 | }
33 | android {
34 | namespace = "com.netguru.multiplatform.charts.application"
35 | }
36 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/theme/ChartDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.theme
2 |
3 | import androidx.compose.material.MaterialTheme
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.graphics.Color
6 |
7 | /**
8 | * Contains the default values used by all charts
9 | */
10 | object ChartDefaults {
11 | @Composable
12 | fun chartColors(
13 | primary: Color = MaterialTheme.colors.primary,
14 | surface: Color = Color.Unspecified,
15 | grid: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.4f),
16 | emptyGasBottle: Color = MaterialTheme.colors.error,
17 | fullGasBottle: Color = MaterialTheme.colors.primary,
18 | overlayLine: Color = MaterialTheme.colors.error,
19 | ) = ChartColors(
20 | primary = primary,
21 | surface = surface,
22 | grid = grid,
23 | emptyGasBottle = emptyGasBottle,
24 | fullGasBottle = fullGasBottle,
25 | overlayLine = overlayLine,
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/docs/styles/jetbrains-mono.css:
--------------------------------------------------------------------------------
1 | @font-face{
2 | font-family: 'JetBrains Mono';
3 | src: url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/web/JetBrainsMono-Regular.eot') format('embedded-opentype'),
4 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Regular.woff2') format('woff2'),
5 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Regular.ttf') format('truetype');
6 | font-weight: normal;
7 | font-style: normal;
8 | }
9 |
10 | @font-face{
11 | font-family: 'JetBrains Mono';
12 | src: url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/web/JetBrainsMono-Bold.eot') format('embedded-opentype'),
13 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Bold.woff2') format('woff2'),
14 | url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Bold.ttf') format('truetype');
15 | font-weight: normal;
16 | font-style: normal;
17 | }
--------------------------------------------------------------------------------
/example-app/desktop/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.netguru.multiplatform.charts.extensions.jvmMain
2 | import org.jetbrains.compose.desktop.application.dsl.TargetFormat
3 |
4 | plugins {
5 | kotlin("multiplatform")
6 | @Suppress("DSL_SCOPE_VIOLATION")
7 | alias(libs.plugins.kotlin.compose)
8 | alias(libs.plugins.compose.desktop)
9 | }
10 |
11 | group = libs.versions.project.group.get()
12 | version = libs.versions.project.version.get()
13 |
14 | kotlin {
15 | jvm()
16 | sourceSets {
17 | jvmMain {
18 | dependencies {
19 | implementation(compose.desktop.currentOs)
20 | implementation(project(":example-app:application"))
21 | }
22 | }
23 | }
24 | }
25 |
26 | compose.desktop {
27 | application {
28 | mainClass = "MainKt"
29 | nativeDistributions {
30 | targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
31 | packageName = libs.versions.desktop.packageName.get()
32 | packageVersion = libs.versions.desktop.packageVersion.get()
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/bar/BarChartConfig.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bar
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.unit.Dp
5 | import com.netguru.multiplatform.charts.grid.GridDefaults
6 |
7 | /**
8 | * The customization parameters for [BarChart]
9 | *
10 | * @param thickness Width of a single bar
11 | * @param cornerRadius 0 for square bars, thickness/2 for fully rounded corners
12 | * @param barsSpacing The space between bars in a cluster
13 | * @param maxHorizontalLinesCount Max number of lines that are allowed to draw for marking y-axis
14 | * @param roundMinMaxClosestTo Number to which min and max range will be rounded to
15 | */
16 | @Immutable
17 | data class BarChartConfig(
18 | val thickness: Dp = BarChartDefaults.BAR_THICKNESS,
19 | val cornerRadius: Dp = BarChartDefaults.BAR_CORNER_RADIUS,
20 | val barsSpacing: Dp = BarChartDefaults.BAR_HORIZONTAL_SPACING,
21 | val maxHorizontalLinesCount: Int = GridDefaults.NUMBER_OF_GRID_LINES,
22 | val roundMinMaxClosestTo: Int = GridDefaults.ROUND_MIN_MAX_CLOSEST_TO,
23 | )
24 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Netguru
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/dial/DialDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.dial
2 |
3 | import androidx.compose.foundation.layout.padding
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.unit.dp
9 | import androidx.compose.ui.unit.sp
10 |
11 | internal object DialDefaults {
12 |
13 | val THICKNESS = 15.dp
14 | val SCALE_PADDING = 24.dp
15 | val SCALE_STROKE_WIDTH = 2.dp
16 | val SCALE_STROKE_LENGTH = 16.dp
17 | const val START_ANGLE = -180f
18 | val JOIN_STYLE = DialJoinStyle.WithDegreeGap(2f)
19 |
20 | val MainLabel: @Composable (value: Any) -> Unit = {
21 | Text(text = it.toString(), fontSize = 24.sp)
22 | }
23 |
24 | val MinAndMaxValueLabel: @Composable (value: Any) -> Unit = {
25 | Text(
26 | text = it.toString(),
27 | style = MaterialTheme.typography.body2,
28 | modifier = Modifier
29 | .padding(top = 16.dp)
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/example-app/common/src/androidMain/kotlin/com/netguru/multiplatform/charts/common/Resources.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.common
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.graphics.painter.Painter
5 | import androidx.compose.ui.platform.LocalContext
6 | import androidx.compose.ui.res.painterResource
7 | import androidx.compose.ui.text.font.Font
8 | import androidx.compose.ui.text.font.FontStyle
9 | import androidx.compose.ui.text.font.FontWeight
10 |
11 | @Composable
12 | actual fun imageResources(image: String): Painter {
13 | val context = LocalContext.current
14 | val name = image.substringBefore(".")
15 | val drawable = context.resources.getIdentifier(name, "drawable", context.packageName)
16 | return painterResource(drawable)
17 | }
18 |
19 | @Composable
20 | actual fun fontResources(
21 | font: String,
22 | weight: FontWeight,
23 | style: FontStyle
24 | ): Font {
25 | val context = LocalContext.current
26 | val name = font.substringBefore(".")
27 | val fontRes = context.resources.getIdentifier(name, "font", context.packageName)
28 | return Font(fontRes, weight, style)
29 | }
30 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/dial/DialConfig.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.dial
2 |
3 | import androidx.compose.ui.unit.Dp
4 |
5 | /**
6 | * The customization parameters for [Dial]
7 | *
8 | * @param thickness The width of arc
9 | * @param scalePadding Size of space between arc and scale
10 | * @param scaleLineWidth Thickness of scale lines
11 | * @param scaleLineLength The length of majors scale lines.
12 | * The maximum value is 20% width of the chart
13 | * @param joinStyle How the lib should draw space between major arc and minor arc [DialJoinStyle]
14 | */
15 | data class DialConfig(
16 | val thickness: Dp = DialDefaults.THICKNESS,
17 | val scalePadding: Dp = DialDefaults.SCALE_PADDING,
18 | val scaleLineWidth: Dp = DialDefaults.SCALE_STROKE_WIDTH,
19 | val scaleLineLength: Dp = DialDefaults.SCALE_STROKE_LENGTH,
20 | val joinStyle: DialJoinStyle = DialDefaults.JOIN_STYLE,
21 | val displayScale: Boolean = true,
22 | val roundCorners: Boolean = false,
23 | )
24 |
25 | sealed class DialJoinStyle {
26 | object Joined : DialJoinStyle()
27 | object Overlapped : DialJoinStyle()
28 | data class WithDegreeGap(val degrees: Float) : DialJoinStyle()
29 | }
30 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/dial/PercentageDial.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.dial
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import com.netguru.multiplatform.charts.ChartAnimation
6 | import com.netguru.multiplatform.charts.theme.ChartTheme
7 |
8 | /**
9 | * Variant of [Dial] with min value set to 0 and max value set to 100.
10 | *
11 | * @param percentage Value to show
12 | *
13 | * @see Dial
14 | */
15 | @Composable
16 | fun PercentageDial(
17 | percentage: Int,
18 | modifier: Modifier = Modifier,
19 | animation: ChartAnimation = ChartAnimation.Simple(),
20 | colors: DialColors = ChartTheme.colors.dialColors,
21 | config: DialConfig = DialConfig(),
22 | minAndMaxValueLabel: @Composable (value: Int) -> Unit = DialDefaults.MinAndMaxValueLabel,
23 | mainLabel: @Composable (value: Int) -> Unit = DialDefaults.MainLabel,
24 | ) {
25 | Dial(
26 | value = percentage,
27 | minValue = 0,
28 | maxValue = 100,
29 | modifier = modifier,
30 | animation = animation,
31 | colors = colors,
32 | config = config,
33 | minAndMaxValueLabel = minAndMaxValueLabel,
34 | mainLabel = mainLabel,
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/docs/images/docs_logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/pie/PieDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.pie
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.material.MaterialTheme
7 | import androidx.compose.material.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.unit.Dp
12 | import androidx.compose.ui.unit.dp
13 | import com.netguru.multiplatform.charts.round
14 |
15 | internal object PieDefaults {
16 | const val FULL_CIRCLE_DEGREES = 360f
17 | const val START_ANGLE = 270.0
18 | const val NUMBER_OF_COLS_IN_LEGEND = 4
19 | val THICKNESS = Dp.Infinity
20 | val GAP_SIZE = 0.dp
21 | val LEGEND_PADDING = 16.dp
22 | val LEGEND_ICON_SIZE = 12.dp
23 |
24 | val LegendItemLabel: @Composable (PieChartData) -> Unit = {
25 |
26 | Column(
27 | horizontalAlignment = Alignment.CenterHorizontally,
28 | ) {
29 | Spacer(modifier = Modifier.height(8.dp))
30 |
31 | Text(
32 | text = it.value.round(1)
33 | )
34 |
35 | Text(
36 | text = it.name,
37 | style = MaterialTheme.typography.body2,
38 | )
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/example-app/android/src/main/java/com/netguru/multiplatform/charts/android/previews/HomePreviews.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.android.previews
2 |
3 | import androidx.compose.material.icons.Icons
4 | import androidx.compose.material.icons.filled.Favorite
5 | import androidx.compose.material.icons.filled.Person
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
8 | import androidx.compose.ui.tooling.preview.Preview
9 | import com.netguru.multiplatform.charts.application.home.HomeDrawerButton
10 | import com.netguru.multiplatform.charts.common.AppTheme
11 | import com.netguru.multiplatform.charts.common.WindowSize
12 |
13 | @Composable
14 | @Preview(widthDp = 200)
15 | fun CurrentHomeDrawerButtonPreview() {
16 | AppTheme(WindowSize.EXPANDED) {
17 | HomeDrawerButton(
18 | isCurrent = true,
19 | onClick = {},
20 | iconPainter = rememberVectorPainter(Icons.Filled.Person),
21 | label = "User"
22 | )
23 | }
24 | }
25 |
26 | @Composable
27 | @Preview(widthDp = 200, showBackground = true)
28 | fun IdleHomeDrawerButtonPreview() {
29 | AppTheme(WindowSize.EXPANDED) {
30 | HomeDrawerButton(
31 | isCurrent = false,
32 | onClick = {},
33 | iconPainter = rememberVectorPainter(Icons.Filled.Favorite),
34 | label = "Favorite"
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/example-app/android/src/main/java/com/netguru/multiplatform/charts/android/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.android
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 | import androidx.activity.compose.setContent
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.remember
9 | import androidx.compose.ui.graphics.toComposeRect
10 | import androidx.compose.ui.platform.LocalConfiguration
11 | import androidx.compose.ui.platform.LocalDensity
12 | import androidx.window.layout.WindowMetricsCalculator
13 | import com.netguru.multiplatform.charts.application.Application
14 | import com.netguru.multiplatform.charts.common.WindowSize
15 |
16 | class MainActivity : AppCompatActivity() {
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | setContent {
21 | Application(rememberWindowSize())
22 | }
23 | }
24 | }
25 |
26 | @Composable
27 | private fun Activity.rememberWindowSize(): WindowSize {
28 | val configuration = LocalConfiguration.current
29 | val windowMetrics = remember(configuration) {
30 | WindowMetricsCalculator.getOrCreate()
31 | .computeCurrentWindowMetrics(this)
32 | }
33 | val windowDpSize = with(LocalDensity.current) {
34 | windowMetrics.bounds.toComposeRect().size.toDpSize()
35 | }
36 | return WindowSize.basedOnWidth(windowDpSize.width)
37 | }
38 |
--------------------------------------------------------------------------------
/example-app/android/src/androidTest/kotlin/com/netguru/multiplatform/charts/line/Data.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.line
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import com.soywiz.klock.DateTime
5 | import com.soywiz.klock.TimeSpan
6 |
7 | internal object Data {
8 |
9 | private val colors = listOf(
10 | Color.Blue,
11 | Color.Yellow,
12 | Color.Cyan,
13 | Color.Black,
14 | Color.DarkGray,
15 | Color.Green,
16 | Color.LightGray,
17 | Color.Magenta,
18 | Color.Red,
19 | )
20 |
21 | fun generateLineData(nOfLines: Int) : LineChartData {
22 | return LineChartData(
23 | series = (1..nOfLines).map {
24 | LineChartSeries(
25 | dataName = "data $it",
26 | lineColor = colors[it % colors.size],
27 | listOfPoints = (1..5).map { point ->
28 | val sign = if((point+it) % 2 == 0) -1 else 1
29 | val value = (it + point) * sign
30 | LineChartPoint(
31 | x = DateTime.fromString("2021-12-31")
32 | .minus(TimeSpan(point * 24 * 60 * 60 * 1000.0))
33 | .utc
34 | .unixMillisLong,
35 | y = value.toFloat(),
36 | )
37 | }
38 | )
39 | },
40 | )
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/example-app/android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.netguru.multiplatform.charts.extensions.baseAndroidSetup
2 | import kotlin.collections.listOf
3 |
4 | baseAndroidSetup()
5 | plugins {
6 | kotlin("android")
7 | @Suppress("DSL_SCOPE_VIOLATION")
8 | alias(libs.plugins.kotlin.compose)
9 | id("com.android.application")
10 | id("shot")
11 | }
12 |
13 | group = libs.versions.project.group.get()
14 | version = libs.versions.project.version.get()
15 |
16 | dependencies {
17 | implementation(libs.androidx.compose)
18 | debugImplementation(libs.compose.uiTooling)
19 | implementation(libs.compose.preview)
20 | implementation(libs.androidx.window)
21 | implementation(project(":example-app:application"))
22 | androidTestImplementation(libs.test.junit)
23 | androidTestImplementation(libs.test.ui.junit)
24 | debugImplementation(libs.test.ui.manifest)
25 | }
26 |
27 | android {
28 | buildTypes {
29 | getByName("release") {
30 | isMinifyEnabled = false
31 | }
32 | }
33 | defaultConfig {
34 | applicationId = libs.versions.applicationId.get()
35 | testInstrumentationRunner = "com.karumi.shot.ShotTestRunner"
36 | }
37 |
38 | packagingOptions {
39 | resources.excludes += listOf(
40 | "META-INF/AL2.0",
41 | "META-INF/LGPL2.1"
42 | )
43 | }
44 | namespace = "com.netguru.multiplatform.charts.android"
45 | }
46 |
47 | shot {
48 | tolerance = 2.0 // 2% tolerance, needed for testing on different devices
49 | }
50 |
--------------------------------------------------------------------------------
/example-app/android/src/androidTest/kotlin/com/netguru/multiplatform/charts/line/LineWithLegendTest.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.line
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.material.Text
5 | import androidx.compose.ui.Alignment
6 | import androidx.compose.ui.test.junit4.createComposeRule
7 | import com.karumi.shot.ScreenshotTest
8 | import com.netguru.multiplatform.charts.Util.checkComposable
9 | import org.junit.Rule
10 | import org.junit.Test
11 |
12 | class LineWithLegendTest : ScreenshotTest {
13 |
14 | @get:Rule
15 | val composeRule = createComposeRule()
16 |
17 | // @Test // removed due to: https://github.com/pedrovgs/Shot/issues/265
18 | // fun mixedValues_defaultUI() {
19 | // checkComposable(composeRule) {
20 | // LineChartWithLegend(
21 | // lineChartData = Data.generateLineData(3),
22 | // )
23 | // }
24 | // }
25 | //
26 | // @Test // removed due to: https://github.com/pedrovgs/Shot/issues/265
27 | // fun mixedValues_customUI() {
28 | // checkComposable(composeRule) {
29 | // LineChartWithLegend(
30 | // lineChartData = Data.generateLineData(3),
31 | // legendItemLabel = {
32 | // Column(
33 | // horizontalAlignment = Alignment.CenterHorizontally
34 | // ) {
35 | // Text(text = it)
36 | // Text(text = "line")
37 | // }
38 | // }
39 | // )
40 | // }
41 | // }
42 | }
43 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/bar/BarChartData.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bar
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.netguru.multiplatform.charts.grid.GridChartData
5 | import com.netguru.multiplatform.charts.line.LegendItemData
6 | import com.netguru.multiplatform.charts.line.SymbolShape
7 |
8 | @Immutable
9 | data class BarChartData(
10 | val categories: List,
11 | ) : GridChartData {
12 | // TODO hide those values from the user
13 | override val minX: Long = 0
14 | override val maxX: Long
15 | get() = if (categories.isEmpty()) {
16 | 1
17 | } else {
18 | categories.size * 2.toLong()
19 | }
20 |
21 | override val minY: Float
22 | get() = if (categories.isEmpty()) {
23 | 0f
24 | } else {
25 | categories.minOf { it.minY }.coerceAtMost(0f)
26 | }
27 |
28 | override val maxY: Float
29 | get() = if (categories.isEmpty()) {
30 | 1f
31 | } else {
32 | categories.maxOf { it.maxY }.coerceAtLeast(0f)
33 | }
34 |
35 | override val legendData: List
36 | get() {
37 | return categories
38 | .flatMap { it.entries }
39 | .distinctBy { it.x }
40 | .map {
41 | LegendItemData(
42 | name = it.x,
43 | symbolShape = SymbolShape.RECTANGLE,
44 | color = it.color,
45 | dashed = false,
46 | )
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/pie/PieChartConfig.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.pie
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.unit.Dp
5 |
6 | /**
7 | * The customization parameters for [PieChart]
8 | *
9 | * @param thickness Width of the arc
10 | * @param gap The margin between segments
11 | * @param legendPadding The space between chart and legend in both orientations
12 | * @param legendIcon The type of icon in legend [LegendIcon]
13 | * @param legendIconSize Size of the icon in legend
14 | * @param legendOrientation This parameters determinate the place where the legend will be drawn.
15 | * [LegendOrientation.HORIZONTAL] will draw all legend items under the chart in grid with number
16 | * of columns provided by [numberOfColsInLegend].
17 | * [LegendOrientation.VERTICAL] will draw all legend items on the right side in column.
18 | * @param numberOfColsInLegend Determinate number of legend items per row in
19 | * [LegendOrientation.HORIZONTAL] orientation.
20 | */
21 | @Immutable
22 | data class PieChartConfig(
23 | val thickness: Dp = PieDefaults.THICKNESS,
24 | val gap: Dp = PieDefaults.GAP_SIZE,
25 | val legendPadding: Dp = PieDefaults.LEGEND_PADDING,
26 | val legendIcon: LegendIcon = LegendIcon.CAKE,
27 | val legendIconSize: Dp = PieDefaults.LEGEND_ICON_SIZE,
28 | val legendOrientation: LegendOrientation = LegendOrientation.HORIZONTAL,
29 | val numberOfColsInLegend: Int = PieDefaults.NUMBER_OF_COLS_IN_LEGEND,
30 | )
31 |
32 | enum class LegendIcon {
33 | SQUARE,
34 | CIRCLE,
35 | ROUND,
36 | CAKE,
37 | }
38 |
39 | enum class LegendOrientation {
40 | HORIZONTAL,
41 | VERTICAL,
42 | }
43 |
--------------------------------------------------------------------------------
/example-app/application/src/commonMain/kotlin/com/netguru/multiplatform/charts/application/UiWrappers.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.application
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.ColumnScope
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.rememberScrollState
9 | import androidx.compose.foundation.verticalScroll
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.text.font.FontWeight
14 | import androidx.compose.ui.unit.sp
15 | import com.netguru.multiplatform.charts.common.AppTheme
16 |
17 | @Composable
18 | fun TitleText(
19 | text: String,
20 | modifier: Modifier = Modifier,
21 | ) {
22 | Text(
23 | text = text,
24 | color = AppTheme.colors.primaryText,
25 | fontSize = 40.sp,
26 | fontWeight = FontWeight.SemiBold,
27 | modifier = modifier,
28 | )
29 | }
30 |
31 | @Composable
32 | fun SpacedColumn(
33 | content: @Composable ColumnScope.() -> Unit,
34 | ) {
35 | Column(
36 | verticalArrangement = Arrangement.spacedBy(AppTheme.dimens.grid_4),
37 | content = content,
38 | )
39 | }
40 |
41 | @Composable
42 | fun ScrollableScreen(
43 | content: @Composable ColumnScope.() -> Unit,
44 | ) {
45 | Column(
46 | modifier = Modifier
47 | .fillMaxWidth()
48 | .verticalScroll(rememberScrollState())
49 | .padding(AppTheme.dimens.grid_4),
50 | content = content,
51 | )
52 | }
53 |
--------------------------------------------------------------------------------
/example-app/application/src/commonMain/kotlin/com/netguru/multiplatform/charts/application/Store.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.application
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.CoroutineScope
5 | import kotlinx.coroutines.ExperimentalCoroutinesApi
6 | import kotlinx.coroutines.SupervisorJob
7 | import kotlinx.coroutines.cancel
8 | import kotlinx.coroutines.flow.MutableSharedFlow
9 | import kotlinx.coroutines.flow.MutableStateFlow
10 | import kotlinx.coroutines.flow.StateFlow
11 | import kotlinx.coroutines.flow.filterIsInstance
12 | import kotlinx.coroutines.flow.launchIn
13 | import kotlinx.coroutines.flow.mapLatest
14 | import kotlinx.coroutines.flow.update
15 | import kotlinx.coroutines.launch
16 |
17 | @OptIn(ExperimentalCoroutinesApi::class)
18 | internal abstract class Store(
19 | initialState: State,
20 | dispatcher: CoroutineDispatcher,
21 | ) {
22 |
23 | val state: StateFlow
24 | get() = _state
25 |
26 | private val _state = MutableStateFlow(initialState)
27 | private val actions = MutableSharedFlow()
28 | private val storeScope = CoroutineScope(dispatcher + SupervisorJob())
29 |
30 | fun dispatch(action: Action) {
31 | storeScope.launch { actions.emit(action) }
32 | }
33 |
34 | open fun onCleared() {
35 | storeScope.cancel()
36 | }
37 |
38 | protected fun updateState(function: State.() -> State) = _state.update(function)
39 |
40 | protected inline fun onAction(
41 | crossinline function: suspend (SpecificAction) -> Unit,
42 | ) {
43 | actions
44 | .filterIsInstance()
45 | .mapLatest { function(it) }
46 | .launchIn(storeScope)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/charts/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.netguru.multiplatform.charts.extensions.baseAndroidSetup
2 | import com.netguru.multiplatform.charts.extensions.commonMain
3 | import com.netguru.multiplatform.charts.extensions.commonTest
4 | import com.netguru.multiplatform.charts.extensions.kotlin
5 | import com.netguru.multiplatform.charts.extensions.sourceSets
6 | import java.net.URL
7 |
8 | baseAndroidSetup()
9 |
10 | plugins {
11 | alias(libs.plugins.kotlin.compose)
12 | kotlin("multiplatform")
13 | id("com.android.library")
14 | alias(libs.plugins.dokka)
15 | }
16 |
17 | kotlin {
18 | androidTarget()
19 | jvm("desktop")
20 |
21 | sourceSets {
22 | commonMain {
23 | dependencies {
24 | api(libs.compose.runtime)
25 | api(libs.compose.ui)
26 | api(libs.compose.foundation)
27 | api(libs.compose.material)
28 | api(libs.compose.materialIconsExtended)
29 | }
30 | }
31 | commonTest {
32 | dependencies {
33 | implementation(libs.kotlin.test)
34 | }
35 | }
36 | }
37 | }
38 | android {
39 | namespace = "com.netguru.multiplatform.charts"
40 | }
41 |
42 | tasks.withType().configureEach {
43 | dokkaSourceSets {
44 | named("commonMain") {
45 | moduleName.set("Kotlin multiplatform charts")
46 | sourceLink {
47 | val dir = "src/commonMain/kotlin"
48 | localDirectory.set(file(dir))
49 | remoteUrl.set(
50 | URL(
51 | "https://github.com/netguru/compose-multiplatform-charts/tree/main/charts/$dir"
52 | )
53 | )
54 | remoteLineSuffix.set("#L")
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/bar/BarChartWithLegend.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bar
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.netguru.multiplatform.charts.ChartAnimation
7 | import com.netguru.multiplatform.charts.grid.GridDefaults
8 | import com.netguru.multiplatform.charts.line.ChartLegend
9 | import com.netguru.multiplatform.charts.theme.ChartTheme
10 |
11 | /**
12 | * This bar chart shows data organised in categories together with a legend.
13 | *
14 | * @param legendItemLabel Composable to show for each item in the legend. Square representing the
15 | * color of the item, drawn to the left of it, is not customizable.
16 | *
17 | * @see BarChart
18 | */
19 | @Composable
20 | fun BarChartWithLegend(
21 | data: BarChartData,
22 | modifier: Modifier = Modifier,
23 | animation: ChartAnimation = ChartAnimation.Simple(),
24 | colors: BarChartColors = ChartTheme.colors.barChartColors,
25 | config: BarChartConfig = BarChartConfig(),
26 | xAxisLabel: @Composable (value: Any) -> Unit = GridDefaults.XAxisLabel,
27 | yAxisLabel: @Composable (value: Any) -> Unit = GridDefaults.YAxisLabel,
28 | legendItemLabel: @Composable (String) -> Unit = GridDefaults.LegendItemLabel,
29 | ) {
30 | Column(modifier) {
31 | BarChart(
32 | modifier = Modifier.weight(1f),
33 | data = data,
34 | animation = animation,
35 | colors = colors,
36 | config = config,
37 | xAxisLabel = xAxisLabel,
38 | yAxisLabel = yAxisLabel,
39 | )
40 | ChartLegend(
41 | legendData = data.legendData,
42 | animation = animation,
43 | config = config,
44 | legendItemLabel = legendItemLabel,
45 | )
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/grid/GridDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.grid
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.text.style.TextAlign
9 | import androidx.compose.ui.unit.dp
10 | import androidx.compose.ui.unit.sp
11 |
12 | internal object GridDefaults {
13 |
14 | val HORIZONTAL_LINES_OFFSET = 10.dp
15 | const val NUMBER_OF_GRID_LINES = 5
16 | const val ROUND_MIN_MAX_CLOSEST_TO = 10
17 |
18 | val YAxisLabel: @Composable (value: Any) -> Unit = { value ->
19 | Text(
20 | modifier = Modifier
21 | .fillMaxWidth(),
22 | fontSize = 12.sp,
23 | text = value.toString(),
24 | textAlign = TextAlign.End,
25 | maxLines = 1
26 | )
27 | }
28 |
29 | val OverlayHeaderLabel: @Composable (value: Any) -> Unit = { value ->
30 | Text(
31 | text = value.toString(),
32 | modifier = Modifier.fillMaxWidth(),
33 | textAlign = TextAlign.Center,
34 | style = MaterialTheme.typography.overline
35 | )
36 | }
37 |
38 | val OverlayDataEntryLabel: @Composable (dataName: String, value: Any) -> Unit = { dataName, value ->
39 | Text(
40 | text = "$dataName: $value"
41 | )
42 | }
43 |
44 | val XAxisLabel: @Composable (value: Any) -> Unit = { value ->
45 | Text(
46 | fontSize = 12.sp,
47 | text = value.toString(),
48 | textAlign = TextAlign.Center
49 | )
50 | }
51 |
52 | val LegendItemLabel: @Composable (String) -> Unit = {
53 | Text(
54 | text = it,
55 | )
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/docs/scripts/clipboard.js:
--------------------------------------------------------------------------------
1 | window.addEventListener('load', () => {
2 | document.querySelectorAll('span.copy-icon').forEach(element => {
3 | element.addEventListener('click', (el) => copyElementsContentToClipboard(element));
4 | })
5 |
6 | document.querySelectorAll('span.anchor-icon').forEach(element => {
7 | element.addEventListener('click', (el) => {
8 | if(element.hasAttribute('pointing-to')){
9 | const location = hrefWithoutCurrentlyUsedAnchor() + '#' + element.getAttribute('pointing-to')
10 | copyTextToClipboard(element, location)
11 | }
12 | });
13 | })
14 | })
15 |
16 | const copyElementsContentToClipboard = (element) => {
17 | const selection = window.getSelection();
18 | const range = document.createRange();
19 | range.selectNodeContents(element.parentNode.parentNode);
20 | selection.removeAllRanges();
21 | selection.addRange(range);
22 |
23 | copyAndShowPopup(element, () => selection.removeAllRanges())
24 | }
25 |
26 | const copyTextToClipboard = (element, text) => {
27 | var textarea = document.createElement("textarea");
28 | textarea.textContent = text;
29 | textarea.style.position = "fixed";
30 | document.body.appendChild(textarea);
31 | textarea.select();
32 |
33 | copyAndShowPopup(element, () => document.body.removeChild(textarea))
34 | }
35 |
36 | const copyAndShowPopup = (element, after) => {
37 | try {
38 | document.execCommand('copy');
39 | element.nextElementSibling.classList.add('active-popup');
40 | setTimeout(() => {
41 | element.nextElementSibling.classList.remove('active-popup');
42 | }, 1200);
43 | } catch (e) {
44 | console.error('Failed to write to clipboard:', e)
45 | }
46 | finally {
47 | if(after) after()
48 | }
49 | }
50 |
51 | const hrefWithoutCurrentlyUsedAnchor = () => window.location.href.split('#')[0]
52 |
53 |
--------------------------------------------------------------------------------
/example-app/android/src/androidTest/kotlin/com/netguru/multiplatform/charts/bar/Data.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bar
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import kotlin.math.roundToInt
5 |
6 | internal object Data {
7 | internal enum class ValueTypes {
8 | POSITIVE,
9 | NEGATIVE,
10 | POSITIVE_AND_NEGATIVE,
11 | ZERO,
12 | }
13 |
14 | private val colors = listOf(
15 | Color.Blue,
16 | Color.Cyan,
17 | Color.Black,
18 | Color.DarkGray,
19 | Color.Green,
20 | Color.LightGray,
21 | Color.Magenta,
22 | Color.Red,
23 | Color.Yellow,
24 | )
25 |
26 | private fun generateEntries(n: Int, valueTypes: ValueTypes): List {
27 | val nonZeroValues = when (valueTypes) {
28 | ValueTypes.POSITIVE -> 1..n
29 | ValueTypes.NEGATIVE -> -n..-1
30 | ValueTypes.POSITIVE_AND_NEGATIVE -> ((-n / 2)..-1) + (1..(n/2.0).roundToInt())
31 | ValueTypes.ZERO -> List(n) { 0 }
32 | }
33 | return nonZeroValues.map {
34 | BarChartEntry(
35 | x = "entry_$it",
36 | y = it * 10f,
37 | color = colors.elementAt(it.mod(colors.size))
38 | )
39 | }
40 | }
41 |
42 | private fun generateCategories(
43 | nOfCategories: Int,
44 | nOfEntries: Int,
45 | valueTypes: ValueTypes
46 | ): List {
47 | return (1..nOfCategories).map {
48 | BarChartCategory(
49 | name = "category_$it",
50 | entries = generateEntries(nOfEntries, valueTypes),
51 | )
52 | }
53 | }
54 |
55 | fun generateData(nOfCategories: Int, nOfEntries: Int, valueTypes: ValueTypes): BarChartData {
56 | return BarChartData(
57 | categories = generateCategories(nOfCategories, nOfEntries, valueTypes)
58 | )
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/example-app/android/src/androidTest/kotlin/com/netguru/multiplatform/charts/pie/PieTest.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.pie
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import androidx.compose.ui.unit.dp
6 | import com.karumi.shot.ScreenshotTest
7 | import com.netguru.multiplatform.charts.Util.checkComposable
8 | import org.junit.Rule
9 | import org.junit.Test
10 |
11 | class PieTest : ScreenshotTest {
12 |
13 | @get:Rule
14 | val composeRule = createComposeRule()
15 |
16 | @Test
17 | fun twoCategories() {
18 | checkComposable(composeRule) {
19 | PieChart(
20 | data = listOf(
21 | PieChartData("first", 22.0, Color.Blue),
22 | PieChartData("second", 22.0, Color.Yellow)
23 | ),
24 | config = PieChartConfig(
25 | thickness = 80.dp
26 | )
27 | )
28 | }
29 | }
30 |
31 | @Test
32 | fun threeCategories() {
33 | checkComposable(composeRule) {
34 | PieChart(
35 | data = listOf(
36 | PieChartData("first", 22.0, Color.Blue),
37 | PieChartData("second", 22.0, Color.Yellow),
38 | PieChartData("third", 22.0, Color.Green)
39 | ),
40 | config = PieChartConfig(
41 | thickness = 80.dp
42 | )
43 | )
44 | }
45 | }
46 |
47 | @Test
48 | fun twoCategories_withZeroValue() {
49 | checkComposable(composeRule) {
50 | PieChart(
51 | data = listOf(
52 | PieChartData("first", 22.0, Color.Blue),
53 | PieChartData("second", 0.0, Color.Yellow)
54 | ),
55 | config = PieChartConfig(
56 | thickness = 80.dp
57 | )
58 | )
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/example-app/application/src/commonMain/kotlin/com/netguru/multiplatform/charts/application/screen/BubbleChartScreen.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.application.screen
2 |
3 | import androidx.compose.foundation.layout.size
4 | import androidx.compose.material.icons.Icons
5 | import androidx.compose.material.icons.filled.Album
6 | import androidx.compose.material.icons.filled.Bed
7 | import androidx.compose.material.icons.filled.House
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.unit.dp
11 | import com.netguru.multiplatform.charts.ChartAnimation
12 | import com.netguru.multiplatform.charts.application.ScrollableScreen
13 | import com.netguru.multiplatform.charts.application.SpacedColumn
14 | import com.netguru.multiplatform.charts.application.TitleText
15 | import com.netguru.multiplatform.charts.bubble.Bubble
16 | import com.netguru.multiplatform.charts.bubble.BubbleChart
17 | import com.netguru.multiplatform.charts.common.AppTheme
18 |
19 | @Composable
20 | fun BubbleChartScreen() {
21 | val bubbles = listOf(
22 | Bubble(
23 | name = "first",
24 | value = 1.2f,
25 | icon = Icons.Default.Album,
26 | color = AppTheme.colors.yellow
27 | ),
28 | Bubble(
29 | name = "second",
30 | value = 4.6f,
31 | icon = Icons.Default.House,
32 | color = AppTheme.colors.green
33 | ),
34 | Bubble(
35 | name = "third",
36 | value = 6.9f,
37 | icon = Icons.Default.Bed,
38 | color = AppTheme.colors.blue
39 | ),
40 | )
41 |
42 | ScrollableScreen {
43 | SpacedColumn {
44 | TitleText(text = "Bubble chart")
45 | BubbleChart(
46 | bubbles = bubbles,
47 | modifier = Modifier
48 | .size(300.dp),
49 | animation = ChartAnimation.Sequenced(),
50 | )
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/Helpers.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.LaunchedEffect
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.runtime.setValue
9 | import kotlin.math.pow
10 | import kotlin.math.roundToInt
11 |
12 | internal fun Double.mapValueToDifferentRange(
13 | inMin: Double,
14 | inMax: Double,
15 | outMin: Double,
16 | outMax: Double,
17 | ) = (this - inMin) * (outMax - outMin) / (inMax - inMin) + outMin
18 |
19 | fun Float.mapValueToDifferentRange(
20 | inMin: Float,
21 | inMax: Float,
22 | outMin: Float,
23 | outMax: Float,
24 | ) = (this - inMin) * (outMax - outMin) / (inMax - inMin) + outMin
25 |
26 | fun Long.mapValueToDifferentRange(
27 | inMin: Long,
28 | inMax: Long,
29 | outMin: Long,
30 | outMax: Long,
31 | ) = (this - inMin) * (outMax - outMin) / (inMax - inMin) + outMin
32 |
33 | fun Long.mapValueToDifferentRange(
34 | inMin: Long,
35 | inMax: Long,
36 | outMin: Float,
37 | outMax: Float,
38 | ) = (this - inMin) * (outMax - outMin) / (inMax - inMin) + outMin
39 |
40 | fun Number.round(decimals: Int = 2): String {
41 | return when (this) {
42 | is Double,
43 | is Float,
44 | -> try {
45 | ((this.toDouble() * 10.0.pow(decimals)).roundToInt() / 10.0.pow(decimals)).toString()
46 | } catch (e: IllegalArgumentException) {
47 | "-"
48 | }
49 | else -> {
50 | this.toString()
51 | }
52 | }
53 | }
54 |
55 | @Composable
56 | internal fun StartAnimation(animation: ChartAnimation, data: Any): Boolean {
57 | var animationPlayed by remember(data) {
58 | mutableStateOf(animation is ChartAnimation.Disabled)
59 | }
60 | LaunchedEffect(Unit) {
61 | animationPlayed = true
62 | }
63 |
64 | return animationPlayed
65 | }
66 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/grid/ChartAxis.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.grid
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.IntrinsicSize
5 | import androidx.compose.foundation.layout.fillMaxHeight
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.layout.width
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.layout.layout
11 | import androidx.compose.ui.unit.dp
12 | import kotlin.math.roundToInt
13 |
14 | @Composable
15 | internal fun YAxisLabels(
16 | horizontalGridLines: List,
17 | yAxisMarkerLayout: @Composable (value: Number) -> Unit,
18 | ) {
19 | Box(
20 | modifier = Modifier
21 | .width(IntrinsicSize.Max)
22 | .fillMaxHeight()
23 | .padding(end = 8.dp)
24 | ) {
25 | horizontalGridLines.forEach { horizontalLine ->
26 | Box(
27 | modifier = Modifier
28 | .alignCenterToOffsetVertical(horizontalLine.position)
29 | ) {
30 | yAxisMarkerLayout(horizontalLine.value)
31 | }
32 | }
33 | }
34 | }
35 |
36 | private fun Modifier.alignCenterToOffsetVertical(
37 | offsetToAlignWith: Float,
38 | ) = layout { measurable, constraints ->
39 | val placeable = measurable.measure(constraints)
40 | val placeableY = (offsetToAlignWith - (placeable.height / 2f)).coerceAtLeast(0f)
41 |
42 | layout(placeable.width, placeable.height) {
43 | placeable.placeRelative(0, placeableY.roundToInt())
44 | }
45 | }
46 |
47 | internal fun Modifier.alignCenterToOffsetHorizontal(
48 | offsetToAlignWith: Float,
49 | ) = layout { measurable, constraints ->
50 | val placeable = measurable.measure(constraints)
51 | val placeableX = offsetToAlignWith - (placeable.width / 2f)
52 |
53 | layout(placeable.width, placeable.height) {
54 | placeable.placeRelative(placeableX.roundToInt(), 0)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/OverlayInformation.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.BoxWithConstraints
5 | import androidx.compose.foundation.layout.offset
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.foundation.layout.width
8 | import androidx.compose.foundation.shape.RoundedCornerShape
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.draw.alpha
12 | import androidx.compose.ui.draw.clip
13 | import androidx.compose.ui.geometry.Size
14 | import androidx.compose.ui.graphics.Color
15 | import androidx.compose.ui.platform.LocalDensity
16 | import androidx.compose.ui.unit.Dp
17 | import androidx.compose.ui.unit.dp
18 |
19 | private val TOUCH_OFFSET = 20.dp
20 | private val OVERLAY_WIDTH = 200.dp
21 |
22 | @Composable
23 | internal fun OverlayInformation(
24 | positionX: Float,
25 | containerSize: Size,
26 | surfaceColor: Color,
27 | touchOffset: Dp = TOUCH_OFFSET,
28 | overlayWidth: Dp = OVERLAY_WIDTH,
29 | content: @Composable () -> Unit,
30 | ) {
31 | if (positionX < 0) return
32 | BoxWithConstraints(
33 | modifier = Modifier
34 | .offset(
35 | x = with(LocalDensity.current) {
36 | positionX.toDp() +
37 | // change offset based on cursor position to avoid out of screen drawing on the right
38 | if (positionX.toDp() > (containerSize.width / 2).toDp()) {
39 | -OVERLAY_WIDTH - TOUCH_OFFSET
40 | } else {
41 | TOUCH_OFFSET
42 | }
43 | },
44 | y = touchOffset
45 | )
46 | .width(OVERLAY_WIDTH)
47 | .alpha(0.9f)
48 | .clip(RoundedCornerShape(10.dp))
49 | .background(surfaceColor)
50 | .padding(8.dp)
51 | ) {
52 | content()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/grid/axisscale/YAxisScale.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.grid.axisscale
2 |
3 | import kotlin.math.floor
4 | import kotlin.math.log10
5 | import kotlin.math.pow
6 |
7 | class YAxisScale(
8 | min: Float,
9 | max: Float,
10 | maxTickCount: Int,
11 | roundClosestTo: Int,
12 | ) {
13 | val tick: Float
14 | val min: Float
15 | val max: Float
16 |
17 | init {
18 | this.min = if (!min.isNaN()) {
19 | min.getClosest(roundClosestTo)
20 | } else {
21 | 0f
22 | }
23 | this.max = if (!max.isNaN()) {
24 | max.getClosest(roundClosestTo)
25 | } else {
26 | 0f
27 | }
28 |
29 | val range = niceNum(this.max - this.min, false)
30 | this.tick = niceNum(range / (maxTickCount), true)
31 | }
32 |
33 | private fun Float.getClosest(n: Int) = when {
34 | this > 0f -> (((this.toInt() + n - 1) / n) * n).toFloat()
35 | this < 0f -> (((this.toInt() - n + 1) / n) * n).toFloat()
36 | else -> 0f
37 | }
38 |
39 | /**
40 | * Returns a "nice" number approximately equal to range.
41 | * Rounds the number if round = true Takes the ceiling if round = false.
42 | *
43 | * @param range the data range
44 | * @param round whether to round the result
45 | * @return a "nice" number to be used for the data range
46 | */
47 | private fun niceNum(range: Float, round: Boolean): Float {
48 | /** nice, rounded fraction */
49 | val exponent: Float = floor(log10(range))
50 | /** exponent of range */
51 | val fraction = range / 10.0f.pow(exponent)
52 | /** fractional part of range */
53 | val niceFraction: Float = if (round) {
54 | if (fraction < 1.5) 1.0f else if (fraction < 3) 2.0f else if (fraction < 7) 5.0f else 10.0f
55 | } else {
56 | if (fraction <= 1) 1.0f else if (fraction <= 2) 2.0f else if (fraction <= 5) 5.0f else 10.0f
57 | }
58 | return niceFraction * 10.0f.pow(exponent)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/bubble/Vector.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bubble
2 |
3 | import kotlin.math.sqrt
4 |
5 | data class Vector(var x: Float, var y: Float) {
6 | fun mag(): Float {
7 | return sqrt((magSq().toDouble()).toFloat())
8 | }
9 |
10 | private fun magSq(): Float {
11 | return x * x + y * y
12 | }
13 |
14 | fun add(v: Vector): Vector {
15 | x += v.x
16 | y += v.y
17 | return this
18 | }
19 |
20 | fun sub(v: Vector): Vector {
21 | x -= v.x
22 | y -= v.y
23 | return this
24 | }
25 |
26 | operator fun set(x: Float, y: Float): Vector {
27 | this.x = x
28 | this.y = y
29 | return this
30 | }
31 |
32 | fun mult(n: Float): Vector {
33 | x *= n
34 | y *= n
35 | return this
36 | }
37 |
38 | operator fun div(n: Float): Vector {
39 | x /= n
40 | y /= n
41 | return this
42 | }
43 |
44 | fun normalize(): Vector {
45 | val m = mag()
46 | if (m != 0f && m != 1f) {
47 | div(m)
48 | }
49 | return this
50 | }
51 |
52 | fun limit(max: Float): Vector {
53 | if (magSq() > max * max) {
54 | normalize()
55 | mult(max)
56 | }
57 | return this
58 | }
59 |
60 | fun setMag(len: Float): Vector {
61 | normalize()
62 | mult(len)
63 | return this
64 | }
65 |
66 | override fun equals(other: Any?): Boolean {
67 | if (other !is Vector) {
68 | return false
69 | }
70 | return x == other.x && y == other.y
71 | }
72 |
73 | override fun hashCode(): Int {
74 | var result = x.hashCode()
75 | result = 31 * result + y.hashCode()
76 | return result
77 | }
78 |
79 | companion object {
80 | fun sub(v1: Vector, v2: Vector): Vector = Vector(v1.x - v2.x, v1.y - v2.y)
81 |
82 | fun dist(v1: Vector, v2: Vector): Float {
83 | val dx = v1.x - v2.x
84 | val dy = v1.y - v2.y
85 | return sqrt((dx * dx + dy * dy).toDouble()).toFloat()
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/example-app/android/src/androidTest/kotlin/com/netguru/multiplatform/charts/gasbottle/GasBottleTest.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.gasbottle
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.test.junit4.createComposeRule
5 | import com.karumi.shot.ScreenshotTest
6 | import com.netguru.multiplatform.charts.Util.checkComposable
7 | import com.netguru.multiplatform.charts.theme.ChartDefaults
8 | import org.junit.Rule
9 | import org.junit.Test
10 |
11 | class GasBottleTest : ScreenshotTest {
12 |
13 | @get:Rule
14 | val composeRule = createComposeRule()
15 |
16 | // @Test // removed due to: https://github.com/pedrovgs/Shot/issues/265
17 | // fun value0_defaultUI() {
18 | // checkComposable(composeRule) {
19 | // GasBottle(percentage = 0f)
20 | // }
21 | // }
22 | //
23 | // @Test // removed due to: https://github.com/pedrovgs/Shot/issues/265
24 | // fun value50_defaultUI() {
25 | // checkComposable(composeRule) {
26 | // GasBottle(percentage = 50f)
27 | // }
28 | // }
29 | //
30 | // @Test // removed due to: https://github.com/pedrovgs/Shot/issues/265
31 | // fun value100_defaultUI() {
32 | // checkComposable(composeRule) {
33 | // GasBottle(percentage = 100f)
34 | // }
35 | // }
36 | //
37 | // @Test // removed due to: https://github.com/pedrovgs/Shot/issues/265
38 | // fun value25_customUI() {
39 | // checkComposable(composeRule) {
40 | // GasBottle(
41 | // percentage = 25f,
42 | // chartColors = ChartDefaults.chartColors(
43 | // emptyGasBottle = Color.Yellow,
44 | // fullGasBottle = Color.Blue,
45 | // )
46 | // )
47 | // }
48 | // }
49 | //
50 | // @Test // removed due to: https://github.com/pedrovgs/Shot/issues/265
51 | // fun value100_customUI() {
52 | // checkComposable(composeRule) {
53 | // GasBottle(
54 | // percentage = 100f,
55 | // chartColors = ChartDefaults.chartColors(
56 | // emptyGasBottle = Color.Yellow,
57 | // fullGasBottle = Color.Blue,
58 | // )
59 | // )
60 | // }
61 | // }
62 | }
63 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/line/LineChartWithLegend.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.line
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import com.netguru.multiplatform.charts.ChartAnimation
7 | import com.netguru.multiplatform.charts.grid.GridDefaults
8 | import com.netguru.multiplatform.charts.theme.ChartTheme
9 |
10 | /**
11 | * Classic line chart with legend below the chart.
12 | *
13 | * For more information, check [LineChart].
14 | *
15 | * @param legendItemLabel Composable to use to represent the value in the legend. Only text value
16 | * is customizable.
17 | *
18 | * @see LineChart
19 | */
20 | @Composable
21 | fun LineChartWithLegend(
22 | lineChartData: LineChartData,
23 | modifier: Modifier = Modifier,
24 | maxVerticalLines: Int = GridDefaults.NUMBER_OF_GRID_LINES,
25 | maxHorizontalLines: Int = GridDefaults.NUMBER_OF_GRID_LINES,
26 | animation: ChartAnimation = ChartAnimation.Simple(),
27 | colors: LineChartColors = ChartTheme.colors.lineChartColors,
28 | xAxisLabel: @Composable (value: Any) -> Unit = GridDefaults.XAxisLabel,
29 | yAxisLabel: @Composable (value: Any) -> Unit = GridDefaults.YAxisLabel,
30 | overlayHeaderLabel: @Composable (value: Any) -> Unit = GridDefaults.OverlayHeaderLabel,
31 | overlayDataEntryLabel: @Composable (dataName: String, value: Any) -> Unit = GridDefaults.OverlayDataEntryLabel,
32 | legendItemLabel: @Composable (String) -> Unit = GridDefaults.LegendItemLabel,
33 | ) {
34 | Column(modifier = modifier) {
35 | LineChart(
36 | lineChartData = lineChartData,
37 | modifier = Modifier.weight(1f),
38 | maxVerticalLines = maxVerticalLines,
39 | maxHorizontalLines = maxHorizontalLines,
40 | animation = animation,
41 | colors = colors,
42 | xAxisLabel = xAxisLabel,
43 | yAxisLabel = yAxisLabel,
44 | overlayHeaderLabel = overlayHeaderLabel,
45 | overlayDataEntryLabel = overlayDataEntryLabel,
46 | )
47 | ChartLegend(
48 | legendData = lineChartData.legendData,
49 | animation = animation,
50 | legendItemLabel = legendItemLabel,
51 | )
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/bubble/BubbleDefaults.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bubble
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.Spacer
5 | import androidx.compose.foundation.layout.fillMaxWidth
6 | import androidx.compose.foundation.layout.height
7 | import androidx.compose.foundation.layout.size
8 | import androidx.compose.foundation.layout.width
9 | import androidx.compose.material.Icon
10 | import androidx.compose.material.MaterialTheme
11 | import androidx.compose.material.Text
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.ui.Alignment
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.graphics.vector.rememberVectorPainter
17 | import androidx.compose.ui.text.style.TextAlign
18 | import androidx.compose.ui.text.style.TextOverflow
19 | import androidx.compose.ui.unit.dp
20 | import com.netguru.multiplatform.charts.round
21 |
22 | internal object BubbleDefaults {
23 |
24 | const val MINIMUM_BUBBLE_RADIUS = 40f
25 |
26 | val BubbleLabel: @Composable (Bubble) -> Unit = { bubble ->
27 | Column(
28 | horizontalAlignment = Alignment.CenterHorizontally,
29 | modifier = Modifier.width(bubble.radius.dp * 1.6f)
30 | ) {
31 | Icon(
32 | painter = rememberVectorPainter(bubble.icon),
33 | contentDescription = null,
34 | tint = Color.White,
35 | modifier = Modifier.size(20.dp),
36 | )
37 |
38 | Spacer(Modifier.height(4.dp))
39 |
40 | Text(
41 | modifier = Modifier.fillMaxWidth(),
42 | text = bubble.value.round(1),
43 | textAlign = TextAlign.Center,
44 | color = Color.White,
45 | style = MaterialTheme.typography.h5
46 | )
47 | Text(
48 | modifier = Modifier.fillMaxWidth(),
49 | text = bubble.name,
50 | color = Color.White.copy(alpha = 0.6f),
51 | maxLines = 1,
52 | overflow = TextOverflow.Ellipsis,
53 | textAlign = TextAlign.Center,
54 | style = MaterialTheme.typography.body2
55 | )
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/example-app/common/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.netguru.multiplatform.charts.extensions.androidMain
2 | import com.netguru.multiplatform.charts.extensions.baseAndroidSetup
3 | import com.netguru.multiplatform.charts.extensions.baseTestSetup
4 | import com.netguru.multiplatform.charts.extensions.sourceSets
5 | import org.gradle.kotlin.dsl.kotlin
6 |
7 | baseAndroidSetup()
8 | baseTestSetup()
9 |
10 | plugins {
11 | kotlin("multiplatform")
12 | @Suppress("DSL_SCOPE_VIOLATION")
13 | alias(libs.plugins.kotlin.compose)
14 | id("com.android.library")
15 | }
16 |
17 | kotlin {
18 | androidTarget()
19 | jvm("desktop")
20 |
21 | sourceSets {
22 | commonMain {
23 | kotlin.srcDir("${buildDir.absolutePath}/generated/resources")
24 | dependencies {
25 | api(libs.compose.runtime)
26 | api(libs.compose.ui)
27 | api(libs.compose.foundation)
28 | api(libs.compose.material)
29 | api(libs.compose.materialIconsExtended)
30 | api(libs.time.klock)
31 | api(project(":charts"))
32 | }
33 | }
34 | androidMain {
35 | dependencies {
36 | api(libs.androidx.appcompat)
37 | api(libs.androidx.coreKtx)
38 | }
39 | }
40 | }
41 | }
42 |
43 | android {
44 | sourceSets["main"].apply {
45 | res.srcDirs("src/androidMain/res", "src/commonMain/resources")
46 | }
47 | namespace = "com.netguru.multiplatform.charts.common"
48 | }
49 |
50 | project.rootProject.tasks.apply {
51 | register("resourceGeneratorTask", com.netguru.multiplatform.charts.resources.ResourceGeneratorTask::class) {
52 | group = "resource Generator"
53 | description = "Task for generating resources"
54 | }
55 | matching {
56 | it.name.contains("prepareKotlinBuildScriptModel")
57 | }.configureEach {
58 | dependsOn("resourceGeneratorTask")
59 | }
60 | }
61 |
62 | project.tasks.apply {
63 | register("resourceGeneratorTask", com.netguru.multiplatform.charts.resources.ResourceGeneratorTask::class) {
64 | group = "resource Generator"
65 | description = "Task for generating resources"
66 | }
67 | withType {
68 | dependsOn("resourceGeneratorTask")
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/docs/scripts/navigation-loader.js:
--------------------------------------------------------------------------------
1 | navigationPageText = fetch(pathToRoot + "navigation.html").then(response => response.text())
2 |
3 | displayNavigationFromPage = () => {
4 | navigationPageText.then(data => {
5 | document.getElementById("sideMenu").innerHTML = data;
6 | }).then(() => {
7 | document.querySelectorAll(".overview > a").forEach(link => {
8 | link.setAttribute("href", pathToRoot + link.getAttribute("href"));
9 | })
10 | }).then(() => {
11 | document.querySelectorAll(".sideMenuPart").forEach(nav => {
12 | if (!nav.classList.contains("hidden"))
13 | nav.classList.add("hidden")
14 | })
15 | }).then(() => {
16 | revealNavigationForCurrentPage()
17 | })
18 | document.querySelectorAll('.footer a[href^="#"]').forEach(anchor => {
19 | anchor.addEventListener('click', function (e) {
20 | e.preventDefault();
21 | document.querySelector(this.getAttribute('href')).scrollIntoView({
22 | behavior: 'smooth'
23 | });
24 | });
25 | });
26 | }
27 |
28 | revealNavigationForCurrentPage = () => {
29 | let pageId = document.getElementById("content").attributes["pageIds"].value.toString();
30 | let parts = document.querySelectorAll(".sideMenuPart");
31 | let found = 0;
32 | do {
33 | parts.forEach(part => {
34 | if (part.attributes['pageId'].value.indexOf(pageId) !== -1 && found === 0) {
35 | found = 1;
36 | if (part.classList.contains("hidden")) {
37 | part.classList.remove("hidden");
38 | part.setAttribute('data-active', "");
39 | }
40 | revealParents(part)
41 | }
42 | });
43 | pageId = pageId.substring(0, pageId.lastIndexOf("/"))
44 | } while (pageId.indexOf("/") !== -1 && found === 0)
45 | };
46 | revealParents = (part) => {
47 | if (part.classList.contains("sideMenuPart")) {
48 | if (part.classList.contains("hidden"))
49 | part.classList.remove("hidden");
50 | revealParents(part.parentNode)
51 | }
52 | };
53 |
54 | /*
55 | This is a work-around for safari being IE of our times.
56 | It doesn't fire a DOMContentLoaded, presumabely because eventListener is added after it wants to do it
57 | */
58 | if (document.readyState == 'loading') {
59 | window.addEventListener('DOMContentLoaded', () => {
60 | displayNavigationFromPage()
61 | })
62 | } else {
63 | displayNavigationFromPage()
64 | }
--------------------------------------------------------------------------------
/example-app/android/src/androidTest/kotlin/com/netguru/multiplatform/charts/pie/PieWithLegendTest.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.pie
2 |
3 | import androidx.compose.material.Text
4 | import androidx.compose.ui.graphics.Color
5 | import androidx.compose.ui.test.junit4.createComposeRule
6 | import androidx.compose.ui.unit.dp
7 | import com.karumi.shot.ScreenshotTest
8 | import com.netguru.multiplatform.charts.Util.checkComposable
9 | import org.junit.Rule
10 | import org.junit.Test
11 |
12 | class PieWithLegendTest : ScreenshotTest {
13 |
14 | @get:Rule
15 | val composeRule = createComposeRule()
16 |
17 | @Test
18 | fun twoCategories() {
19 | checkComposable(composeRule) {
20 | PieChartWithLegend(
21 | config = PieChartConfig(thickness = 80.dp, legendIcon = LegendIcon.ROUND),
22 | pieChartData = listOf(
23 | PieChartData("first", 22.0, Color.Blue),
24 | PieChartData("second", 22.0, Color.Yellow)
25 | )
26 | )
27 | }
28 | }
29 |
30 | @Test
31 | fun threeCategories() {
32 | checkComposable(composeRule) {
33 | PieChartWithLegend(
34 | config = PieChartConfig(thickness = 80.dp, legendIcon = LegendIcon.ROUND),
35 | pieChartData = listOf(
36 | PieChartData("first", 22.0, Color.Blue),
37 | PieChartData("second", 22.0, Color.Yellow),
38 | PieChartData("third", 22.0, Color.Green),
39 | )
40 | )
41 | }
42 | }
43 |
44 | @Test
45 | fun twoCategories_withZeroValue() {
46 | checkComposable(composeRule) {
47 | PieChartWithLegend(
48 | config = PieChartConfig(thickness = 80.dp, legendIcon = LegendIcon.ROUND),
49 | pieChartData = listOf(
50 | PieChartData("first", 22.0, Color.Blue),
51 | PieChartData("second", 0.0, Color.Yellow),
52 | )
53 | )
54 | }
55 | }
56 |
57 | @Test
58 | fun threeCategories_customUI() {
59 | checkComposable(composeRule) {
60 | PieChartWithLegend(
61 | pieChartData = listOf(
62 | PieChartData("first", 22.0, Color.Blue),
63 | PieChartData("second", 22.0, Color.Yellow),
64 | PieChartData("third", 22.0, Color.Green),
65 | ),
66 | config = PieChartConfig(
67 | thickness = 80.dp,
68 | numberOfColsInLegend = 2,
69 | legendIcon = LegendIcon.ROUND
70 | ),
71 | legendItemLabel = {
72 | Text(text = "${it.name} (${it.value})")
73 | }
74 | )
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/pie/PieChartWithLegend.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.pie
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.fillMaxHeight
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.wrapContentWidth
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Alignment
11 | import androidx.compose.ui.Modifier
12 | import com.netguru.multiplatform.charts.ChartAnimation
13 |
14 | /**
15 | * Version of [PieChart] with legend.
16 | *
17 | * @param legendItemLabel Composable to use to represent the item in the legend
18 | *
19 | * @see PieChart
20 | */
21 | @Composable
22 | fun PieChartWithLegend(
23 | pieChartData: List,
24 | modifier: Modifier = Modifier,
25 | animation: ChartAnimation = ChartAnimation.Simple(),
26 | config: PieChartConfig = PieChartConfig(),
27 | legendItemLabel: @Composable (PieChartData) -> Unit = PieDefaults.LegendItemLabel,
28 | ) {
29 | when (config.legendOrientation) {
30 | LegendOrientation.HORIZONTAL ->
31 | Column(
32 | horizontalAlignment = Alignment.CenterHorizontally,
33 | verticalArrangement = Arrangement.spacedBy(config.legendPadding),
34 | modifier = modifier,
35 | ) {
36 | PieChart(
37 | data = pieChartData,
38 | modifier = Modifier.weight(1f),
39 | animation = animation,
40 | config = config,
41 | )
42 | PieChartLegend(
43 | data = pieChartData,
44 | animation = animation,
45 | legendItemLabel = legendItemLabel,
46 | config = config,
47 | modifier = Modifier.fillMaxWidth(),
48 | )
49 | }
50 | LegendOrientation.VERTICAL ->
51 | Row(
52 | verticalAlignment = Alignment.CenterVertically,
53 | horizontalArrangement = Arrangement.spacedBy(config.legendPadding),
54 | modifier = modifier,
55 | ) {
56 | PieChart(
57 | data = pieChartData,
58 | modifier = Modifier.fillMaxHeight(),
59 | animation = animation,
60 | config = config,
61 | )
62 | PieChartLegend(
63 | data = pieChartData,
64 | animation = animation,
65 | legendItemLabel = legendItemLabel,
66 | config = config,
67 | modifier = Modifier.wrapContentWidth(),
68 | )
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/line/LineChartSeries.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.line
2 |
3 | import androidx.compose.runtime.Immutable
4 | import androidx.compose.ui.graphics.Color
5 | import androidx.compose.ui.unit.Dp
6 | import androidx.compose.ui.unit.dp
7 | import com.netguru.multiplatform.charts.grid.GridChartData
8 |
9 | @Immutable
10 | data class LineChartPoint(
11 | val x: Long,
12 | val y: Float,
13 | )
14 |
15 | @Immutable
16 | data class LineChartSeries(
17 | val dataName: String,
18 | val lineWidth: Dp = 3.dp,
19 | val lineColor: Color,
20 | val fillColor: Color = lineColor,
21 | val dashedLine: Boolean = false,
22 | val listOfPoints: List = emptyList(),
23 | ) {
24 | val minValue: Float
25 | val maxValue: Float
26 | val minTimestamp: Long
27 | val maxTimeStamp: Long
28 |
29 | init {
30 | // find max and min in series
31 | if (listOfPoints.isNotEmpty()) {
32 | val minMaxValue = getMinMaxValue()
33 | minValue = minMaxValue.first
34 | maxValue = minMaxValue.second
35 |
36 | val minMaxTimestamp = getMinMaxTimestamp()
37 | minTimestamp = minMaxTimestamp.first
38 | maxTimeStamp = minMaxTimestamp.second
39 | } else {
40 | minValue = 0f
41 | maxValue = 0f
42 | minTimestamp = 0L
43 | maxTimeStamp = 0L
44 | }
45 | }
46 |
47 | private fun getMinMaxTimestamp(): Pair {
48 | val sortedTimestamp = listOfPoints.sortedBy { it.x }
49 | return Pair(sortedTimestamp.first().x, sortedTimestamp.last().x)
50 | }
51 |
52 | private fun getMinMaxValue(): Pair {
53 | val sortedValue = listOfPoints.sortedBy { it.y }
54 | return Pair(sortedValue.first().y, sortedValue.last().y)
55 | }
56 | }
57 |
58 | @Immutable
59 | data class LineChartData(
60 | val series: List,
61 | ) : GridChartData {
62 | override val legendData: List
63 | get() = series.map {
64 | LegendItemData(
65 | name = it.dataName,
66 | symbolShape = SymbolShape.LINE,
67 | color = it.lineColor,
68 | dashed = it.dashedLine
69 | )
70 | }
71 |
72 | override val minX: Long
73 | override val maxX: Long
74 | override val minY: Float
75 | override val maxY: Float
76 |
77 | init {
78 | // find max and min in all data
79 | val timeStamps = mutableListOf()
80 | val values = mutableListOf()
81 | series.forEach {
82 | timeStamps.add(it.minTimestamp)
83 | timeStamps.add(it.maxTimeStamp)
84 | values.add(it.minValue)
85 | values.add(it.maxValue)
86 | }
87 | minX = timeStamps.minOrNull() ?: 0
88 | maxX = timeStamps.maxOrNull() ?: 0
89 | minY = values.minOrNull() ?: 0f
90 | maxY = values.maxOrNull() ?: 0f
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/example-app/android/src/androidTest/kotlin/com/netguru/multiplatform/charts/dial/PercentageDialTest.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.dial
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material.Text
6 | import androidx.compose.ui.Alignment
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.compose.ui.test.junit4.createComposeRule
10 | import androidx.compose.ui.unit.dp
11 | import androidx.compose.ui.unit.sp
12 | import com.karumi.shot.ScreenshotTest
13 | import com.netguru.multiplatform.charts.theme.ChartDefaults
14 | import com.netguru.multiplatform.charts.Util.checkComposable
15 | import org.junit.Rule
16 | import org.junit.Test
17 |
18 | class PercentageDialTest : ScreenshotTest {
19 |
20 | @get:Rule
21 | val composeRule = createComposeRule()
22 |
23 | @Test
24 | fun value_50_UI_default() {
25 | checkComposable(composeRule) { PercentageDial(percentage = 50) }
26 | }
27 |
28 | @Test
29 | fun value_0_UI_default() {
30 | checkComposable(composeRule) { PercentageDial(percentage = 0) }
31 | }
32 |
33 | @Test
34 | fun value_100_UI_default() {
35 | checkComposable(composeRule) { PercentageDial(percentage = 100) }
36 | }
37 |
38 | @Test
39 | fun value_minus100_UI_default() {
40 | checkComposable(composeRule) { PercentageDial(percentage = -100) }
41 | }
42 |
43 | @Test
44 | fun value_150_UI_default() {
45 | checkComposable(composeRule) { PercentageDial(percentage = 150) }
46 | }
47 |
48 | @Test
49 | fun value_69_UI_custom_colors_and_labels() {
50 | checkComposable(composeRule) {
51 | PercentageDial(
52 | percentage = 69,
53 | colors = ChartDefaults.chartColors(
54 | primary = Color.Blue,
55 | grid = Color.Magenta,
56 | ).dialColors,
57 | minAndMaxValueLabel = {
58 | Text(
59 | text = it.toString(),
60 | color = Color.Blue,
61 | fontSize = 32.sp,
62 | modifier = Modifier.padding(top = 15.dp)
63 | )
64 | },
65 | mainLabel = {
66 | Column(
67 | horizontalAlignment = Alignment.CenterHorizontally
68 | ) {
69 | Text(text = it.toString(), color = Color.Blue, fontSize = 40.sp)
70 | Text(text = "tests", color = Color.Magenta, fontSize = 24.sp)
71 | }
72 | }
73 | )
74 | }
75 | }
76 |
77 | @Test
78 | fun value_69_UI_no_labels() {
79 | checkComposable(composeRule) {
80 | PercentageDial(
81 | percentage = 69,
82 | minAndMaxValueLabel = { },
83 | mainLabel = { }
84 | )
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/bar/BarChartDrawing.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bar
2 |
3 | import androidx.compose.ui.geometry.CornerRadius
4 | import androidx.compose.ui.geometry.Offset
5 | import androidx.compose.ui.geometry.Size
6 | import androidx.compose.ui.graphics.drawscope.DrawScope
7 | import com.netguru.multiplatform.charts.mapValueToDifferentRange
8 |
9 | internal fun DrawScope.drawBarChart(
10 | data: BarChartData,
11 | config: BarChartConfig,
12 | yAxisUpperValue: Float,
13 | yAxisLowerValue: Float,
14 | valueScale: List,
15 | yAxisZeroPosition: Float
16 | ): List {
17 | val xAxisTickWidth = size.width / (data.categories.size * 2)
18 | val maxBarsCountInACluster =
19 | data.categories.maxOfOrNull { it.entries.size } ?: return emptyList()
20 | val maximumThickness = config.thickness.toPx().coerceAtLeast(1f)
21 | val barWidth = (xAxisTickWidth / maxBarsCountInACluster).coerceIn(1f, maximumThickness)
22 | val gapsCount = maxBarsCountInACluster - 1
23 | val freeSpaceInACluster = (xAxisTickWidth - (barWidth * maxBarsCountInACluster))
24 | val maxHorizontalSpacing = if (gapsCount > 0) freeSpaceInACluster / gapsCount else 0f
25 | val barsHorizontalSpacing = config.barsSpacing.toPx().coerceIn(0f, maxHorizontalSpacing)
26 | return data.categories.flatMapIndexed { categoryIndex, category ->
27 | val clusterWidth = category.entries.size * barWidth +
28 | (category.entries.size - 1) * barsHorizontalSpacing
29 | val clusterXOffset = (categoryIndex * 2 + 1) * xAxisTickWidth - clusterWidth / 2
30 |
31 | category.entries.mapIndexed { entryIndex, entry ->
32 | val x = clusterXOffset + entryIndex * (barWidth + barsHorizontalSpacing)
33 | val y = entry.y * valueScale[entryIndex]
34 | val currentPosition = y.mapValueToDifferentRange(
35 | yAxisLowerValue,
36 | yAxisUpperValue,
37 | size.height,
38 | 0f
39 | )
40 | val barHeight = if (currentPosition < yAxisZeroPosition) {
41 | yAxisZeroPosition - currentPosition
42 | } else {
43 | currentPosition - yAxisZeroPosition
44 | }
45 | val newY = if (entry.y > 0) {
46 | currentPosition
47 | } else {
48 | yAxisZeroPosition
49 | }
50 | drawRoundRect(
51 | color = entry.color,
52 | topLeft = Offset(
53 | x = x,
54 | y = newY
55 | ),
56 | size = Size(
57 | width = barWidth,
58 | height = barHeight
59 | ),
60 | cornerRadius = CornerRadius(config.cornerRadius.toPx(), config.cornerRadius.toPx())
61 | )
62 | BarChartBar(
63 | width = x..(x + barWidth),
64 | height = newY..(newY + barHeight),
65 | data = entry
66 | )
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Creating new Pull Request
4 | * Fork repository and work on your fork.
5 | * Create a new PR with a request to merge to the **main** branch
6 | * If PR is based on issue, then include its number in PR title and add link to it in description
7 | * One pull request should always address one issue or feature
8 | * When contributing a new feature, provide motivation and use-cases describing value it may brings to the project
9 | * Adding video or screenshot is very beneficial, but it's not mandatory
10 | * Make sure any code contributed is covered by test and documented
11 |
12 | ## Code Style
13 | * Make sure your code is meeting the default [Ktlint standards](https://ktlint.github.io/#rules)
14 |
15 | ## Documentation
16 | Dokka is added to the project, so to create (or, better say, update) the docs, you need to run
17 | ```
18 | ./gradlew charts:dokkaHtml
19 | ```
20 | on the root dir of the project.
21 |
22 | After the task is done, copy the generated docs into `./docs`, which can also be done by running
23 | ```
24 | rm -rf ./docs ; cp -r ./charts/build/dokka/html ./docs
25 | ```
26 |
27 | ## Testing
28 | Testing is implemented using [Shot](https://github.com/pedrovgs/Shot) library. In order to run
29 | it against prerecorded results, create at least one of the following emulators:
30 | - tablet: Nexus 10, running API 31
31 | - phone: Pixel 4a, running API 31
32 |
33 | For tests to work, make sure you create emulators with default values, as **screen size and
34 | density must be exactly the same**.
35 |
36 | Details as to how to run the tests are in the link to the library itself, but to run it against
37 | prerecorded screenshots, run the following command:
38 | ```
39 | ./gradlew executeScreenshotTests -PdirectorySuffix=$deviceName
40 | ```
41 | where `$deviceName` is one of:
42 | - Nexus_10_API_31
43 | - Pixel_4a_API_31
44 |
45 | ### Flakiness
46 | Due to differences in graphics between M1 and x86 chips, images appear different to the script
47 | and so the tests fail (difference between images can be well above 10%). To human eye they do
48 | seem the same, though. This is explained and shown in [issue 265](https://github.com/pedrovgs/Shot/issues/265). Until this is fixed, there is a way to test stuff at least locally:
49 |
50 | 1. uncomment tests that are commented out because of this issue
51 | 2. record all the tests
52 | 3. make the desired changes in the code
53 | 4. check the new UI state against the pre-recorded screenshots (I suggest removing tolerance if
54 | the tests were recorded on your machine!)
55 | 5. comment the flaky tests out again
56 | 6. revert the screenshots
57 |
58 | ### Other issues
59 |
60 | - Library [does not work with Java 17](https://github.com/pedrovgs/Shot/pull/292). The error
61 | itself is not as obvious as one might expect, though. To fix the issue, use Java 11.
62 | - Java heap space sometimes [needs to be enlarged](https://github.com/pedrovgs/Shot/issues/304)
63 | - You might encounter [INSTALL_FAILED_SHARED_USER_INCOMPATIBLE](https://stackoverflow.com/questions/15205159/install-failed-shared-user-incompatible-while-using-shared-user-id)
64 | issue. [Here](https://stackoverflow.com/a/21809883/6835732) is a shortcut to the answer.
65 |
--------------------------------------------------------------------------------
/example-app/android/src/androidTest/kotlin/com/netguru/multiplatform/charts/line/LineTest.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.line
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.material.Text
5 | import androidx.compose.ui.Alignment
6 | import androidx.compose.ui.graphics.Color
7 | import androidx.compose.ui.test.junit4.createComposeRule
8 | import androidx.compose.ui.text.style.TextAlign
9 | import androidx.compose.ui.unit.dp
10 | import androidx.compose.ui.unit.sp
11 | import com.karumi.shot.ScreenshotTest
12 | import com.netguru.multiplatform.charts.Util.checkComposable
13 | import com.netguru.multiplatform.charts.theme.ChartDefaults
14 | import com.soywiz.klock.DateTime
15 | import org.junit.Rule
16 | import org.junit.Test
17 |
18 | class LineTest : ScreenshotTest {
19 |
20 | @get:Rule
21 | val composeRule = createComposeRule()
22 |
23 | // @Test // removed due to: https://github.com/pedrovgs/Shot/issues/265
24 | // fun mixedValues_defaultUI() {
25 | // checkComposable(composeRule) {
26 | // LineChart(
27 | // lineChartData = Data.generateLineData(3)
28 | // )
29 | // }
30 | // }
31 | //
32 | // @Test // removed due to: https://github.com/pedrovgs/Shot/issues/265
33 | // fun mixedValues_customUI() {
34 | // val data = Data.generateLineData(3)
35 | // checkComposable(composeRule) {
36 | // LineChart(
37 | // lineChartData = data
38 | // .copy(
39 | // series = data.series.map {
40 | // it.copy(
41 | // lineWidth = 12.dp,
42 | // fillColor = Color.Yellow,
43 | // lineColor = Color.Blue,
44 | // dashedLine = true,
45 | // )
46 | // }
47 | // ),
48 | // chartColors = ChartDefaults.chartColors(
49 | // grid = Color.Magenta,
50 | // surface = Color.Cyan,
51 | // overlayLine = Color.Gray,
52 | // ),
53 | // xAxisLabel = {
54 | // Column(
55 | // horizontalAlignment = Alignment.CenterHorizontally,
56 | // ) {
57 | // Text(
58 | // fontSize = 12.sp,
59 | // text = DateTime.fromUnix(it as Long).format("yyyy-MM-dd"),
60 | // textAlign = TextAlign.Center
61 | // )
62 | // Text(
63 | // text = "midday"
64 | // )
65 | // }
66 | // },
67 | // yAxisLabel = {
68 | // Column(
69 | // horizontalAlignment = Alignment.End
70 | // ) {
71 | // Text(text = it.toString())
72 | // Text(text = "units")
73 | // }
74 | // },
75 | // maxVerticalLines = 2,
76 | // maxHorizontalLines = 2,
77 | // )
78 | // }
79 | // }
80 | }
81 |
--------------------------------------------------------------------------------
/example-app/android/src/androidTest/kotlin/com/netguru/multiplatform/charts/dial/DialTest.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.dial
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material.Text
6 | import androidx.compose.ui.Alignment
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.graphics.Color
9 | import androidx.compose.ui.test.junit4.createComposeRule
10 | import androidx.compose.ui.unit.dp
11 | import androidx.compose.ui.unit.sp
12 | import com.karumi.shot.ScreenshotTest
13 | import com.netguru.multiplatform.charts.theme.ChartDefaults
14 | import com.netguru.multiplatform.charts.Util.checkComposable
15 | import org.junit.Rule
16 | import org.junit.Test
17 |
18 | class DialTest : ScreenshotTest {
19 |
20 | @get:Rule
21 | val composeRule = createComposeRule()
22 |
23 | @Test
24 | fun range_0_100_value_50_UI_default() {
25 | checkComposable(composeRule) { Dial(value = 50, minValue = 0, maxValue = 100) }
26 | }
27 |
28 | @Test
29 | fun range_0_100_value_0_UI_default() {
30 | checkComposable(composeRule) { Dial(value = 0, minValue = 0, maxValue = 100) }
31 | }
32 |
33 | @Test
34 | fun range_0_100_value_100_UI_default() {
35 | checkComposable(composeRule) { Dial(value = 100, minValue = 0, maxValue = 100) }
36 | }
37 |
38 | @Test
39 | fun range_0_100_value_minus50_UI_default() {
40 | checkComposable(composeRule) { Dial(value = -50, minValue = 0, maxValue = 100) }
41 | }
42 |
43 | @Test
44 | fun range_0_100_value_150_UI_default() {
45 | checkComposable(composeRule) { Dial(value = 150, minValue = 0, maxValue = 100) }
46 | }
47 |
48 | @Test
49 | fun range_0_100_value_69_UI_custom_colors_and_labels() {
50 | checkComposable(composeRule) {
51 | Dial(
52 | value = 69,
53 | minValue = 0,
54 | maxValue = 100,
55 | colors = ChartDefaults.chartColors(
56 | primary = Color.Blue,
57 | grid = Color.Magenta,
58 | ).dialColors,
59 | minAndMaxValueLabel = {
60 | Text(
61 | text = it.toString(),
62 | color = Color.Blue,
63 | fontSize = 32.sp,
64 | modifier = Modifier.padding(top = 15.dp)
65 | )
66 | },
67 | mainLabel = {
68 | Column(
69 | horizontalAlignment = Alignment.CenterHorizontally
70 | ) {
71 | Text(text = it.toString(), color = Color.Blue, fontSize = 40.sp)
72 | Text(text = "tests", color = Color.Magenta, fontSize = 24.sp)
73 | }
74 | }
75 | )
76 | }
77 | }
78 |
79 | @Test
80 | fun range_0_100_value_69_UI_no_labels() {
81 | checkComposable(composeRule) {
82 | Dial(
83 | value = 69,
84 | minValue = 0,
85 | maxValue = 100,
86 | minAndMaxValueLabel = { },
87 | mainLabel = { }
88 | )
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/example-app/android/src/main/java/com/netguru/multiplatform/charts/android/previews/LineChartPreview.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.android.previews
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.height
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.tooling.preview.Preview
8 | import androidx.compose.ui.unit.dp
9 | import com.netguru.multiplatform.charts.ChartAnimation
10 | import com.netguru.multiplatform.charts.common.AppTheme
11 | import com.netguru.multiplatform.charts.common.HOUR_IN_MS
12 | import com.netguru.multiplatform.charts.common.WindowSize
13 | import com.netguru.multiplatform.charts.line.LineChartData
14 | import com.netguru.multiplatform.charts.line.LineChartPoint
15 | import com.netguru.multiplatform.charts.line.LineChartSeries
16 | import com.netguru.multiplatform.charts.line.LineChartWithLegend
17 | import com.soywiz.klock.DateTime
18 |
19 | @Preview(showBackground = true, widthDp = 600)
20 | @Composable
21 | fun LineChartPreview() {
22 | AppTheme(windowSize = WindowSize.EXPANDED) {
23 | LineChartWithLegend(
24 | modifier = Modifier
25 | .height(300.dp)
26 | .fillMaxWidth(),
27 | lineChartData = getLineChartSampleData(),
28 | maxVerticalLines = 10,
29 | animation = ChartAnimation.Disabled,
30 | )
31 | }
32 | }
33 |
34 | @Composable
35 | private fun getLineChartSampleData(): LineChartData {
36 | val startTime = DateTime.now().milliseconds
37 | val list = mutableListOf()
38 |
39 | list.add(
40 | LineChartSeries(
41 | "Solar",
42 | lineColor = AppTheme.colors.yellow,
43 | dashedLine = false,
44 | listOfPoints = listOf(
45 | LineChartPoint(0L * HOUR_IN_MS + startTime, 0f),
46 | LineChartPoint(1L * HOUR_IN_MS + startTime, 1f),
47 | LineChartPoint(2L * HOUR_IN_MS + startTime, 2f),
48 | LineChartPoint(3L * HOUR_IN_MS + startTime, 3f),
49 | LineChartPoint(5L * HOUR_IN_MS + startTime, 4f),
50 | )
51 | )
52 | )
53 |
54 | list.add(
55 | LineChartSeries(
56 | "Grid",
57 | lineColor = AppTheme.colors.green,
58 | dashedLine = false,
59 | listOfPoints = listOf(
60 | LineChartPoint(0L * HOUR_IN_MS + startTime, 3f),
61 | LineChartPoint(1L * HOUR_IN_MS + startTime, 2f),
62 | LineChartPoint(2L * HOUR_IN_MS + startTime, 1f),
63 | LineChartPoint(5L * HOUR_IN_MS + startTime, 3f),
64 | )
65 | )
66 | )
67 |
68 | list.add(
69 | LineChartSeries(
70 | "Fossil",
71 | lineColor = AppTheme.colors.blue,
72 | dashedLine = false,
73 | listOfPoints = listOf(
74 | LineChartPoint(0L * HOUR_IN_MS + startTime, 1f),
75 | LineChartPoint(1L * HOUR_IN_MS + startTime, 3f),
76 | LineChartPoint(2L * HOUR_IN_MS + startTime, 2f),
77 | LineChartPoint(3L * HOUR_IN_MS + startTime, 0f),
78 | LineChartPoint(5L * HOUR_IN_MS + startTime, 1f),
79 | )
80 | )
81 | )
82 |
83 | return LineChartData(
84 | series = list,
85 | )
86 | }
87 |
--------------------------------------------------------------------------------
/docs/-kotlin multiplatform charts/com.netguru.multiplatform.charts.line/-symbol-shape/-l-i-n-e/name.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | name
5 |
6 |
7 |
8 |
14 |
15 |
16 |
17 |
23 |
24 |
name
25 |
26 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/docs/-kotlin multiplatform charts/com.netguru.multiplatform.charts.line/-symbol-shape/-l-i-n-e/ordinal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ordinal
5 |
6 |
7 |
8 |
14 |
15 |
16 |
17 |
23 |
24 |
ordinal
25 |
26 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/docs/-kotlin multiplatform charts/com.netguru.multiplatform.charts.line/-symbol-shape/-r-e-c-t-a-n-g-l-e/name.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | name
5 |
6 |
7 |
8 |
14 |
15 |
16 |
17 |
23 |
24 |
name
25 |
26 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/docs/-kotlin multiplatform charts/com.netguru.multiplatform.charts.line/-symbol-shape/-r-e-c-t-a-n-g-l-e/ordinal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ordinal
5 |
6 |
7 |
8 |
14 |
15 |
16 |
17 |
23 |
24 |
ordinal
25 |
26 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/example-app/android/src/main/java/com/netguru/multiplatform/charts/android/previews/BarChartPreview.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.android.previews
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.height
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.tooling.preview.Preview
9 | import androidx.compose.ui.unit.dp
10 | import com.netguru.multiplatform.charts.bar.BarChartCategory
11 | import com.netguru.multiplatform.charts.bar.BarChartData
12 | import com.netguru.multiplatform.charts.bar.BarChartEntry
13 | import com.netguru.multiplatform.charts.bar.BarChartWithLegend
14 | import com.netguru.multiplatform.charts.common.AppTheme
15 | import com.netguru.multiplatform.charts.common.WindowSize
16 |
17 | @Preview(widthDp = 800, showBackground = true)
18 | @Composable
19 | fun BarChartPreview() {
20 | AppTheme(windowSize = WindowSize.EXPANDED) {
21 | BarChartWithLegend(
22 | data = barChartSampleData(),
23 | modifier = Modifier
24 | .height(300.dp)
25 | .fillMaxWidth()
26 | .padding(20.dp),
27 | )
28 | }
29 | }
30 |
31 | @Composable
32 | fun barChartSampleData(): BarChartData {
33 | return BarChartData(
34 | categories = listOf(
35 | BarChartCategory(
36 | name = "Mon",
37 | entries = listOf(
38 | BarChartEntry("Actual usage", 10f, AppTheme.colors.yellow),
39 | BarChartEntry("Forecasted usage", 20f, AppTheme.colors.green)
40 | )
41 | ),
42 | BarChartCategory(
43 | name = "Tue",
44 | entries = listOf(
45 | BarChartEntry("Actual usage", 15f, AppTheme.colors.yellow),
46 | BarChartEntry("Forecasted usage", 10f, AppTheme.colors.green)
47 | )
48 | ),
49 | BarChartCategory(
50 | name = "Wed",
51 | entries = listOf(
52 | BarChartEntry("Actual usage", 5f, AppTheme.colors.yellow),
53 | BarChartEntry("Forecasted usage", 10f, AppTheme.colors.green)
54 | )
55 | ),
56 | BarChartCategory(
57 | name = "Thu",
58 | entries = listOf(
59 | BarChartEntry("Actual usage", 10f, AppTheme.colors.yellow),
60 | BarChartEntry("Forecasted usage", 10f, AppTheme.colors.green),
61 | )
62 | ),
63 | BarChartCategory(
64 | name = "Fri",
65 | entries = listOf(
66 | BarChartEntry("Actual usage", 3f, AppTheme.colors.yellow),
67 | BarChartEntry("Forecasted usage", 2f, AppTheme.colors.green)
68 | )
69 | ),
70 | BarChartCategory(
71 | name = "Sat",
72 | entries = listOf(
73 | BarChartEntry("Actual usage", 0f, AppTheme.colors.yellow),
74 | BarChartEntry("Forecasted usage", 2f, AppTheme.colors.green)
75 | )
76 | ),
77 | BarChartCategory(
78 | name = "Sun",
79 | entries = listOf(
80 | BarChartEntry("Actual usage", 12f, AppTheme.colors.yellow),
81 | BarChartEntry("Forecasted usage", 12f, AppTheme.colors.green)
82 | )
83 | ),
84 | ),
85 | )
86 | }
87 |
--------------------------------------------------------------------------------
/example-app/application/src/commonMain/kotlin/com/netguru/multiplatform/charts/application/screen/DialChartScreen.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.application.screen
2 |
3 | import androidx.compose.animation.core.Spring
4 | import androidx.compose.animation.core.spring
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.material.MaterialTheme
9 | import androidx.compose.material.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.unit.dp
14 | import com.netguru.multiplatform.charts.ChartAnimation
15 | import com.netguru.multiplatform.charts.application.ScrollableScreen
16 | import com.netguru.multiplatform.charts.application.SpacedColumn
17 | import com.netguru.multiplatform.charts.application.TitleText
18 | import com.netguru.multiplatform.charts.common.AppTheme
19 | import com.netguru.multiplatform.charts.common.HorizontalDivider
20 | import com.netguru.multiplatform.charts.dial.Dial
21 | import com.netguru.multiplatform.charts.dial.DialConfig
22 | import com.netguru.multiplatform.charts.dial.PercentageDial
23 |
24 | @Composable
25 | fun DialChartScreen() {
26 | ScrollableScreen {
27 | SpacedColumn {
28 | TitleText(text = "Percentage dial")
29 |
30 | PercentageDial(
31 | percentage = 69,
32 | modifier = Modifier
33 | .fillMaxWidth(),
34 | animation = ChartAnimation.Simple {
35 | spring(
36 | dampingRatio = Spring.DampingRatioMediumBouncy,
37 | stiffness = Spring.StiffnessLow
38 | )
39 | },
40 | config = DialConfig(
41 | thickness = 20.dp,
42 | roundCorners = true,
43 | ),
44 | mainLabel = {
45 | Column(
46 | horizontalAlignment = Alignment.CenterHorizontally
47 | ) {
48 | Text(
49 | text = "$it%",
50 | style = MaterialTheme.typography.h4,
51 | color = AppTheme.colors.yellow
52 | )
53 | Text(
54 | text = "of people like numbers",
55 | style = MaterialTheme.typography.body2,
56 | modifier = Modifier.padding(vertical = AppTheme.dimens.grid_2)
57 | )
58 | }
59 | }
60 | )
61 |
62 | HorizontalDivider()
63 |
64 | TitleText(text = "Custom ranged dial")
65 |
66 | Dial(
67 | modifier = Modifier
68 | .fillMaxWidth(),
69 | value = 17,
70 | minValue = -20,
71 | maxValue = 50,
72 | animation = ChartAnimation.Simple {
73 | spring(
74 | dampingRatio = Spring.DampingRatioMediumBouncy,
75 | stiffness = Spring.StiffnessLow
76 | )
77 | },
78 | config = DialConfig(
79 | thickness = 30.dp,
80 | ),
81 | mainLabel = {
82 | Text(
83 | text = "$it°C",
84 | style = MaterialTheme.typography.h4,
85 | color = AppTheme.colors.yellow
86 | )
87 | }
88 | )
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/example-app/android/src/androidTest/kotlin/com/netguru/multiplatform/charts/bar/BarWithLegendTest.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.bar
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.material.Text
5 | import androidx.compose.ui.Alignment
6 | import androidx.compose.ui.graphics.Color
7 | import androidx.compose.ui.test.junit4.createComposeRule
8 | import androidx.compose.ui.unit.dp
9 | import com.karumi.shot.ScreenshotTest
10 | import com.netguru.multiplatform.charts.Util.checkComposable
11 | import com.netguru.multiplatform.charts.theme.ChartDefaults
12 | import org.junit.Rule
13 | import org.junit.Test
14 |
15 | class BarWithLegendTest : ScreenshotTest {
16 |
17 | @get:Rule
18 | val composeRule = createComposeRule()
19 |
20 | @Suppress("SameParameterValue")
21 | private fun testBasicChartWithLegend(
22 | nOfCategories: Int,
23 | nOfEntries: Int,
24 | valueTypes: Data.ValueTypes
25 | ) {
26 | val data = Data.generateData(nOfCategories, nOfEntries, valueTypes)
27 | checkComposable(composeRule) {
28 | BarChartWithLegend(
29 | data = data,
30 | config = BarChartConfig(
31 | thickness = 8.dp,
32 | cornerRadius = 0.dp,
33 | barsSpacing = 1.dp,
34 | maxHorizontalLinesCount = 2,
35 | )
36 | )
37 | }
38 | }
39 |
40 | @Suppress("SameParameterValue")
41 | private fun testCustomChartWithLegend(
42 | nOfCategories: Int,
43 | nOfEntries: Int,
44 | valueTypes: Data.ValueTypes
45 | ) {
46 | val data = Data.generateData(nOfCategories, nOfEntries, valueTypes)
47 | checkComposable(composeRule) {
48 | BarChartWithLegend(
49 | data = data,
50 | colors = ChartDefaults.chartColors(grid = Color.Red).barChartColors,
51 | xAxisLabel = {
52 | Column(
53 | horizontalAlignment = Alignment.CenterHorizontally,
54 | ) {
55 | Text(text = it.toString())
56 | Text(text = "testing")
57 | }
58 | },
59 | yAxisLabel = {
60 | Column(
61 | horizontalAlignment = Alignment.CenterHorizontally,
62 | ) {
63 | Text(text = it.toString())
64 | Text(text = "units")
65 | }
66 | },
67 | legendItemLabel = {
68 | Text(text = "Custom label for: $it")
69 | }
70 | )
71 | }
72 | }
73 |
74 | /********************************
75 | * zero values
76 | ********************************/
77 |
78 | // @Test
79 | // fun oneCategory_oneBar_zeroValues() { TODO BN-3899
80 | // val data = Data.generateData(1, 1, Data.ValueTypes.ZERO)
81 | // checkComposable(composeRule) {
82 | // BarChartWithLegend(data = data)
83 | // }
84 | // }
85 |
86 | /********************************
87 | * positive values
88 | ********************************/
89 |
90 | @Test
91 | fun oneCategory_oneBar_positiveValues() {
92 | testBasicChartWithLegend(1, 1, Data.ValueTypes.POSITIVE)
93 | }
94 |
95 | @Test
96 | fun oneCategory_fiveBars_positiveValues() {
97 | testBasicChartWithLegend(1, 5, Data.ValueTypes.POSITIVE)
98 | }
99 |
100 | @Test
101 | fun oneCategory_oneBar_positiveValues_customUI() {
102 | testCustomChartWithLegend(1, 1, Data.ValueTypes.POSITIVE)
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | ## SDK Versions
3 | compileSdk = "35"
4 | minSdk = "24"
5 | targetSdk = "31"
6 | versionCode = "1"
7 | versionName = "0.1"
8 | applicationId = "com.netguru.multiplatform.charts"
9 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
10 | optInFlags = "-opt-in=kotlin.RequiresOptIn"
11 |
12 | #kotlin
13 | jvmTarget = "11"
14 |
15 | #desktop
16 | desktop-packageVersion = "1.0.0"
17 | desktop-packageName = "jvm"
18 |
19 | # Dependencies
20 | kotlin-gradle-plugin = "2.1.20"
21 | android-gradle-plugin = "8.9.1"
22 | jetbrains-compose = "1.7.3"
23 | activity-compose = "1.10.1"
24 | compose-test = "1.7.8"
25 | coroutines = "1.10.2"
26 | appcompat = "1.7.0"
27 | core-ktx = "1.16.0"
28 | junit = "4.13.2"
29 | window = "1.3.0"
30 | turbine = "0.7.0"
31 | klock = "2.4.13"
32 | kotlin-poet = "1.14.2"
33 | mockk = "1.12.3"
34 | shot = "5.14.1"
35 | ktlint = "10.2.1"
36 | dokka = "2.0.0"
37 |
38 | # Project
39 | project-group = "com.netguru"
40 | project-version = "0.1"
41 | project-name = "Compose Multiplatform Charts"
42 |
43 |
44 | [libraries]
45 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-gradle-plugin" }
46 | plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin-gradle-plugin" }
47 | plugin-android = { module = "com.android.tools.build:gradle", version.ref = "android-gradle-plugin" }
48 | plugin-poet = { module = "com.squareup:kotlinpoet", version.ref = "kotlin-poet"}
49 | plugin-shot = { module = "com.karumi:shot", version.ref = "shot" }
50 | compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrains-compose" }
51 | compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "jetbrains-compose" }
52 | compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "jetbrains-compose" }
53 | compose-material = { module = "org.jetbrains.compose.material:material", version.ref = "jetbrains-compose" }
54 | compose-materialIconsExtended = { module = "org.jetbrains.compose.material:material-icons-extended", version.ref = "jetbrains-compose" }
55 | compose-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "jetbrains-compose" }
56 | compose-uiTooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "jetbrains-compose" }
57 | kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines"}
58 | androidx-compose = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" }
59 | androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
60 | androidx-coreKtx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" }
61 | androidx-window = { module = "androidx.window:window", version.ref = "window" }
62 | test-junit = { module = "junit:junit", version.ref = "junit" }
63 | test-ui-junit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose-test" }
64 | test-ui-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose-test" }
65 | test-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
66 | test-turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" }
67 | time-klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
68 | mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
69 | mockk-agentJvm = { module = "io.mockk:mockk-agent-jvm", version.ref = "mockk" }
70 | mockk-common = { module = "io.mockk:mockk-common", version.ref = "mockk" }
71 |
72 | [plugins]
73 | compose-desktop = { id = "org.jetbrains.compose", version.ref = "jetbrains-compose" }
74 | ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
75 | kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-gradle-plugin"}
76 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
77 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin-gradle-plugin" }
78 | kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin-gradle-plugin" }
79 |
--------------------------------------------------------------------------------
/example-app/application/src/commonMain/kotlin/com/netguru/multiplatform/charts/application/screen/LineChartScreen.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.application.screen
2 |
3 | import androidx.compose.foundation.layout.height
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.Text
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.text.style.TextAlign
11 | import androidx.compose.ui.unit.dp
12 | import androidx.compose.ui.unit.sp
13 | import com.netguru.multiplatform.charts.ChartAnimation
14 | import com.netguru.multiplatform.charts.application.ScrollableScreen
15 | import com.netguru.multiplatform.charts.application.SpacedColumn
16 | import com.netguru.multiplatform.charts.application.TitleText
17 | import com.netguru.multiplatform.charts.common.HorizontalDivider
18 | import com.netguru.multiplatform.charts.line.LineChart
19 | import com.netguru.multiplatform.charts.line.LineChartData
20 | import com.netguru.multiplatform.charts.line.LineChartPoint
21 | import com.netguru.multiplatform.charts.line.LineChartSeries
22 | import com.netguru.multiplatform.charts.line.LineChartWithLegend
23 | import com.soywiz.klock.DateTime
24 | import com.soywiz.klock.TimeSpan
25 |
26 | @Composable
27 | fun LineChartScreen() {
28 |
29 | val lineData = remember {
30 | LineChartData(
31 | series = (1..3).map {
32 | LineChartSeries(
33 | dataName = "data $it",
34 | lineColor = listOf(
35 | Color(0xFFFFCC00),
36 | Color(0xFF00D563),
37 | Color(0xFF32ADE6),
38 | )[it - 1],
39 | listOfPoints = (1..10).map { point ->
40 | LineChartPoint(
41 | x = DateTime.now().minus(TimeSpan(point * 24 * 60 * 60 * 1000.0)).unixMillisLong,
42 | y = (1..15).random().toFloat(),
43 | )
44 | }
45 | )
46 | },
47 | )
48 | }
49 |
50 | ScrollableScreen {
51 | SpacedColumn {
52 |
53 | TitleText(text = "Line chart")
54 | LineChart(
55 | lineChartData = lineData,
56 | modifier = Modifier
57 | .height(300.dp),
58 | xAxisLabel = {
59 | Text(
60 | fontSize = 12.sp,
61 | text = DateTime.fromUnix(it as Long).format("yyyy-MM-dd"),
62 | textAlign = TextAlign.Center
63 | )
64 | },
65 | overlayHeaderLabel = {
66 | Text(
67 | text = DateTime.fromUnix(it as Long).format("yyyy-MM-dd"),
68 | style = MaterialTheme.typography.overline
69 | )
70 | },
71 | animation = ChartAnimation.Sequenced()
72 | )
73 |
74 | HorizontalDivider()
75 |
76 | TitleText(text = "Line chart with legend")
77 | LineChartWithLegend(
78 | modifier = Modifier
79 | .height(300.dp),
80 | lineChartData = lineData,
81 | maxVerticalLines = 5,
82 | xAxisLabel = {
83 | Text(
84 | fontSize = 12.sp,
85 | text = DateTime.fromUnix(it as Long).format("yyyy-MM-dd"),
86 | textAlign = TextAlign.Center
87 | )
88 | },
89 | overlayHeaderLabel = {
90 | Text(
91 | text = DateTime.fromUnix(it as Long).format("yyyy-MM-dd"),
92 | style = MaterialTheme.typography.overline
93 | )
94 | },
95 | animation = ChartAnimation.Sequenced()
96 | )
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/line/Line.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.line
2 |
3 | import androidx.compose.ui.geometry.Size
4 | import androidx.compose.ui.graphics.Brush
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.graphics.Path
7 | import androidx.compose.ui.graphics.drawscope.DrawScope
8 | import androidx.compose.ui.graphics.drawscope.Fill
9 | import androidx.compose.ui.graphics.drawscope.Stroke
10 | import androidx.compose.ui.unit.Dp
11 | import com.netguru.multiplatform.charts.mapValueToDifferentRange
12 |
13 | internal fun DrawScope.drawLineChart(
14 | lineChartData: LineChartData,
15 | graphTopPadding: Dp,
16 | graphBottomPadding: Dp,
17 | alpha: List,
18 | ) {
19 | // calculate path
20 | val path = Path()
21 | lineChartData.series.forEachIndexed { seriesIndex, data ->
22 |
23 | val mappedPoints =
24 | mapDataToPixels(
25 | lineChartData,
26 | data,
27 | size,
28 | graphTopPadding.toPx(),
29 | graphBottomPadding.toPx()
30 | )
31 | val connectionPoints = calculateConnectionPointsForBezierCurve(mappedPoints)
32 |
33 | path.reset() // reuse path
34 | mappedPoints.forEachIndexed { index, value ->
35 | if (index == 0) {
36 | path.moveTo(value.x, value.y)
37 | } else {
38 | path.cubicTo(
39 | connectionPoints[index - 1].first.x,
40 | connectionPoints[index - 1].first.y,
41 | connectionPoints[index - 1].second.x,
42 | connectionPoints[index - 1].second.y,
43 | value.x,
44 | value.y
45 | )
46 | }
47 | }
48 |
49 | // draw line
50 | drawPath(
51 | path = path,
52 | color = data.lineColor.copy(alpha[seriesIndex]),
53 | style = Stroke(
54 | width = data.lineWidth.toPx(),
55 | pathEffect = if (data.dashedLine) dashedPathEffect else null
56 | )
57 | )
58 |
59 | // close shape and fill
60 | path.lineTo(mappedPoints.last().x, size.height)
61 | path.lineTo(mappedPoints.first().x, size.height)
62 | drawPath(
63 | path = path,
64 | Brush.verticalGradient(
65 | listOf(
66 | Color.Transparent,
67 | data.fillColor.copy(alpha[seriesIndex] / 12),
68 | data.fillColor.copy(alpha[seriesIndex] / 6)
69 | ),
70 | startY = path.getBounds().bottom,
71 | endY = path.getBounds().top,
72 | ),
73 | style = Fill
74 | )
75 | }
76 | }
77 |
78 | private fun mapDataToPixels(
79 | lineChartData: LineChartData,
80 | currentSeries: LineChartSeries,
81 | canvasSize: Size,
82 | graphTopPadding: Float = 0f,
83 | graphBottomPadding: Float,
84 | ): List {
85 | val mappedPoints = currentSeries.listOfPoints.map {
86 | val x = it.x.mapValueToDifferentRange(
87 | lineChartData.minX,
88 | lineChartData.maxX,
89 | 0L,
90 | canvasSize.width.toLong()
91 | ).toFloat()
92 | val y = it.y.mapValueToDifferentRange(
93 | lineChartData.minY,
94 | lineChartData.maxY,
95 | canvasSize.height - graphBottomPadding,
96 | graphTopPadding
97 | )
98 | PointF(x, y)
99 | }
100 |
101 | return mappedPoints
102 | }
103 |
104 | private fun calculateConnectionPointsForBezierCurve(points: List): MutableList> {
105 | val conPoint = mutableListOf>()
106 | for (i in 1 until points.size) {
107 | conPoint.add(
108 | Pair(
109 | PointF((points[i].x + points[i - 1].x) / 2f, points[i - 1].y),
110 | PointF((points[i].x + points[i - 1].x) / 2f, points[i].y)
111 | )
112 | )
113 | }
114 | return conPoint
115 | }
116 |
117 | data class PointF(val x: Float, val y: Float)
118 |
--------------------------------------------------------------------------------
/charts/src/commonMain/kotlin/com/netguru/multiplatform/charts/grid/GridChartDrawing.kt:
--------------------------------------------------------------------------------
1 | package com.netguru.multiplatform.charts.grid
2 |
3 | import androidx.compose.ui.geometry.Offset
4 | import androidx.compose.ui.graphics.Color
5 | import androidx.compose.ui.graphics.drawscope.DrawScope
6 | import androidx.compose.ui.unit.Dp
7 | import com.netguru.multiplatform.charts.grid.axisscale.XAxisScale
8 | import com.netguru.multiplatform.charts.grid.axisscale.YAxisScale
9 | import com.netguru.multiplatform.charts.mapValueToDifferentRange
10 |
11 | fun DrawScope.drawChartGrid(grid: ChartGrid, color: Color) {
12 | grid.horizontalLines.forEach {
13 | drawLine(
14 | color = color,
15 | start = Offset(0f, it.position),
16 | end = Offset(size.width, it.position),
17 | strokeWidth = 1f
18 | )
19 | }
20 | drawLine(
21 | color = color,
22 | start = Offset(0f, grid.zeroPosition.position),
23 | end = Offset(size.width, grid.zeroPosition.position),
24 | strokeWidth = 1f
25 | )
26 | grid.verticalLines.forEach {
27 | drawLine(
28 | color = color,
29 | start = Offset(it.position, 0f),
30 | end = Offset(it.position, size.height),
31 | strokeWidth = 1f
32 | )
33 | }
34 | }
35 |
36 | fun DrawScope.measureChartGrid(
37 | xAxisScale: XAxisScale,
38 | yAxisScale: YAxisScale,
39 | horizontalLinesOffset: Dp
40 | ): ChartGrid {
41 |
42 | val horizontalLines = measureHorizontalLines(
43 | axisScale = yAxisScale,
44 | startPosition = size.height,
45 | endPosition = 0f
46 | )
47 |
48 | val verticalLines = measureVerticalLines(
49 | axisScale = xAxisScale,
50 | startPosition = 0f,
51 | endPosition = size.width
52 | )
53 |
54 | val zero = when {
55 | yAxisScale.min > 0 -> yAxisScale.min
56 | yAxisScale.max < 0 -> yAxisScale.max
57 | else -> 0f
58 | }
59 | return ChartGrid(
60 | verticalLines = verticalLines,
61 | horizontalLines = horizontalLines,
62 | zeroPosition = LineParameters(
63 | zero.mapValueToDifferentRange(
64 | yAxisScale.min,
65 | yAxisScale.max,
66 | size.height,
67 | 0f
68 | ),
69 | zero
70 | )
71 | )
72 | }
73 |
74 | private fun measureHorizontalLines(
75 | axisScale: YAxisScale,
76 | startPosition: Float,
77 | endPosition: Float
78 | ): List {
79 | val horizontalLines = mutableListOf()
80 |
81 | if (axisScale.max == axisScale.min || axisScale.tick == 0f)
82 | return listOf(
83 | LineParameters(
84 | position = startPosition / 2f,
85 | value = 0
86 | )
87 | )
88 |
89 | val valueStep = axisScale.tick
90 | var currentValue = axisScale.min
91 |
92 | while (currentValue in axisScale.min..axisScale.max) {
93 | val currentPosition = currentValue.mapValueToDifferentRange(
94 | axisScale.min,
95 | axisScale.max,
96 | startPosition,
97 | endPosition
98 | )
99 | horizontalLines.add(
100 | LineParameters(
101 | position = currentPosition,
102 | value = currentValue
103 | )
104 | )
105 | currentValue += valueStep
106 | }
107 | return horizontalLines
108 | }
109 |
110 | private fun measureVerticalLines(
111 | axisScale: XAxisScale,
112 | startPosition: Float,
113 | endPosition: Float
114 | ): List {
115 | val verticalLines = mutableListOf()
116 | val valueStep = axisScale.tick
117 | var currentValue = axisScale.start
118 |
119 | while (currentValue in axisScale.min..axisScale.max) {
120 | val currentPosition = currentValue.mapValueToDifferentRange(
121 | axisScale.min,
122 | axisScale.max,
123 | startPosition,
124 | endPosition
125 | )
126 | verticalLines.add(
127 | LineParameters(
128 | position = currentPosition,
129 | value = currentValue
130 | )
131 | )
132 | currentValue += valueStep
133 | }
134 | return verticalLines
135 | }
136 |
--------------------------------------------------------------------------------
/docs/-kotlin multiplatform charts/com.netguru.multiplatform.charts.bubble/-bubble/update.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | update
5 |
6 |
7 |
8 |
14 |
15 |
16 |
17 |
23 |
24 |
update
25 |
26 |
32 |
Sources
33 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/docs/-kotlin multiplatform charts/com.netguru.multiplatform.charts.line/-point-f/x.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | x
5 |
6 |
7 |
8 |
14 |
15 |
16 |
17 |
23 |
24 |
x
25 |
26 |
32 |
Sources
33 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/docs/-kotlin multiplatform charts/com.netguru.multiplatform.charts.line/-point-f/y.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | y
5 |
6 |
7 |
8 |
14 |
15 |
16 |
17 |
23 |
24 |
y
25 |
26 |
32 |
Sources
33 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------