hi guys, I was wondering if there is a way to save...
# tornadofx
e
hi guys, I was wondering if there is a way to save the resulting graph into a file or an image
e
uhm, let me ask you then
what are the best tools/libs in order to create a scatter graph and save it to a file/image?
because I'm wondering if tornado is the right choice for me or not
r
I don't know. It probably depends on a number of things, including where you're getting your data, what format it's in, what you expect the output to be, etc.
e
I'll create all the data, the format is up to me
output is the image
r
Then go with whatever tool you find most comfortable. Most spreadsheet software will be able to do it, as will many number/data libraries in various languages.
e
I'd love to stay on kotlin, so far I only found some javafx resources..
r
If your sole purpose is to create a picture, TornadoFX (or any UI framework or library for that matter) is not the right tool for the job. It's a UI frameworks after all.
You are aware TornadoFX is fundamentally a Kotlin wrapper around JavaFX, right? You can do anything in TornadoFX you can in JavaFX.
If you create the graph in TonradoFX, you can use the JavaFX
Node.snapshot(...)
function to generate an image of the graph (everything in JavaFX is a
Node
, including graphs)
e
I wasnt aware of that
thanks for the heads up
r
Ah, sorry. I thought that was general knowledge. That's one of the main selling points of TornadoFX: you get to use JavaFX in a more idiomatic Kotlin way, with some additional features fore awesomeness.
e
I got this code running atm
what shall I call
snapshot
on?
r
In your case, it would be the
root
of
MyView
(which is a JavaFX
ScatterChart
)
(You can either use some sort of button to take the snapshot when pressed, or use some sort of delay or callback to wait for the screen to be fully rendered).
Or just add it to the onClick of the graph itself
e
no, I'd need to be automatized
snapshot
looks great in this regards
I'm just having issue because the image seems like a very small angle of the whole graph
r
What do you mean by "a very small angle"? How are you setting the width and height of the resulting image?
e
added this on
root
Copy code
.apply {
        animated = false
        applyCss()
        layout()

        val image = snapshot(SnapshotParameters(), WritableImage(1000, 700))

        val file = File("chart.png")

        ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file)
    }
r
Why not just use
snapshot(null, WritableImage(width, height))
?
e
and this was my result https://imgur.com/2Vc8HCA
width
and
height
appears to be both
0.0
..
r
Than you may not waiting long enough for the scene to get fully drawn before taking the snapshot
e
however hardcoding value changes nothing from the pic I showed you
r
Here's what I used, and it seems to work great on click:
Copy code
class MyView : View() {
    override val root = scatterchart("WBCC Clustering by Age", NumberAxis(), NumberAxis()) {
        patients.multiKMeansCluster(k = 3,
            maxIterations = 10000,
            trialCount = 50,
            xSelector = { it.age.toDouble() },
            ySelector = { it.whiteBloodCellCount.toDouble() }
        ).forEachIndexed { index, centroid ->
            series("Group ${index + 1}") {
                centroid.points.forEach {
                    data(it.age, it.whiteBloodCellCount)
                }
            }
        }
        setOnMouseClicked {
            val image = snapshot(null, WritableImage(width.toInt(), height.toInt()))
            val file = File("chart.png")
            ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file)
        }
    }
}
e
it looks like indeed it doesnt wait on my case
r
That's where this whole thing falls through. JavaFX wasn't designed for automated running. It was designed to create user interfaces. Timing is a completely different beast with user interaction than it is with automated running, and JavaFX was built with that in mind.
e
A
Thread.sleep
doesnt seem useful
the whole rendering is simply delayed
r
You need to delay separately. You could try using a
pause
.
e
it did it
thanks dear
r
Good to hear
Actually, you may be able to get away with just a
runLater
e
let me try
you master, sir 😛
r
This seems to work for me, with just a flash on the screen:
Copy code
runLater {
    val image = snapshot(null, WritableImage(width.toInt(), height.toInt()))
    val file = File("chart.png")
    ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file)
    primaryStage.close()
}
e
why
primaryStage.close()
?
r
Just for fun, sorry. I was messing around to see how the timing worked.
e
😄
r
If you're data takes time to load though, you may just get an empty graph with
runLater
.
I'm not sure...
e
I'll play with it
r
Ah, I noticed the axis labels don't show. They must get populated through a callback.
e
I see them
using
runLater {
try to explicitely add a
Duration
r
Interesting. I see the whole graph with data, but no axis labels.
Add a duration to
runLater
?
e
runLater(Duration) {
r
Ah, yet another bit of TornadoFX magic 🙂
e
🐿️