KoalaPlot / koalaplot-core

Koala Plot is a Compose Multiplatform based charting and plotting library written in Kotlin
https://koalaplot.github.io/
MIT License
392 stars 18 forks source link

Exception thrown for XYGraph: Minimum tick spacing must be greater than 0 and less than or equal to 1 #42

Closed sunny-chung closed 7 months ago

sunny-chung commented 7 months ago

I got an exception when trying to display a XYGraph. I thought it was related to empty data, so I ensure there are some data but still in vain. How to avoid errors to display the chart correctly?

Version: 0.5.2 Target Platform: Desktop

Stacktrace:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Minimum tick spacing must be greater than 0 and less than or equal to 1
    at io.github.koalaplot.core.xygraph.LinearAxisModel.computeMajorTickSpacing(LinearAxisModel.kt:104)
    at io.github.koalaplot.core.xygraph.LinearAxisModel.computeMajorTickValues(LinearAxisModel.kt:74)
    at io.github.koalaplot.core.xygraph.LinearAxisModel.computeTickValues-0680j_4(LinearAxisModel.kt:92)
    at io.github.koalaplot.core.xygraph.AxisDelegate$Companion.createAxis-eqLRuRQ(AxisDelegate.kt:95)
    at io.github.koalaplot.core.xygraph.AxisDelegate$Companion.createVerticalAxis-wH6b6FI(AxisDelegate.kt:82)
    at io.github.koalaplot.core.xygraph.XYGraphKt$XYGraph$1$1.invoke-0kLqBqw(XYGraph.kt:119)
    at io.github.koalaplot.core.xygraph.XYGraphKt$XYGraph$1$1.invoke(XYGraph.kt:98)
    at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s(SubcomposeLayout.kt:709)
    at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:252)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:251)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2304)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:504)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:260)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1617)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:620)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:596)
    at androidx.compose.foundation.layout.BoxMeasurePolicy.measure-3p2s80s(Box.kt:122)
    at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:252)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:251)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2304)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:504)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:260)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1617)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:620)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:596)
    at io.github.koalaplot.core.util.HoverableElementAreaKt$HoverableElementArea$2$1.measure-3p2s80s(HoverableElementArea.kt:85)
    at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:252)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:251)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2304)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:504)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:260)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1617)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:620)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:596)
    at androidx.compose.foundation.layout.BoxMeasurePolicy.measure-3p2s80s(Box.kt:122)
    at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:252)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:251)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2304)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:504)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:260)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1617)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:620)
    at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui(LayoutNode.kt:1145)
    at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui$default(LayoutNode.kt:1136)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:356)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:514)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.onlyRemeasureIfScheduled(MeasureAndLayoutDelegate.kt:598)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:624)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:631)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:631)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:631)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:631)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:631)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtree(MeasureAndLayoutDelegate.kt:587)
    at androidx.compose.ui.node.RootNodeOwner$OwnerImpl.forceMeasureTheSubtree(RootNodeOwner.skiko.kt:308)
    at androidx.compose.ui.node.Owner.forceMeasureTheSubtree$default(Owner.kt:239)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:632)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:596)
    at org.jetbrains.compose.splitpane.DesktopSplitPaneKt$SplitPane$5.measure-3p2s80s(DesktopSplitPane.kt:100)
    at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:252)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:251)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2304)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:504)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:260)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1617)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:620)
    at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui(LayoutNode.kt:1145)
    at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui$default(LayoutNode.kt:1136)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:356)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:514)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.onlyRemeasureIfScheduled(MeasureAndLayoutDelegate.kt:598)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:624)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtree(MeasureAndLayoutDelegate.kt:587)
    at androidx.compose.ui.node.RootNodeOwner$OwnerImpl.forceMeasureTheSubtree(RootNodeOwner.skiko.kt:308)
    at androidx.compose.ui.node.Owner.forceMeasureTheSubtree$default(Owner.kt:239)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:632)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0(LayoutNodeLayoutDelegate.kt:596)
    at org.jetbrains.compose.splitpane.DesktopSplitPaneKt$SplitPane$5.measure-3p2s80s(DesktopSplitPane.kt:100)
    at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:252)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:251)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2304)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:504)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:260)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1617)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
    at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:620)
    at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui(LayoutNode.kt:1145)
    at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui$default(LayoutNode.kt:1136)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:356)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:514)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded$default(MeasureAndLayoutDelegate.kt:491)
    at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout(MeasureAndLayoutDelegate.kt:377)
    at androidx.compose.ui.node.RootNodeOwner$OwnerImpl.measureAndLayout(RootNodeOwner.skiko.kt:290)
    at androidx.compose.ui.node.RootNodeOwner.measureAndLayout(RootNodeOwner.skiko.kt:187)
    at androidx.compose.ui.scene.MultiLayerComposeSceneImpl.measureAndLayout(MultiLayerComposeScene.skiko.kt:247)
    at androidx.compose.ui.scene.BaseComposeScene.doLayout(BaseComposeScene.skiko.kt:225)
    at androidx.compose.ui.scene.BaseComposeScene.access$doLayout(BaseComposeScene.skiko.kt:51)
    at androidx.compose.ui.scene.BaseComposeScene.render(BaseComposeScene.skiko.kt:164)
    at androidx.compose.ui.scene.ComposeSceneMediator$DesktopSkikoView.onRender(ComposeSceneMediator.desktop.kt:490)
    at org.jetbrains.skiko.SkiaLayer.update$skiko(SkiaLayer.awt.kt:548)
    at org.jetbrains.skiko.redrawer.AWTRedrawer.update(AWTRedrawer.kt:54)
    at org.jetbrains.skiko.redrawer.MetalRedrawer$frameDispatcher$1.invokeSuspend(MetalRedrawer.kt:82)
    at org.jetbrains.skiko.redrawer.MetalRedrawer$frameDispatcher$1.invoke(MetalRedrawer.kt)
    at org.jetbrains.skiko.redrawer.MetalRedrawer$frameDispatcher$1.invoke(MetalRedrawer.kt)
    at org.jetbrains.skiko.FrameDispatcher$job$1.invokeSuspend(FrameDispatcher.kt:33)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:773)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
    Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@570805d2, SwingDispatcher@6d583e10]

Reproducer:

ChartLayout(title = { AppText("Latencies over Time (95%)") }) {
    latenciesMsOverTime.takeIf { it.size >= 2 }?.let { latenciesMsOverTime ->
        XYGraph(
            xAxisModel = CategoryAxisModel(
                latenciesMsOverTime.keys.toList().ifEmpty { listOf(KInstant.now().toMilliseconds()) }),
            yAxisModel = LinearAxisModel(
                range = 0f..6000f,
            ),
            xAxisLabels = { KZonedInstant(it, KZoneOffset.local()).format("HH:mm:ss") },
            xAxisTitle = "Time",
            yAxisTitle = "Latency (ms)",
        ) {
            latenciesMsOverTime.forEach { (timestamp, result) ->
                DefaultPoint(timestamp, result.at95Percent)
            }
        }
    }
}
gsteckman commented 7 months ago

I can't run this without your data. But are you putting the graph in a vertically scrollable area without otherwise restricting the height of the graph, e.g. by using a modifier?

sunny-chung commented 7 months ago

Yes. Is this not supported?

gsteckman commented 7 months ago

The graph will expand to consume all available space, so if you put it in a scrollable column the vertical space is infinite - and by inspecting the code I think that will lead to what you are seeing (I didn't try it but there's a calculation that divides by the available space to find the relative tick spacing, which would result in it being 0 - in any case you probably don't want an infinitely tall graph).

If you use a Modifier to set a max height or aspect ratio, this exception will likely go away.

On Mon, Feb 26, 2024 at 7:33 PM sunny-chung @.***> wrote:

Yes. Is this not supported?

— Reply to this email directly, view it on GitHub https://github.com/KoalaPlot/koalaplot-core/issues/42#issuecomment-1965725769, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGOGXZGYXJDJ2OSPWRMSMILYVVHZNAVCNFSM6AAAAABD2UCZLOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSNRVG4ZDKNZWHE . You are receiving this because you commented.Message ID: @.***>

sunny-chung commented 7 months ago

Thanks for the prompt response. I overcame this error, and encountered more errors.

Then, I took an hour to figure out how to reduce minor grid lines.

I encountered an exception when I try to reduce number of grid lines in x-axis by reducing number of items in CategoryAxisModel.

And then, I changed to use LinearAxisModel, which only accepts Float values. My data are timestamps, so they are in Long. I keep encountering an error IllegalArgumentException: Axis range end (1.70904125E12) must be greater than start (1.70904125E12), even if I hardcoded things in the below code. Because x - 2000 and x has the same value after converting to Float, which is a low-precision data type.

                val pseudoPoints = points.ifEmpty {
                    val now = KInstant.now().toMilliseconds()
                    listOf(DefaultPoint(now - 2000, 0f), DefaultPoint(now, 0f))
                }.let {
                    if (it.size == 1) {
                        listOf(DefaultPoint(it.first().x - 2000, it.first().y), it.first())
                    } else {
                        it
                    }
                }

                val floatPoints = points.map { DefaultPoint(it.x.toFloat(), it.y) }
                XYGraph(
                    xAxisModel = LinearAxisModel(
                        pseudoPoints.first().x.toFloat() .. pseudoPoints.last().x.toFloat(),
                        minorTickCount = 0,
                        minimumMajorTickIncrement = 2000f,
                    ),
                    yAxisModel = LinearAxisModel(
                        pseudoPoints.autoScaleYRange(),
                        minorTickCount = 2,
                    ),

And autoScaleXRange() is only available for Float values.

Even if there is no IllegalArgumentException throwing, I would have to deal with precision loss issues raised by conversion from Long to Float and then back to Long.

This is a great library. I like the customizable styles and designs even though they are deeply buried and not documented. The look and feel is also okay. Also I like the compose-style APIs, which cannot be found in other alternatives. I wish the documentation on getting started could be better, the library could provide more friendly APIs and not coupled to one low-precision inflexible data type. Many of the exceptions I encountered can be avoided with a few lines of documentation. I may have to look for alternatives due to the floating point issues.

Please correct me if I did not understand the library well, so that I can still explore this library.

gsteckman commented 7 months ago

Thanks for the feedback, this is useful for helping to make the library better and understanding how people want to use it. Have you seen the documentation here https://koalaplot.github.io/docs/? If specific things are missing in the documentation that caused you trouble, please let me know so we can add more information on those topics.

With regards to the data types, that is something that should be fairly easy to add - I'll look into it. I was using Float myself, and and didn't create versions for every data type and was waiting for interest from other users. Since the Number class has no arithmetic operators, it's not as straightforward as just adding a generic type to the existing implementation.

The ClassCastException looks like a bug. The xychart.* classes/functions are deprecated and I plan to remove them in 0.6.0 - I'll do that soon.

Autoscaling will need at least 2 points to compute a scale. I don't know of a way to get the compiler to enforce that.

sunny-chung commented 7 months ago

Glad to hear you will look into the data types! I can't wait to see your updates.

For the term "documentation" I used, I am referring to https://koalaplot.github.io/docs/ and javadocs.

  1. It does not mention autoScaleXRange() requires an non-empty list.

But I think it is an API design rather than a documentation issue. Perhaps autoScaleXRangeOrElse(default: ClosedFloatRange) to handle exceptional cases.

  1. ClassCastException

I expect import statements with full package names should be specified in the examples in documentation when there are multiple classes with the same names. It would be cool if you would remove the duplicates, but not specifying deprecated stuffs and not annotating with @Deprecated is also an issue.

  1. Reduce number of minor grid lines

Nowhere I could find in the documentation. I expect it is inside "Getting Started", "XY Graphs", or a dedicated section of "Look and feel". Documentation did mention "removing minor ticks", but that is different from "decreasing number of minor grid lines".

  1. autoScaleXRange() is only available for Float values.

Also not in documentation.

  1. Minimum tick spacing must be greater than 0 and less than or equal to 1

I expect "graph cannot be wrapped in an infinite scrollable container" or "maximum height needs to be limited" is documented somewhere near "Getting Started" or in README. Users seeing this limitation would probably not encounter this exception.

For javadocs issue, I expect there are descriptions for the parameters of rememberAxisStyle(), rather than guessing from the outcomes. Nevertheless, I can see there are javadocs for most of the classes and functions I used in KoalaPlot, which are good.

Since the Number class has no arithmetic operators

Yes, this is one of the limitation in Kotlin. There are type conversions in arithmetics. But I believe custom (operator) functions can be written, as the number of subclasses are discrete, and we don't have to handle all the possibilties. For example, this is working:

operator fun Number.plus(another: Number): Number {
    if (this is Int && another is Int) {
        println("working")
        return this + another
    }
    return 0
}

fun main() {
    val a: Number = 1
    val b: Number = 2
    println(a + b)
}

It would be great if BigDecimal / BigNumber can be supported as well. I encountered these data types in some projects. But it is not multiplatform, and I have not yet seen a good alternative multiplatform implementation.

gsteckman commented 7 months ago

I just pushed a change adding LongLinearAxisModel (as well as for other data types) and addressing some of the other issues. It's not been heavily tested so give it a try and if you encounter bugs please open a new issue for each one.

The minor grid lines are tied to the minor ticks - there is 1 minor grid line for each minor tick. If you change the number of minor ticks it also changes the number of minor grid lines. I don't think I've ever seen a graph where the number of minor grid lines was different than the number of minor ticks (you can also change the length of minor ticks and set it to 0 if you don't want them to show, in which case you'll only see a change in the number of minor grid lines). If you have an example of what you are trying to achieve I'll take a look at it.