golang-ui / nuklear

This project provides Go bindings for nuklear.h — a small ANSI C GUI library.
https://github.com/vurtun/nuklear
MIT License
1.57k stars 98 forks source link

Making scatter plot graph #19

Closed sindbach closed 7 years ago

sindbach commented 7 years ago

I'm attempting to write an interactive scatter plot graph. Currently I don't see a chart type for it yet, hence i'm trying draw the dots/data points on a canvas. However I found difficulties in rendering many dots.

a) There are occasional glitches when trying to render many elements. My code below is mostly based on cmd/nk-example/main.go with the gfxmain() containing below loop

       if nk.NkBegin(ctx, "Title", bounds, nk.WindowTitle|nk.WindowNoScrollbar) > 0 {
        canvas := nk.NkWindowGetCanvas(ctx)
        for fy := 50; fy <= 450; fy += 10 {
            for fx := 50; fx <= 450; fx += 10 {
                c1 := nk.NkRect(float32(fx), float32(fy), 5.0, 5.0)
                nk.NkFillCircle(canvas, c1, nk.NkRgb(171, 239, 29))
            }
        }
    }
    nk.NkEnd(ctx)

I found a related vurtun/nuklear/issue 419, but failed to find the equivalent control for glDrawElements through the go binding.

b) The number of dots that can be drawn are restricted by the value of maxVertexBuffer and maxElementBuffer. The scatter plot application wouldn't know the data (how many dots would be).

Is there an example on how to solve the above points ? or perhaps advice or suggestions on what should I do ?
I'm not an expert in GL, only knows basic concepts along with some experience in Qt. I'd appreciate any help, thank you.

xlab commented 7 years ago

Hi! glDrawElements in the GL function which is called in the rendering backend. For OpenGL3 see https://github.com/golang-ui/nuklear/blob/master/nk/impl_glfw_gl3.go#L120

You can try to change GL_UNSIGNED_SHORT to GL_UNSIGNED_INT there. It's also sizeofDrawIndex const to override, as per instructions from the related issue.

The number of dots that can be drawn are restricted by the value of maxVertexBuffer and maxElementBuffer.

I had no preference about this, so I chose my reasonable defaults (i.e. randomly). Feel free to change these limits, and limiting the amount of points to sane limits based on your context.

I also would recommend to use external plotting library, which can draw to texture or even in memory ppm / RGBA data, it will be sub-1ms and you just render it as a texture (example: https://github.com/xlab/libvpx-go/blob/master/cmd/webm-player/view.go#L216)

sindbach commented 7 years ago

I also would recommend to use external plotting library, which can draw to texture or even in memory ppm / RGBA data, it will be sub-1ms and you just render it as a texture

If this is the path to take, then mapping between input and the texture (image) is needed to translate mouse click/hover? if so, a bit of a long shot, but is there an example for this ? Thank you.

xlab commented 7 years ago

Oh, I'm not suggesting options how to avoid this path completely, I still believe it's better to stick with nk itself to make interactive graphs, I was thinking about static plot chart with lots of dots.

I will try your example to see the glitch.

UPD:

but is there an example for this ?

No, I meant there was my example of rendering a custom image into a widget. The input mapping is usually made by hand, even when I did my custom widgets in Qt I was forced to handle input mapping, which was extremely unhandy, because I also had viewport with zoom levels. https://github.com/xlab/teg-workshop/blob/master/tegview/controller.go#L225-L231

There is always a problem with infinite canvases with lots of objects and GUI libraries. When I tried to do this project https://github.com/xlab/teg-workshop (see gifs) I initially started with QML scene where all elements are implementing QML Item and have MouseArea to simplify interaction and forget about user mapping. Of course with 100+ items on the table it was laggy as hell. The project in the repo is the second version.

So, on the second try, which was a complete rewrite, I used QML canvas with input mapping to the model (a.k.a. state) and a controller to handle clicks and change the state if clicks are hitting something. It's not as laggy as the first version, implements infinite canvas and the lag only comes from rendering which I chose to be Context2D instead of GL, as I didn't know the GL api well..

Im trying to say there that making interactive plots with 10000 points is not as making just 10000 buttons on X,Y axis. It won't work this way.

xlab commented 7 years ago

@sindbach Ok, I managed to resolve the problem using discussion of https://github.com/vurtun/nuklear/issues/419

STEP 1: custom widget, it's the same as yours but a widget, it seems that it renders more smoothly, not sure on this. But it's the official way to do widgets (and also suggests how to do input mapping): https://github.com/vurtun/nuklear/blob/master/nuklear.h#L2890-L2938.

func plotWidget(ctx *nk.Context) {
    canvas := nk.NkWindowGetCanvas(ctx)
    state := nk.NkWidget(nk.NewRect(), ctx)
    switch state {
    case nk.WidgetInvalid:
        return
    case nk.WidgetRom:
        // temporary state
    case nk.WidgetValid:
        // update by user input
    }
    for fy := 50; fy <= 450; fy += 10 {
        for fx := 50; fx <= 450; fx += 10 {
            c1 := nk.NkRect(float32(fx), float32(fy), 5.0, 5.0)
            nk.NkFillCircle(canvas, c1, nk.NkRgb(171, 239, 29))
        }
    }
}

STEP 2: increase vertex/element buffers, I did this (in main.go):

    maxVertexBuffer  = 8 * 1024 * 1024
    maxElementBuffer = 8 * 1024 * 1024

STEP 3: replace ushort with uint for draw index,

in nuklear.h:

--- a/nk/nuklear.h
+++ b/nk/nuklear.h
@@ -3081,7 +3081,7 @@ NK_API int nk_input_is_key_down(const struct nk_input*, enum nk_keys);
     In fact it is probably more powerful than needed but allows even more crazy
     things than this library provides by default.
 */
-typedef nk_ushort nk_draw_index;
+typedef nk_uint nk_draw_index;

in nk/types.go:

--- a/nk/types.go
+++ b/nk/types.go

 // DrawIndex type as declared in nk/nuklear.h:3084
-type DrawIndex uint16
+type DrawIndex uint32

in nk/impl_glfw_gl3.go:

--- a/nk/impl_glfw_gl3.go
+++ b/nk/impl_glfw_gl3.go
@@ -117,7 +117,7 @@ func NkPlatformRender(aa AntiAliasing, maxVertexBuffer, maxElementBuffer int) {
                                int32(clipRect.W()*state.fbScaleX),
                                int32(clipRect.H()*state.fbScaleY),
                        )
-                       gl.DrawElements(gl.TRIANGLES, int32(elemCount), gl.UNSIGNED_SHORT, unsafe.Pointer(offset))
+                       gl.DrawElements(gl.TRIANGLES, int32(elemCount), gl.UNSIGNED_INT, unsafe.Pointer(offset))

It's sad that the limit is hardcoded into nuklear.h so I can't avoid that with bindings as I can't modify the original nuklear.h for the sake of consistency. But you can fork the package and patch it. Good luck.

xlab commented 7 years ago
sindbach commented 7 years ago

Im trying to say there that making interactive plots with 10000 points is not as making just 10000 buttons on X,Y axis. It won't work this way.

Noted. Thanks.

t's sad that the limit is hardcoded into nuklear.h so I can't avoid that with bindings as I can't modify the original nuklear.h for the sake of consistency. But you can fork the package and patch it.

This is what I was wondering about, thanks for the example and diffs as well.

Good luck.

Thank you for the quick responses, and examples. This is enough to keep me going again.

For future reference, for this kind of discussion/question would you prefer others to open project issues such this? or is there a more appropriate channel ?

Thank you.

xlab commented 7 years ago

I like issues as they may be referenced and won't vanish with time.

sindbach commented 7 years ago

Thanks for the help, it works.