Last active
September 9, 2023 16:14
-
-
Save Vindaar/97296bcc5ec12f1630187c1db1de11ea to your computer and use it in GitHub Desktop.
Embedding ggplotnim in SDL2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import datamancer | |
import std / [math, complex] | |
const xn = 960 | |
const yn = 960 | |
const xmin = -2.0 | |
const xmax = 0.6 | |
const ymin = -1.5 | |
const ymax = 1.5 | |
const MAX_ITERS = 200 | |
proc mandelbrot_kernel(c: Complex[float]): int = | |
var z = c | |
for i in 0 ..< MAX_ITERS: | |
z = z * z + c | |
if abs2(z) > 4: | |
return i | |
result = MAX_ITERS | |
proc compute_mandelbrot*(): Tensor[int] = | |
result = zeros[int]([yn, xn]) | |
let x_range = linspace(xmin, xmax, xn) | |
let y_range = linspace(ymin, ymax, xn) | |
for j in 0 ..< yn: | |
for i in 0 ..< xn: | |
let x = x_range[i] | |
let y = y_range[j] | |
result[j, i] = mandelbrot_kernel(complex(x, y)) | |
proc tensor2DtoDf*(t: Tensor[float]): DataFrame = | |
var xs = newSeq[int](t.size) | |
var ys = newSeq[int](t.size) | |
var cs = newSeq[float](t.size) | |
var idx = 0 | |
for y in 0 ..< t.shape[0]: | |
for x in 0 ..< t.shape[1]: | |
xs[idx] = x | |
ys[idx] = y | |
cs[idx] = t[y, x] | |
inc idx | |
result = toDf(xs, ys, cs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import std / [math, os] | |
import sdl2 except Color, Point | |
import ggplotnim, ginger | |
import cairo | |
proc draw*(renderer: RendererPtr, sdlSurface: SurfacePtr, view: Viewport, filename: string, texOptions: TeXOptions = TeXOptions()) = | |
var img = initBImage(CairoBackend, | |
filename, | |
width = view.wImg.val.round.int, height = view.hImg.val.round.int, | |
ftype = fkSvg, | |
texOptions = texOptions) | |
## XXX: overwrite `img.surface` by | |
img.backend.cCanvas = image_surface_create(cast[cstring](sdlSurface.pixels), | |
FORMAT_RGB24, | |
sdl_surface.w, | |
sdl_surface.h, | |
sdl_surface.pitch) | |
img.draw(view) | |
#img.backend.ctx.paint() | |
#img.destroy() | |
let texture = createTextureFromSurface(renderer, sdl_surface) | |
freeSurface(sdl_surface) | |
# copy the surface to the renderer | |
copy(renderer, texture, nil, nil) | |
#[ | |
1. when update in while true loop, update coordinates based on zoom etc | |
2. produce ggplot using `ggcreate` | |
3. pass surface pointer to `draw` | |
4. copy back to SDL? Needed? probably not. | |
]# | |
type | |
Context = object | |
df: DataFrame | |
yMax = 200.0 | |
xn = 960 | |
yn = 960 | |
sizesSet = false | |
requireRedraw = true | |
xScale: ginger.Scale | |
yScale: ginger.Scale | |
plotOrigin: Point | |
plotWidth: int | |
plotHeight: int | |
zoomStart: Point | |
zoomEnd: Point | |
proc calcNewScale(orig: float, length: int, startIn, stopIn: float, scale: ginger.Scale): ginger.Scale = | |
let start = min(startIn, stopIn).float | |
let stop = max(startIn, stopIn).float | |
let length = length.float | |
if start >= orig and start <= orig + length: | |
## Inside the plot | |
let stop = clamp(stop, start, orig + length) | |
let rel1 = (start - orig) / length | |
let rel2 = (stop - orig) / length | |
let dif = (scale.high - scale.low) | |
result = (low: dif * rel1 + scale.low, high: dif * rel2 + scale.low) | |
else: | |
result = scale | |
proc calcNewScale(ctx: Context, axis: AxisKind): ginger.Scale = | |
if ctx.sizesSet and ctx.zoomStart != ctx.zoomEnd: | |
case axis | |
of akX: result = calcNewScale(ctx.plotOrigin[0], ctx.plotWidth, ctx.zoomStart[0], ctx.zoomEnd[0], ctx.xScale) | |
of akY: | |
result = calcNewScale(ctx.plotOrigin[1], ctx.plotHeight, ctx.zoomStart[1], ctx.zoomEnd[1], ctx.yScale) | |
# now invert | |
result = (low: ctx.yn.float - result.high, high: ctx.yn.float - result.low) | |
else: | |
case axis | |
of akX: result = (low: 0.float, high: ctx.xn.float) | |
of akY: result = (low: 0.float, high: ctx.yn.float) | |
proc renderPlot(ctx: var Context, renderer: RendererPtr, surface: SurfacePtr) = | |
if ctx.requireRedraw: | |
let xScale = calcNewScale(ctx, akX) | |
let yScale = if ctx.sizesSet: calcNewScale(ctx, akY) | |
else: (low: 0.float, high: ctx.yn.float) | |
echo "New xscale: ", xScale, " to ", yScale, " from ", ctx.zoomStart, " and ", ctx.zoomEnd | |
let plt = ggcreate( | |
ggplot(ctx.df, aes("xs", "ys", fill = "cs"), backend = bkCairo) + | |
geom_raster() + | |
scale_fill_continuous(scale = (low: 0.0, high: ctx.yMax)) + | |
xlim(xScale[0].int, xScale[1].int) + ylim(yScale[0].int, yScale[1].int) + | |
margin(top = 1.5), | |
640.0, 480.0) | |
if not ctx.sizesSet: | |
## 1. get coordinates of the plot viewport by converting `origin` to ukPoint, same width/height | |
## 2. determine if zoom inside/outside | |
## 3. convert relative zoom in x / y of total scale to ginger.scale using xScale and yScale | |
## 4. add a `xlim`, `ylim` to call | |
for ch in plt.view: | |
if ch.name == "plot": | |
ctx.xScale = ch.xScale | |
ctx.yScale = ch.yScale | |
let orig = ch.origin.to(ukPoint, absWidth = some(ch.wImg), absHeight = some(ch.hImg)) | |
ctx.plotOrigin = (orig.x.pos, orig.y.pos) | |
ctx.plotWidth = (times(ch.wView, ch.width)).toPoints().val.round.int | |
ctx.plotHeight = (times(ch.hView, ch.height)).toPoints().val.round.int | |
ctx.sizesSet = true | |
echo ctx | |
renderer.draw(surface, plt.view, "") | |
ctx.requireRedraw = false | |
import ./mandelbrot | |
proc initContext(): Context = | |
let t = compute_mandelbrot().asType(float) | |
result = Context(df: tensor2DtoDf(t)) | |
proc render(width, height: int) = | |
discard sdl2.init(INIT_EVERYTHING) | |
var screen = sdl2.createWindow("Interactive".cstring, | |
SDL_WINDOWPOS_UNDEFINED, | |
SDL_WINDOWPOS_UNDEFINED, | |
width.cint, height.cint, | |
SDL_WINDOW_OPENGL); | |
var renderer = sdl2.createRenderer(screen, -1, 1) | |
if screen.isNil: | |
quit($sdl2.getError()) | |
var quit = false | |
var event = sdl2.defaultEvent | |
var window = sdl2.getsurface(screen) | |
var ctx = initContext() | |
while not quit: | |
var anyEvents = false | |
while pollEvent(event): | |
anyEvents = true | |
case event.kind | |
of QuitEvent: | |
quit = true | |
of KeyDown: | |
const dist = 1.0 | |
case event.key.keysym.scancode | |
of SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_A, SDL_SCANCODE_D: | |
discard | |
else: discard | |
of MousebuttonDown: | |
## activate relative mouse motion | |
let ev = evMouseButton(event) | |
echo "Clicked at: ", (ev.x, ev.y) | |
ctx.zoomStart = (ev.x.float, ev.y.float) | |
ctx.zoomEnd = (ev.x.float, ev.y.float) | |
of MousebuttonUp: | |
## activate relative mouse motion | |
let ev = evMouseButton(event) | |
echo "Liftet at: ", (ev.x, ev.y) | |
ctx.zoomEnd = (ev.x.float, ev.y.float) | |
ctx.requireRedraw = true | |
of WindowEvent: | |
freeSurface(window) | |
window = sdl2.getsurface(screen) | |
of MouseMotion: | |
discard | |
else: echo event.kind | |
#discard lockSurface(window) | |
## rendering of this frame | |
renderPlot(ctx, renderer, window) | |
#unlockSurface(window) | |
#sdl2.clear(arg.renderer) | |
sdl2.present(renderer) | |
if not anyEvents: | |
sleep(10) | |
sdl2.quit() | |
proc main = | |
render(640, 480) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment