[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
plot object
Hey,
after my last silly question, thought I'd try to contribute something
semi-useful for a change. So here's a generic plotting object that
I've been using a bit lately.
The idea is to set a bunch of properties of the plot, including the
data, and then tell it to draw itself. There are some advantages to
this approach. You can fiddle with the properties without having to
enter the entire plot command every time. The legend will draw itself
semi-automatically. And if you want to select points based on where
they lie on the plot, it's easy--there's one routine to select a polygon
region and another to return the points within that region.
I find that this object is most useful in plots of intermediate
formality. If I just want a simple, one-off plot for my one purposes
it's easier just to enter the plot command directly, usually adding
extra annotations with pencil. If I want the plot to go in a paper
then I'd better save it in a routine. But if I want to show it to
others in my research group, but not necessarily publish it, the
plotting object seems to work the best for me. Maybe that's because
I tend to tinker with plots.
I'm sure many people have written things like this. E.g., David
F. wrote about one back in his long object-advocacy post. Anyone want
to send theirs in? If we could get as many submissions as with the
did with the "read tabular data" problem we could probably wind up
with something pretty cool. Or if you have suggestions for my code
please let me know.
Words of warning:
This is all recently written, and isn't production quality. I'm only
submitting it to spur discussion.
The legend uses my slightly modified version of David Windt's legend.pro,
which I call legend_w. Feel free to replace it with your favorite
legend-constructor.
The routine to pick the polygon region is from the JHU library.
The routine PNPOLY to figure out which points are in the polygon is
basically copied from the comp.graphics.algorithms FAQ. I can post it
if you want.
Mark Fardal
UMass
************************************************************************
;+
; NAME
; OBJPLOT__DEFINE
;
; PURPOSE: Defines an OBJPLOT object, which records the data and
; plotting style for a plot. A legend can be added easily. Sets of
; points can be selected with the mouse. The plotting is done in
; the direct graphics system.
;
; AUTHOR:
; Mark Fardal
; UMass
;
; CATEGORY:
; Graphics
;
; CALLING SEQUENCE:
; p = obj_new('objplot')
;
; INPUTS:
; Input to the program is done through the object methods
; SET, ADDDATA, CHANGEDATA.
;
; OBJECT METHODS:
; set the plot properties
; SET, title=title, xtitle=xtitle, ytitle=ytitle, tfont=tfont,
; polycolor=polycolor
; add a x-y pair, with optional properties
; ADDDATA, x, y, name=name, psym=psym, symbol=symbol,
; line=line, thick=thick, color=color
; shorthand for ADDDATA, x(*,ix), y(*,ix)
; ADDCOL, x, ix, iy
; change data or plotting properties
; CHANGEDATA, x, y, key=key, name=name, psym=psym,
; symbol=symbol, line=line, thick=thick, color=color
; remove one or all datasets
; ZAPDATA, key=key
; grab region on the screen
; GRAB
; return the points so selected, for dataset "key"
; function SELECTED, key
; draw the plot
; DRAW, drawpoly=drawpoly, _extra=e
; add a legend
; ADDLEGEND, position=position, order=order, _extra=e
;
; OUTPUTS:
; none
;
; SIDE EFFECTS:
; headaches, nausea, vomiting
;
; RESTRICTIONS:
; need the routines pnpoly, drawpoly, and legend_w to be in path
; needs IDL 5.0 at least, only tested on 5.1
;
; EXAMPLE:
; A random example:
;
; IDL> x=findgen(100)/5.
; IDL> y=sin(x)
; IDL> p=obj_new('objplot')
; IDL> p->adddata, x, y, psym=6
; IDL> p->draw
; IDL> p->changedata, color=!sky
; IDL> p->adddata, x+5, y, color=!red
; IDL> p->draw
; IDL> p->grab
; % Compiled module: DRAWPOLY.
;
; Draw a polygon using the mouse.
; Left button --- new side.
; Middle button --- delete side.
; Right button --- quit polygon.
;
; IDL> print, p->selected(0)
; % Compiled module: PNPOLY.
; 48 62
; etc...
;
; MODIFICATION HISTORY:
; original version (still evolving) 11/1/99
; BUGS:
; inadequately tested
; should store more plot properties
; usage of key inconsistent, sometimes argument and sometimes keyword
; methods to add:
; get (get property)
; shuffle (swap order of datasets)
; zap (reset all properties)
; ACKNOWLEDGEMENTS:
; J.D. Smith suggested the use of arrays of structures for the data
;
;utility routine for plotting specific colors sensibly
function objplot::checkcolor, color
result = color
fgset = where(color eq -1, count)
if (count gt 0) then result(fgset) = !p.color
return, result
end
;set one or more of the plot properties
pro objplot::set, $
title=title, $
xtitle=xtitle, $
ytitle=ytitle, $
tfont=tfont, $
polycolor=polycolor
if n_elements(title) ne 0 then self.title = title
if n_elements(xtitle) ne 0 then self.xtitle = xtitle
if n_elements(ytitle) ne 0 then self.ytitle = ytitle
if n_elements(tfont) ne 0 then self.tfont = tfont
if n_elements(polycolor) ne 0 then self.polycolor = polycolor
end
;select region on screen
pro objplot::grab
if (n_elements(key) eq 0) then key = 0
drawpoly, xp, yp, /data
if (n_elements(xp) lt 3) then message, 'Need >= 3 vertices for polygon.'
ptr_free, self.xpoly, self.ypoly
self.xpoly = ptr_new(xp)
self.ypoly = ptr_new(yp)
end
;get the points in dataset "key" selected by the current poly region
;or -1 if no elements selected
function objplot::selected, key
nd = n_elements(*self.data)
if n_elements(key) ne 1 then begin
if (n_elements(key) eq 0 and nd eq 1) then key = 1 $
else message, 'Have to select one dataset.'
endif
if (key lt 0 or key ge nd) then $
message, 'Dataset index out of range.'
if (self.xpoly eq ptr_new()) then $
message, 'Region not defined--use grab first.'
inpol = pnpoly(*self.xpoly, *self.ypoly, $
*(*self.data)[key].x, *(*self.data)[key].y)
inlist = where(inpol gt 0, count)
if (count eq 0) then print, 'No points currently selected.'
return, inlist
end
;add a dataset drawn from two columns of an array
pro objplot::addcol, x, ix, iy, _extra=e
if n_params() lt 3 then message, 'Not enough parameters.'
self->adddata, x(*,ix), x(*,iy), _extra=e
end
;add a dataset to the plot object. It will come after all the
;previous datasets
pro objplot::adddata, x, y, name=name, psym=psym, symbol=symbol, $
line=line, thick=thick, color=color
if n_elements(name) eq 0 then name=''
if n_elements(psym) eq 0 then psym=0
if n_elements(symbol) eq 0 then symbol=0
if n_elements(line) eq 0 then line=0
if n_elements(thick) eq 0 then thick=0
if n_elements(color) eq 0 then color=-1 ;turned into !p.color at plotting
pair = {plotpair, $
name:name, $
x:ptr_new(x), $
y:ptr_new(y), $
psym:psym, $
symbol:symbol, $
line:line, $
thick:thick, $
color:color $
}
if ptr_valid(self.data) then begin
*self.data = [*self.data, pair]
endif else begin
self.data = ptr_new([pair])
endelse
end
;get rid of one or all datasets
pro objplot::zapdata, key=key
if n_elements(key) eq 1 then begin
nd = n_elements(*self.data)
if (key ge nd) then message, 'Sorry, no such dataset.'
keep = where(indgen(nd) ne key)
tmp = (*self.data)[keep]
ptr_free, self.data
self.data = ptr_new(tmp)
return
endif else if n_elements(key) eq 1 then begin
ptr_free, (*self.data).x, (*self.data).y
ptr_free, self.data
self.data = ptr_new()
; ptr_free, (*self.data)[i].x, (*self.data)[i].y
; *(*self.data) = *(*self.data)
endif else begin
print, 'Sorry, have to zap one at a time.'
endelse
end
;change properties of the dataset specified
pro objplot::changedata, x, y, key=key, name=name, $
psym=psym, symbol=symbol, line=line, thick=thick, color=color
nd = n_elements(*self.data)
if n_elements(key) eq 0 then begin
if (nd eq 1) then key = 0 else begin
print, 'Please specify dataset to change:'
key = 0
read, key
endelse
endif
if not ptr_valid(self.data) then return
if key ge nd then $
message, 'Sorry, no such dataset: ' + string(key)
;pair = (*self.data)[key]
;data = *self.data
if n_params() ge 1 then begin
ptr_free, (*self.data)[key].x
(*self.data)[key].x = ptr_new(x)
endif
if n_params() ge 2 then begin
ptr_free, (*self.data)[key].y
(*self.data)[key].y = ptr_new(y)
endif
if n_elements(name) ne 0 then (*self.data)[key].name=name
if n_elements(psym) ne 0 then (*self.data)[key].psym=psym
if n_elements(symbol) ne 0 then (*self.data)[key].symbol=symbol
if n_elements(line) ne 0 then (*self.data)[key].line=line
if n_elements(thick) ne 0 then (*self.data)[key].thick=thick
if n_elements(color) ne 0 then (*self.data)[key].color=color
end
;display the plot
pro objplot::draw, drawpoly=drawpoly, _extra=e
if (not ptr_valid(self.data)) then begin
message, 'No data to plot!'
return
endif
plot, *(*self.data)[0].x, *(*self.data)[0].y, /nodata, $
title=self.tfont+self.title, $
xtitle=self.tfont+self.xtitle, $
ytitle=self.tfont+self.ytitle, $
_extra=e
nd = n_elements(*self.data)
for i = 0, nd-1 do begin
if ( (*self.data)[i].psym eq 8 ) then mysym, (*self.data)[i].symbol
oplot, *(*self.data)[i].x, *(*self.data)[i].y, $
psym=(*self.data)[i].psym, line=(*self.data)[i].line, $
thick=(*self.data)[i].thick, color=self->checkcolor((*self.data)[i].color)
endfor
if keyword_set(drawpoly) then begin
npol = n_elements(*self.xpoly)
plots, *self.xpoly, *self.ypoly, color=self->checkcolor(self.polycolor)
plots, (*self.xpoly)[[npol-1,0]], (*self.ypoly)[[npol-1,0]], $
color=self->checkcolor(self.polycolor)
endif
end
;order is a
;position goes like
; 10 11 12
; 7 8 9
; 4 5 6
;order is an array indicating the order of keys to use in legend
;(doesn't have to be all of them!)
pro objplot::addlegend, position=position, order=order, _extra=e
if n_elements(position) eq 0 then position=6
if n_elements(order) eq 0 then order=indgen(n_elements(*self.data))
legend_w, ((*self.data).name)[order], position=position, $
color=self->checkcolor(((*self.data).color)[order]), $
psym=((*self.data).psym)[order], symbol=((*self.data).psym)[order], $
thick=((*self.data).thick)[order], linestyle=((*self.data).line)[order], $
_extra=e
end
pro objplot::cleanup
if ptr_valid(self.data) then begin
ptr_free, (*self.data).x, (*self.data).y
ptr_free, self.data
endif
end
function objplot::init
self.tfont='!6'
self.title = ''
self.xtitle = ''
self.ytitle = ''
self.data = ptr_new()
self.xpoly = ptr_new()
self.ypoly = ptr_new()
self.polycolor = -1
return, 1
end
pro objplot__define
;define the object
obj = {objplot, $
title: '', $
xtitle: '', $
ytitle: '', $
tfont: '', $
data: ptr_new(), $
xpoly: ptr_new(), $
ypoly: ptr_new(), $
polycolor: 0 $
}
; Define an auxiliary structure for new data x-vs-y pairs
struct={plotpair, $
name: '', $
x: ptr_new(), $
y: ptr_new(), $
psym: 0, $
symbol: 0, $
line: 0, $
thick: 0, $
color: 0 $
}
end