Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@caseykneale
Last active December 29, 2019 02:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save caseykneale/49e447f41427cfdcc1efbd681c8f6833 to your computer and use it in GitHub Desktop.
Save caseykneale/49e447f41427cfdcc1efbd681c8f6833 to your computer and use it in GitHub Desktop.
using Gtk, Plots, Cairo
using DataStructures
function replaceBiggest!(a,b)
selinds = abs.(a) .< abs.(b)
a[ selinds ] .= b[ selinds ]
end
mutable struct ClickablePlot
#plot::Plots.Plot#storing and mutating plots is expensive!
X_ref::Any #instead let's store a reference to the points in the plot...
Y_ref::Any
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,
x = plt.series_list[1].plotattributes[:x],
y = plt.series_list[1].plotattributes[:y] )
#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, pltheight )
plot_extent_x = xplotlim[2] - xplotlim[1]
plot_extent_y = yplotlim[2] - yplotlim[1]
bndedpxl = ( (x .- xplotlim[1]) ./ plot_extent_x,
(y .- yplotlim[2]) ./ -plot_extent_y )
widget_extent_x = vp_plot_area_px[2] - vp_plot_area_px[1]
widget_extent_y = vp_plot_area_px[4] - vp_plot_area_px[3]
#need to find upper Y limit of plot!
vp_plot_area_px[3] = (pltheight - vp_plot_area_px[4])
npx = [ (bndedpxl[1] .* widget_extent_x) .+ vp_plot_area_px[1],
(bndedpxl[2] .* widget_extent_y) .+ vp_plot_area_px[3] ] #
return ClickablePlot( npx[1], npx[2],
gtkcvs, vp_plot_area_px,
cairo_img, xplotlim, yplotlim,
UInt8( 0 ),
Queue{ Tuple{ Float64,Float64 } }(),
Queue{ Tuple{ Float64,Float64 } }()
)
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 )
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
mini, maxi = extrema( first.(cp.uicoords) )
selection = findall( mini .< cp.X_ref .< maxi )
for s in 2:length(selection)
move_to( ctx, cp.X_ref[selection[s]-1], cp.Y_ref[selection[s]-1] );
line_to( ctx, cp.X_ref[selection[s]], cp.Y_ref[selection[s]] );
Gtk.stroke(ctx)
end
reveal(widget)
end
else
cp.uicoords = Queue{ Tuple{ Float64,Float64 } }()
cp.plotcoords = Queue{ Tuple{ Float64,Float64 } }()
cp.clickstate = 0
end
end
end
random_stuff = sin.( 0.0 : 0.01 : ( 10pi ) )
rand_len = length(random_stuff)
random_stuff[500:1000] = random_stuff[500:1000] / 10 .- 3.0
random_stuff[2360:end] = random_stuff[2360:end] .* exp.((2360:rand_len) ./ rand_len)
a = Plots.plot(random_stuff, fmt = :png, title = "", legend = false)
click_plot = ClickablePlot( a )
attach_draw( click_plot )
attach_mouse_down( click_plot )
attach_mouse_up( click_plot )
g = GtkGrid()
g[ 1, 1 ] = GtkLabel( "Click and Drag on the Plot to select a region:" )
g[ 1, 2 ] = click_plot.canvas
w = GtkWindow( g, "Plot Viewer", 700, 500 );
Gtk.showall( w )
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment