Skip to content

Instantly share code, notes, and snippets.

@Vindaar
Last active September 9, 2023 16:14
Show Gist options
  • Save Vindaar/97296bcc5ec12f1630187c1db1de11ea to your computer and use it in GitHub Desktop.
Save Vindaar/97296bcc5ec12f1630187c1db1de11ea to your computer and use it in GitHub Desktop.
Embedding ggplotnim in SDL2
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)
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