here’s something a little cleaner… Latency doesn’t feel toooo bad. Would like some input, should probably refine a lot of things
using Gtk, Plots, Cairo, Measures
using DataStructures
function minimaxi(x, padby)
mi,ma = extrema(x)
return mi - ((ma - mi) * padby), ma + ((ma - mi) * padby)
end
function replaceBiggest!(a,b)
selinds = abs.(a) .< abs.(b)
a[ selinds ] .= b[ selinds ]
end
random_stuff = sin.( 0.0 : 0.01 : ( 10pi ) )
a = Plots.plot(random_stuff, fmt = :png, title = "", legend = false,
ylim = minimaxi(random_stuff, 0.05));
mutable struct ClickablePlot
plot::Plots.Plot
canvas::GtkCanvas
plotarea::Vector{Float64}
cairoimg::Cairo.CairoSurfaceBase{UInt32}
xplotlim::Vector{Float64}
yplotlim::Vector{Float64}
clickstate::UInt8
uicoords::Queue{ Tuple{ Float64,Float64 } }
plotcoords::Queue{ Tuple{ Float64,Float64 } }
end
function ClickablePlot( plt::Plots.Plot )
#Find most extreme axis limits in all subplots
xplotlim, yplotlim = zeros(2), zeros(2)
for subplot in a.subplots
replaceBiggest!(xplotlim, Plots.axis_limits(subplot, :x))
replaceBiggest!(yplotlim, Plots.axis_limits(subplot, :y))
end
plot_max = maximum(Plots.gr_plot_size)
vp_plot_area_px = Plots.viewport_plotarea .* plot_max
png("/tmp/wut.png") #save the plot to png
cairo_img = read_from_png("/tmp/wut.png") #load in the png to cairo
pltwidth, pltheight = cairo_img.width, cairo_img.height
c = CairoRGBSurface( pltwidth, pltheight );
cr = CairoContext( c );
set_source_surface( cr, cairo_img, 0, 0 );
gtkcvs = @GtkCanvas( pltwidth + 25, pltheight + 25 )
return ClickablePlot( plt,
gtkcvs, vp_plot_area_px,
cairo_img, xplotlim, yplotlim,
UInt8( 0 ),
Queue{ Tuple{ Float64,Float64 } }(),
Queue{ Tuple{ Float64,Float64 } }()
)
end
function make_selection_plot(cp::ClickablePlot)
x = cp.plot.series_list[1].plotattributes[:x]
y = cp.plot.series_list[1].plotattributes[:y]
mini, maxi = reduce(min, [ f for (f,l) in cp.plotcoords]), reduce(max, [ f for (f,l) in cp.plotcoords ] )
selection = mini .< x .< maxi
if length( cp.plot.series_list ) > 1
deleteat!(cp.plot.series_list, 2)
end
newplt = plot!(deepcopy(cp.plot), x[ selection ], y[ selection ],
fillrange = [ zeros( sum( selection ) ), y[ selection ] ] )
png(newplt,"/tmp/wut2.png") #save the plot to png
cairo_img = read_from_png("/tmp/wut2.png") #load in the png to cairo
pltwidth, pltheight = cairo_img.width, cairo_img.height
c = CairoRGBSurface( pltwidth, pltheight );
cr = CairoContext( c );
set_source_surface( cr, cairo_img, 0, 0 );
cp.cairoimg = cairo_img
end
function render(ctx::CairoContext, cp::ClickablePlot)
set_source_surface(ctx, cp.cairoimg, 0, 0);
paint(ctx); fill(ctx)
return nothing
end
function render_line(ctx::CairoContext, start, finish ; dash = nothing, RGB = (0,0,0) )
@assert(length(start) == 2); @assert(length(finish) == 2); @assert(length(RGB) == 3)
set_source_rgb( ctx, RGB... );
if !isa( dash, Nothing )
set_dash( ctx, dash )
end
move_to( ctx, start... );
line_to( ctx, finish... );
Gtk.stroke(ctx)
return nothing
end
function attach_draw( cp::ClickablePlot )
#Attach drawing routine
@guarded draw( cp.canvas ) do widget
ctx = getgc( cp.canvas )
render(ctx, cp)
for line in cp.uicoords
render_line(ctx, [ line[1], 0 ], [ line[1], Gtk.height( cp.cairoimg )] ;
dash = [ 8.0, 8.0 ], RGB = ( 0, 0, 0 ) )
end
end
end
#Attach mouse events
function attach_mouse_down( cp::ClickablePlot )
cp.canvas.mouse.button1press = @guarded (widget, event) -> begin
#Check bounds to ensure we are clicking inside the plot
if ( cp.plotarea[ 1 ] < event.x < cp.plotarea[ 2 ] ) &&
( cp.plotarea[ 3 ] < event.y < cp.plotarea[ 4 ] )
ctx = getgc( cp.canvas )
render(ctx, cp)
render_line(ctx, [ event.x, 0 ], [event.x, Gtk.height( cp.cairoimg )] ;
dash = [ 8.0, 8.0 ], RGB = ( 0, 0, 0 ) )
reveal(widget)
#Event coords are pixel space
npx = [ (event.x - cp.plotarea[1]) / (cp.plotarea[2] - cp.plotarea[1]),
(event.y - cp.plotarea[3]) / (cp.plotarea[4] - cp.plotarea[3]) ]
plotspace = ( cp.xplotlim[1] + ( npx[1] * (cp.xplotlim[2] - cp.xplotlim[1]) ),
cp.yplotlim[2] - ( npx[2] * (cp.yplotlim[2] - cp.yplotlim[1]) ) )
cp.uicoords = Queue{ Tuple{ Float64,Float64 } }()
cp.plotcoords = Queue{ Tuple{ Float64,Float64 } }()
enqueue!(cp.uicoords, ( event.x , event.y ) )
enqueue!(cp.plotcoords, plotspace )
#println( "\t Plot space: ", plotspace )
cp.clickstate = 1
reveal(widget)
else #reset clickstate someone clicked elsewhere
cp.clickstate = 0
end
end
#handle dragging event
cp.canvas.mouse.button1motion = @guarded (widget, event) -> begin
#Check bounds to ensure we are clicking inside the plot
if ( cp.plotarea[ 1 ] < event.x < cp.plotarea[ 2 ] ) &&
( cp.plotarea[ 3 ] < event.y < cp.plotarea[ 4 ] )
if cp.clickstate == 1
ctx = getgc( cp.canvas )
render(ctx, cp)
for line in cp.uicoords
render_line(ctx, [ line[1], 0 ], [ line[1], Gtk.height( cp.cairoimg )] ;
dash = [ 8.0, 8.0 ], RGB = ( 0, 0, 0 ) )
end
render_line(ctx, [ event.x, 0 ], [event.x, Gtk.height( cp.cairoimg )] ;
dash = [ 8.0, 8.0 ], RGB = ( 0.5, 0.5, 0.5 ) )
reveal(widget)
end
end
end
end
function attach_mouse_up( cp::ClickablePlot )
cp.canvas.mouse.button1release = @guarded (widget, event) -> begin
#Check bounds to ensure we are clicking inside the plot
if ( cp.plotarea[ 1 ] < event.x < cp.plotarea[ 2 ] ) &&
( cp.plotarea[ 3 ] < event.y < cp.plotarea[ 4 ] )
if cp.clickstate == 1
#Event coords are pixel space
npx = [ (event.x - cp.plotarea[1]) / (cp.plotarea[2] - cp.plotarea[1]),
(event.y - cp.plotarea[3]) / (cp.plotarea[4] - cp.plotarea[3]) ]
plotspace = ( cp.xplotlim[1] + ( npx[1] * (cp.xplotlim[2] - cp.xplotlim[1]) ),
cp.yplotlim[2] - ( npx[2] * (cp.yplotlim[2] - cp.yplotlim[1]) ) )
enqueue!(cp.uicoords, ( event.x , event.y ) )
enqueue!(cp.plotcoords, plotspace )
#println( "\t Plot space: ", cp.uicoords )
make_selection_plot(cp)
ctx = getgc( cp.canvas )
render(ctx, cp)
for line in cp.uicoords
render_line(ctx, [ line[1], 0 ], [ line[1], Gtk.height( cp.cairoimg )] ;
dash = [ 8.0, 8.0 ], RGB = ( 0, 0, 0 ) )
end
reveal(widget)
end
else
cp.uicoords = Queue{ Tuple{ Float64,Float64 } }()
cp.plotcoords = Queue{ Tuple{ Float64,Float64 } }()
cp.clickstate = 0
end
end
end
cp = ClickablePlot( a )
attach_draw( cp )
attach_mouse_down( cp )
attach_mouse_up( cp )
g = GtkGrid()
g[ 1, 1 ] = GtkLabel( "Look a plot!" )
g[ 2, 2 ] = cp.canvas
w = GtkWindow( g, "Plot Viewer", 700, 500 );
Gtk.showall( w )