[xiph-commits] r14005 - trunk/sushivision

xiphmont at svn.xiph.org xiphmont at svn.xiph.org
Tue Oct 16 20:18:34 PDT 2007


Author: xiphmont
Date: 2007-10-16 20:18:34 -0700 (Tue, 16 Oct 2007)
New Revision: 14005

Added:
   trunk/sushivision/panel.h
Removed:
   trunk/sushivision/panel-2d.c
   trunk/sushivision/panel-2d.h
Modified:
   trunk/sushivision/Makefile
   trunk/sushivision/example_fractal.c
   trunk/sushivision/panel.c
   trunk/sushivision/sushivision.h
Log:
More code movement/rearrangement toward new style panels


Modified: trunk/sushivision/Makefile
===================================================================
--- trunk/sushivision/Makefile	2007-10-17 02:35:48 UTC (rev 14004)
+++ trunk/sushivision/Makefile	2007-10-17 03:18:34 UTC (rev 14005)
@@ -24,15 +24,15 @@
 SOCFLAGS  = -fPIC
 SOLDFLAGS = -shared -nostdlib -Wl,-soname="lib$(NAME).so.$(MAJOR)"
 
-SRC       = main.c mapping.c scale.c plot.c slider.c slice.c spinner.c objective.c panel.c panel-2d.c \
-	dimension.c function.c undo.c gtksucks.c xml.c \
+SRC       = main.c mapping.c scale.c plot.c slider.c slice.c spinner.c objective.c panel.c \
+	plane-2d.c dimension.c function.c undo.c gtksucks.c xml.c \
 	tokens.c example_fractal.c example_discrete.c example_chirp.c example_spirograph.c
 INC       = sushivision.h sushimacro.h
 MAN	  =
 EXAMPLES  = sushivision_fractal #sushivision_discrete sushivision_chirp
 EX_OBJ    = example_fractal.o #example_discrete.o example_chirp.o example_spirograph.o
-OBJ       = main.o mapping.o scale.o plot.o slider.o slice.o spinner.c objective.o panel.o panel-2d.o \
-	dimension.o function.o undo.o gtksucks.o xml.o tokens.c
+OBJ       = main.o mapping.o scale.o plot.o slider.o slice.o spinner.c objective.o panel.o \
+	plane-2d.o dimension.o function.o undo.o gtksucks.o xml.o tokens.c
 LIBS      = -lpthread -ldl
 CAIROVER  =  >= 1.4.1
 GTKVER    =  >= 2.10.0

Modified: trunk/sushivision/example_fractal.c
===================================================================
--- trunk/sushivision/example_fractal.c	2007-10-17 02:35:48 UTC (rev 14004)
+++ trunk/sushivision/example_fractal.c	2007-10-17 03:18:34 UTC (rev 14005)
@@ -60,32 +60,31 @@
   // "name:label(arg,arg,arg...)"
 
   sv_dim_new("rc:Re\\(c\\)");
-  sv_dim_make_scale("-2.25, -0.75, 0, 0.25, 0.75");
+  sv_dim_scale("-2.25, -0.75, 0, 0.25, 0.75");
   
   sv_dim_new("ic:Im\\(c\\)");
-  sv_dim_make_scale("-2,-1,0,1,2");
+  sv_dim_scale("-2,-1,0,1,2");
 
   sv_dim_new("rz:Re\\(z0\\)");
-  sv_dim_make_scale("-2.25, -1, 0, 1, 2.25");
+  sv_dim_scale("-2.25, -1, 0, 1, 2.25");
 
   sv_dim_new("iz:Im\\(z0\\)");
-  sv_dim_make_scale("-2.25, -1, 0, 1, 2.25");
+  sv_dim_scale("-2.25, -1, 0, 1, 2.25");
 
   sv_dim_new("it:Max Iterations(picklist)");
-  sv_dim_make_scale("100:one hundred,"
-		    "1000:one thousand,"
-		    "10000:ten thousand,"
-		    "100000:one hundred thousand");
-  sv_dim_set_value(10000);
+  sv_dim_scale("100:one hundred,"
+	       "1000:one thousand,"
+	       "10000:ten thousand,"
+	       "100000:one hundred thousand");
+  sv_dim_value(10000);
 
   sv_obj_new("fractal",fractal_objective,"rc,ic,rz,iz,it", "outer,inner");
-  sv_obj_make_scale("outer","0, .001, .01, .1, 1.0");
-  sv_obj_make_scale("inner","0, .001, .01, .1, 1.0");
+  sv_obj_scale("outer","0, .001, .01, .1, 1.0");
+  sv_obj_scale("inner","0, .001, .01, .1, 1.0");
   
-  sv_panel_new_2d(0,"Mandel/Julia Fractal",
-		  "fractal(2d,Z=inner), fractal(2d,Z=outer)",
-		  "rc(X,Y), ic(X,Y), rz(X,Y), iz(X,Y), it",
-		  0);
+  sv_panel_new("Mandel/Julia Fractal",
+	       "fractal(2d,Z=inner), fractal(2d)",
+	       "rc(X,Y), ic(X,Y), rz(X,Y), iz(X,Y), it");
   
   sv_go();
   sv_join();

Deleted: trunk/sushivision/panel-2d.c
===================================================================
--- trunk/sushivision/panel-2d.c	2007-10-17 02:35:48 UTC (rev 14004)
+++ trunk/sushivision/panel-2d.c	2007-10-17 03:18:34 UTC (rev 14005)
@@ -1,2289 +0,0 @@
-/*
- *
- *     sushivision copyright (C) 2006-2007 Monty <monty at xiph.org>
- *
- *  sushivision is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2, or (at your option)
- *  any later version.
- *   
- *  sushivision is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *   
- *  You should have received a copy of the GNU General Public License
- *  along with sushivision; see the file COPYING.  If not, write to the
- *  Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- * 
- */
-
-#define _GNU_SOURCE
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <math.h>
-#include <signal.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <gtk/gtk.h>
-#include <cairo-ft.h>
-#include "internal.h"
-
-// panel.panel_m: rwlock, protects all panel and plane heaps
-// panel.status_m: mutex, protects status variables
-
-// Plane data in the panels is protected as follows:
-//
-//   Modifications of pointers, heap allocations, etc are protected by 
-//     write-locking panel_m.
-//
-//   Both read and write access to the contents of plane.data and 
-//     plane.image are protected by read-locking panel_m; reads may happen 
-//     at any time, writes are checked only that they've not been superceded 
-//     by a later request.  Writes are not locked against reads at all; a 
-//     read from inconsistent state is temporary and cosmetic only. It will 
-//     always be immediately replaced by complete/correct data when the write
-//     finishes and triggers a flush.
-
-//   comp_serialno is used to to verify 'freshness' of operations; anytime a 
-//     thread needs to synchronize with panel/plane data before continuing 
-//     (read or write), it compares the serialno that dispatched it to the 
-//     current serialno.  A mismatch immediately aborts the task in progress 
-//     with STATUS_WORKING.
-
-//   map_serialno performs the same task with respect to remap requests
-//     for a given plane.
-
-// worker thread process order:
-
-// > recompute setup
-// > images resize
-// > scale render
-// > legend render
-// > bg render
-// > expose
-// > data resize
-// > image map render (throttleable)
-// > computation work 
-// > idle             
-
-// proceed to later steps only if there's no work immediately
-// dispatchable from earlier steps.
-
-// UI output comes first (UI is never dead, even briefly, and that
-// includes graphs), however progressive UI work is purposely
-// throttled.
-
-// wake_workers() only explicitly needed when triggering tasks via
-// GDK/API; it is already called implicitly in the main loop whenever
-// a task step completes.
-
-static void payload_free(sv_dim_data_t *dd, int dims){
-  for(i=0;i<dims;i++)
-    _sv_dim_data_clear(dd+i);
-  free(dd);
-}
-
-static int image_resize(sv_plane_t *pl, 
-			sv_panel_t *p){
-  return pl->c.image_resize(pl, p);
-}
-
-static int data_resize(sv_plane_t *pl, 
-		       sv_panel_t *p){
-  return pl->c.data_resize(pl, p);
-}
-
-static int image_work(sv_plane_t *pl, 
-		      sv_panel_t *p){
-  return pl->c.image_work(pl, p);
-}
-
-static int data_work(sv_plane_t *pl, 
-		     sv_panel_t *p){
-  return pl->c.data_work(pl, p);
-}
-
-#define STATUS_IDLE 0
-#define STATUS_BUSY 1
-#define STATUS_WORKING 2
-
-static int plane_loop(sv_panel_t *p, int *next, 
-		      int(*function)(_sv_plane_t *, 
-				     sv_panel_t *)){
-  int finishedflag=1;
-  int last = *next;
-  int serialno = p->serialno;
-  do{
-    int i = *next++;
-    if(*next>=p->planes)*next=0;
-    int status = function(p->plane_list[i],p);
-    if(status == STATUS_WORKING) return STATUS_WORKING;
-    if(status != STATUS_IDLE) finishedflag=0;
-  }while(i!=last);
-
-  if(finishedflag)
-    return STATUS_IDLE;
-  return STATUS_BUSY;
-}
-
-static int done_working(sv_panel_t *p){
-  pthread_mutex_unlock(p->status_m);
-  pthread_rwlock_unlock(p->panel_m);
-  return STATUS_WORKING;
-}
-
-static int done_busy(sv_panel_t *p){
-  pthread_mutex_unlock(p->status_m);
-  pthread_rwlock_unlock(p->panel_m);
-  return STATUS_BUSY;
-}
-
-static int done_idle(sv_panel_t *p){
-  pthread_mutex_unlock(p->status_m);
-  pthread_rwlock_unlock(p->panel_m);
-  return STATUS_IDLE;
-}
-
-int _sv_panel_work(sv_panel_t *p){
-  int i,serialno,status;
-  pthread_rwlock_rdlock(p->panel_m);
-  pthread_mutex_lock(p->status_m);
-
-  // recomute setup
-
-  // plane recompute calls will do nothing if recomputation is not
-  // required.  Even if computation not required, will still request
-  // an image resize; the image resize code will later noop if there's
-  // no need to resize either.
-  if(p->recompute_pending){
-    p->recompute_pending=0;
-    p->comp_serialno++;
-    p->image_resize=1;
-    p->data_resize=1;
-    p->rescale=1;
-    p->relegend=1;
-    p->bgrender=0;
-    p->image_next_plane=0;
-    
-    bg->c.recompute_setup(p->bg, p);
-
-    for(i=0;i<p->planes;i++)
-      p->plane_list[i]->c.recompute_setup(p->plane_list[i], p);
-
-    pthread_mutex_unlock(p->status_m);
-    pthread_rwlock_unlock(p->panel_m);
-
-    return STATUS_WORKING;
-  }
-
-  if(p->relegend_pending){
-    p->relegend=1;
-    p->relegend_pending=0;
-  }
-
-  serialno = p->comp_serialno;
-
-  // image resize
-
-  // again, each plane checks to see if a resize is really necessary.  If not, does nothing.
-  if(p->image_resize){
-    status = plane_loop(p,&p->image_next_plane,image_resize);
-    if(status == STATUS_WORKING) return done_working(p);
-    if(status == STATUS_IDLE){
-      p->image_resize = 0;
-      p->bgrender = 1;
-    }
-  }
-
-  // legend regeneration
-  if(p->relegend){
-    if(bg_legend(p) == STATUS_IDLE){
-      p->expose=1;
-      p->relegend=0;
-    }
-    return done_working(p);
-  }
-
-  // axis scale redraw
-  if(p->rescale){
-    if(bg_scale(p) == STATUS_IDLE){
-      p->expose=1;
-      p->rescale=0;
-    }
-    return done_working(p);
-  }
-
-  // need to join on image resizing before proceeding to background redraw
-  if(p->image_resize)
-    return done_busy(p);
-  
-  // bg render
-  if(p->bg_render){
-    if(bg_render(p) == STATUS_IDLE){
-      p->expose=1;
-      p->bgrender=0;
-    }
-    return done_working(p);
-  }    
-  
-  // expose
-  if(p->expose &&
-     !p->relegend &&
-     !p->rescale &&
-     !p->bgrender){
-    // wait till all these ops are done
-    bg_expose(p);
-    return done_working(p);
-  }    
-
-  // data resize
-  if(p->data_resize){
-    status = plane_loop(p,&p->data_next_plane,data_resize);
-    if(status == STATUS_WORKING) return done_working(p);
-    if(status == STATUS_IDLE) p->data_resize = 0;
-  }
-
-  // need to join on data resizing before proceeding to map/compute work
-  if(p->data_resize)
-    return done_busy(p);
-  
-  // image map render
-  if(p->map_render){
-    status = plane_loop(p,&p->image_next_plane,image_work);
-    if(status == STATUS_WORKING) return done_working(p);
-    if(status == STATUS_IDLE) p->map_render = 0;
-  }
-  
-  // computation work 
-  status = plane_loop(p,&p->data_next_plane,data_work);
-  if(status == STATUS_WORKING) return done_working(p);
-  return done_idle(p);
-}
-
-
-
-static int _sv_plane2d_compute_one(){
-
-}
-
-static int _sv_plane2d_map_one(){
-
-}
-
-// work order: resize/fast scale -> compute -> plane_render -> composite
-
-// enter unlocked
-static void _sv_planez_compute_line(sv_panel_t *p, 
-				    _sv_plane2d_t *z,
-
-				    int serialno,
-
-				    int dw,
-				    int y,
-				    int x_d, 
-				    _sv_scalespace_t sxi,
-				    double *dim_vals, 
-				    _sv_bythread_cache_2d_t *c){
-
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  int i,j;
-  
-  /* cache access is unlocked because the cache is private to this
-     worker thread */
-
-  for(j=0;j<dw;j++){
-    double *fout = c->fout;
-    sv_func_t **f = p2->used_function_list;
-    int *obj_y_off = p2->y_fout_offset;
-    int *onum = p2->y_obj_to_panel;
-    
-    /* by function */
-    dim_vals[x_d] = _sv_scalespace_value(&sxi,j);   
-    for(i=0;i<p2->used_functions;i++){
-      (*f)->callback(dim_vals,fout);
-      fout += (*f)->outputs;
-      f++;
-    }
-    
-    /* process function output by plane type/objective */
-    /* 2d panels currently only care about the Y output value */
-    
-    /* slider map */
-    for(i=0;i<p2->y_obj_num;i++){
-      float val = (float)_sv_slider_val_to_del(p2->range_scales[*onum++], c->fout[*obj_y_off++]);
-      if(isnan(val)){
-	c->y_map[i][j] = -1;
-      }else{
-	if(val<0)val=0;
-	if(val>1)val=1;
-	c->y_map[i][j] = rint(val * (256.f*256.f*256.f));
-      }
-    }
-  }
-
-  gdk_lock ();
-  if(p->private->plot_serialno == serialno){
-    for(j=0;j<p2->y_obj_num;j++){
-      int *d = p2->y_map[j] + y*dw;
-      int *td = c->y_map[j];
-      
-      memcpy(d,td,dw*sizeof(*d));
-      
-    }
-  }
-  gdk_unlock ();
-}
-
-// call with lock
-static void _sv_panel2d_clear_pane(sv_panel_t *p){
-
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  int pw = p2->x.pixels;
-  int ph = p2->y.pixels;
-  int i;
-  _sv_plot_t *plot = PLOT(p->private->graph);
-  
-  for(i=0;i<p2->y_obj_num;i++){
-    // map is freed and nulled to avoid triggering a fast-scale on an empty data pane
-    if(p2->y_map[i])
-      free(p2->y_map[i]);
-    p2->y_map[i]=NULL;
-
-    // free y_planes so that initial remap doesn't waste time on mix
-    // op; they are recreated during remap at the point the y_todo
-    // vector indicates something to do
-    if(p2->y_planes[i])
-      free(p2->y_planes[i]);
-    p2->y_planes[i] = NULL;
-
-    // work vector is merely cleared
-    memset(p2->y_planetodo[i], 0, ph*sizeof(**p2->y_planetodo));
-  }
-
-  // clear the background surface 
-  if(plot->datarect)
-    memset(plot->datarect, 0, ph*pw*sizeof(*plot->datarect));
-
-  // the bg is not marked to be refreshed; computation setup will do
-  // that as part of the fast_scale, even if the fast_scale is
-  // short-circuited to a noop.
-}
-
-typedef struct{
-  double x;
-  double y;
-  double z;
-} compute_result;
-
-// used by the legend code. this lets us get away with having only a mapped display pane
-// call with lock
-static void _sv_panel2d_compute_point(sv_panel_t *p,sv_obj_t *o, double x, double y, compute_result *out){
-  double dim_vals[_sv_dimensions];
-  int i,j;
-  int pflag=0;
-  int eflag=0;
-
-  // fill in dimensions
-  int x_d = p->private->x_d->number;
-  int y_d = p->private->y_d->number;
-
-  for(i=0;i<_sv_dimensions;i++){
-    sv_dim_t *dim = _sv_dimension_list[i];
-    dim_vals[i]=dim->val;
-  }
-
-  gdk_unlock ();
-
-  dim_vals[x_d] = x;
-  dim_vals[y_d] = y;
-
-  *out = (compute_result){NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN};
-
-  // compute
-  for(i=0;i<_sv_functions;i++){
-    sv_func_t *f = _sv_function_list[i];
-    int compflag = 0;
-    double fout[f->outputs];
-    double val;
-
-    // compute and demultiplex output
-    for(j=0;j<o->outputs;j++){
-      if(o->function_map[j] == i){
-
-	if(!compflag) f->callback(dim_vals,fout);
-	compflag = 1;
-	
-	val = fout[o->output_map[j]];
-	switch(o->output_types[j]){
-	case 'X':
-	  out->x = val;
-	  break;
-	case 'Y':
-	  out->y = val;
-	  break;
-	case 'Z':
-	  out->z = val;
-	  break;
-	case 'E':
-	  if(eflag)
-	    out->e2 = val;
-	  else
-	    out->e1 = val;
-	  eflag = 1;
-	  break;
-	case 'P':
-	  if(pflag)
-	    out->p2 = val;
-	  else
-	    out->p1 = val;
-	  pflag = 1;
-	  break;
-	case 'M':
-	  out->m = val;
-	  break;
-	}
-      }
-    }
-  }
-  gdk_lock ();
-
-}
-
-/* functions that perform actual graphical rendering */
-
-static float _sv_panel2d_resample_helpers_init(_sv_scalespace_t *to, _sv_scalespace_t *from,
-					       unsigned char *delA, unsigned char *delB, 
-					       int *posA, int *posB,
-					       int xymul){
-  int i;
-  int dw = from->pixels;
-  int pw = to->pixels;
-
-  long scalenum = _sv_scalespace_scalenum(to,from);
-  long scaleden = _sv_scalespace_scaleden(to,from);
-  long del = _sv_scalespace_scaleoff(to,from);
-  int bin = del / scaleden;
-  del -= bin * scaleden; 
-  int discscale = (scaleden>scalenum?scalenum:scaleden);
-  int total = xymul*scalenum/discscale;
-
-  for(i=0;i<pw;i++){
-    long del2 = del + scalenum;
-    int sizeceil = (del2 + scaleden - 1)/ scaleden; // ceiling
-    int sizefloor = del2 / scaleden;
-
-    while(bin<0 && del2>scaleden){
-      bin++;
-      del = 0;
-      del2 -= scaleden;
-      sizeceil--;
-    }
-    
-    if(del2 > scaleden && bin>=0 && bin<dw){
-      int rem = total;
-
-      delA[i] = ((xymul * (scaleden - del)) + (discscale>>1)) / discscale;
-      posA[i] = bin;
-      rem -= delA[i];
-      rem -= xymul*(sizeceil-2);
-
-      while(bin+sizeceil>dw){
-	sizeceil--;
-	del2=0;
-      }
-
-      del2 %= scaleden;
-      if(rem<0){
-	delA[i] += rem;
-	delB[i] = 0;
-      }else{
-	delB[i] = rem; // don't leak 
-      }
-      posB[i] = bin+sizeceil;
-
-    }else{
-      if(bin<0 || bin>=dw){
-	delA[i] = 0;
-	posA[i] = 0;
-	delB[i] = 0;
-	posB[i] = 0;
-      }else{
-	delA[i] = xymul;
-	posA[i] = bin;
-	delB[i] = 0;
-	posB[i] = bin+1;
-	if(del2 == scaleden)del2=0;
-      }
-    }
-
-    bin += sizefloor;
-    del = del2;
-  }
-  return (float)xymul/total;
-}
-
-/* x resample helpers are put in the per-thread cache because locking it would
-   be relatively expensive. */
-// call while locked
-static void _sv_panel2d_resample_helpers_manage_x(sv_panel_t *p, _sv_bythread_cache_2d_t *c){
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  if(p->private->plot_serialno != c->serialno){
-    int pw = p2->x.pixels;
-    c->serialno = p->private->plot_serialno;
-
-    if(c->xdelA)
-      free(c->xdelA);
-    if(c->xdelB)
-      free(c->xdelB);
-    if(c->xnumA)
-      free(c->xnumA);
-    if(c->xnumB)
-      free(c->xnumB);
-    
-    c->xdelA = calloc(pw,sizeof(*c->xdelA));
-    c->xdelB = calloc(pw,sizeof(*c->xdelB));
-    c->xnumA = calloc(pw,sizeof(*c->xnumA));
-    c->xnumB = calloc(pw,sizeof(*c->xnumB));
-    c->xscalemul = _sv_panel2d_resample_helpers_init(&p2->x, &p2->x_v, c->xdelA, c->xdelB, c->xnumA, c->xnumB, 17);
-  }
-}
-
-/* y resample is in the panel struct as per-row access is already locked */
-// call while locked
-static void _sv_panel2d_resample_helpers_manage_y(sv_panel_t *p){
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  if(p->private->plot_serialno != p2->resample_serialno){
-    int ph = p2->y.pixels;
-    p2->resample_serialno = p->private->plot_serialno;
-
-    if(p2->ydelA)
-      free(p2->ydelA);
-    if(p2->ydelB)
-      free(p2->ydelB);
-    if(p2->ynumA)
-      free(p2->ynumA);
-    if(p2->ynumB)
-      free(p2->ynumB);
-    
-    p2->ydelA = calloc(ph,sizeof(*p2->ydelA));
-    p2->ydelB = calloc(ph,sizeof(*p2->ydelB));
-    p2->ynumA = calloc(ph,sizeof(*p2->ynumA));
-    p2->ynumB = calloc(ph,sizeof(*p2->ynumB));
-    p2->yscalemul = _sv_panel2d_resample_helpers_init(&p2->y, &p2->y_v, p2->ydelA, p2->ydelB, p2->ynumA, p2->ynumB, 15);
-  }
-}
-
-static inline void _sv_panel2d_mapping_calc( void (*m)(int,int, _sv_lcolor_t *), 
-					     int low,
-					     float range,
-					     int in, 
-					     int alpha, 
-					     int mul, 
-					     _sv_lcolor_t *outc){
-  if(mul && in>=alpha){
-    int val = rint((in - low) * range);
-    if(val<0)val=0;
-    if(val>65536)val=65536;
-    m(val,mul,outc);
-  }
-}
-
-/* the data rectangle is data width/height mapped deltas.  we render
-   and subsample at the same time. */
-/* return: -1 == abort
-            0 == more work to be done in this plane
-	    1 == plane fully dispatched (possibly not complete) */
-
-/* enter with lock */
-static int _sv_panel2d_resample_render_y_plane_line(sv_panel_t *p, _sv_bythread_cache_2d_t *c, 
-						    int plot_serialno, int map_serialno, int y_no){
-  
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  int objnum = p2->y_obj_to_panel[y_no]; 
-  int *in_data = p2->y_map[y_no];
-  int ph = p2->y.pixels;
-
-  if(!in_data || !c){
-    p->private->map_complete_count -= ph;
-    return 1;
-  }
-
-  unsigned char *todo = p2->y_planetodo[y_no];
-  int i = p2->y_next_line;
-  int j;
-
-  /* find a row that needs to be updated */
-  while(i<ph && !todo[i]){
-    p->private->map_complete_count--;
-    p2->y_next_line++;
-    i++;
-  }
-
-  if(i == ph) return 1;
-
-  p2->y_next_line++;
-
-  /* row [i] needs to be updated; marshal */
-  _sv_mapping_t *map = p2->mappings+objnum;
-  void (*mapfunc)(int,int, _sv_lcolor_t *) = map->mapfunc;
-  int ol_alpha = rint(p2->alphadel[y_no] * 16777216.f);
-  _sv_ucolor_t *panel = p2->y_planes[y_no];
-  int ol_low = rint(map->low * 16777216.f);
-  float ol_range = map->i_range * (1.f/256.f);
-
-  int pw = p2->x.pixels;
-  int dw = p2->x_v.pixels;
-  int dh = p2->y_v.pixels;
-  _sv_ccolor_t work[pw];
-
-  if(!panel)
-    panel = p2->y_planes[y_no] = calloc(pw*ph, sizeof(**p2->y_planes));
-
-  if(ph!=dh || pw!=dw){
-    /* resampled row computation; may involve multiple data rows */
-
-    _sv_panel2d_resample_helpers_manage_y(p);
-    _sv_panel2d_resample_helpers_manage_x(p,c);
-
-    float idel = p2->yscalemul * c->xscalemul;
-
-    /* by column */
-    int ydelA=p2->ydelA[i];
-    int ydelB=p2->ydelB[i];
-    int ystart=p2->ynumA[i];
-    int yend=p2->ynumB[i];
-    int lh = yend - ystart;
-    int data[lh*dw];
-
-    memcpy(data,in_data+ystart*dw,sizeof(data));
-
-    gdk_unlock();
-
-    unsigned char *xdelA = c->xdelA;
-    unsigned char *xdelB = c->xdelB;
-    int *xnumA = c->xnumA;
-    int *xnumB = c->xnumB;
-      
-    /* by panel col */
-    for(j=0;j<pw;j++){
-      
-      _sv_lcolor_t out = (_sv_lcolor_t){0,0,0,0}; 
-      int xstart = xnumA[j];
-      int xend = xnumB[j];
-      int dx = xstart;
-      int xA = xdelA[j];
-      int xB = xdelB[j];
-      int y = ystart;
-
-      // first line
-      if(y<yend){
-	if(dx<xend)
-	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx++], ol_alpha, ydelA*xA, &out);
-	
-	for(; dx < xend-1; dx++)
-	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelA*17, &out);
-	
-	if(dx<xend)
-	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelA*xB, &out);
-	y++;
-      }
-
-      // mid lines
-      for(;y<yend-1;y++){
-	dx = xstart += dw;
-	xend += dw;
-	if(dx<xend)
-	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx++], ol_alpha, 15*xA, &out);
-	
-	for(; dx < xend-1; dx++)
-	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, 255, &out);
-	
-	if(dx<xend)
-	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, 15*xB, &out);
-      }
-      
-      // last line
-      if(y<yend){
-	dx = xstart += dw;
-	xend += dw;
-	if(dx<xend)
-	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx++], ol_alpha, ydelB*xA, &out);
-	
-	for(; dx < xend-1; dx++)
-	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelB*17, &out);
-	
-	if(dx<xend)
-	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelB*xB, &out);
-      }
-
-      work[j].a = (u_int32_t)(out.a*idel);
-      work[j].r = (u_int32_t)(out.r*idel);
-      work[j].g = (u_int32_t)(out.g*idel);
-      work[j].b = (u_int32_t)(out.b*idel);
-      
-    }
-
-  }else{
-    /* non-resampling render */
-
-    int data[dw];
-    memcpy(data,in_data+i*dw,sizeof(data));
-    gdk_unlock();      
-
-    for(j=0;j<pw;j++){
-
-      _sv_lcolor_t out = (_sv_lcolor_t){0,0,0,0};
-      _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[j], ol_alpha, 255, &out);
-	
-      work[j].a = (u_int32_t)(out.a);
-      work[j].r = (u_int32_t)(out.r);
-      work[j].g = (u_int32_t)(out.g);
-      work[j].b = (u_int32_t)(out.b);
-    }
-  }
-
-  gdk_lock ();  
-  if(plot_serialno != p->private->plot_serialno ||
-     map_serialno != p->private->map_serialno)
-    return -1;
-  memcpy(panel+i*pw,work,sizeof(work));
-  p2->bg_todo[i] = 1;
-  p2->y_planetodo[y_no][i] = 0;
-
-  // must be last; it indicates completion
-  p->private->map_complete_count--;
-  return 0;
-}
-
-static void render_checks(_sv_ucolor_t *c, int w, int y){
-  /* default checked background */
-  /* 16x16 'mid-checks' */ 
-  int x,j;
-  
-  int phase = (y>>4)&1;
-  for(x=0;x<w;){
-    u_int32_t phaseval = 0xff505050UL;
-    if(phase) phaseval = 0xff808080UL;
-    for(j=0;j<16 && x<w;j++,x++)
-      c[x].u = phaseval;
-    phase=!phase;
-  }
-}
-
-// enter with lock
-static int _sv_panel2d_render_bg_line(sv_panel_t *p, int plot_serialno, int map_serialno){
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  _sv_plot_t *plot = PLOT(p->private->graph);
-  if(plot_serialno != p->private->plot_serialno ||
-     map_serialno != p->private->map_serialno) return -1;
-  
-  int ph = p2->y.pixels;
-  int pw = p2->x.pixels;
-  unsigned char *todo = p2->bg_todo;
-  int i = p2->bg_next_line,j;
-  _sv_ucolor_t work_bg[pw];
-  _sv_ucolor_t work_pl[pw];
-  int bgmode = p->private->bg_type;
-
-  /* find a row that needs to be updated */
-  while(i<ph && !todo[i]){
-    p->private->map_complete_count--;
-    p2->bg_next_line++;
-    i++;
-  }
-
-  if(i == ph)
-    goto done;
-
-  if(i < p2->bg_first_line) p2->bg_first_line = i;
-  if(i+1 > p2->bg_last_line) p2->bg_last_line = i+1;
-  p2->bg_next_line++;
-
-  /* gray background checks */
-  gdk_unlock();
-
-  switch(bgmode){
-  case SV_BG_WHITE:
-    for(j=0;j<pw;j++)
-      work_bg[j].u = 0xffffffffU;
-    break;
-  case SV_BG_BLACK:
-    for(j=0;j<pw;j++)
-      work_bg[j].u = 0xff000000U;
-    break;
-  default:
-    render_checks(work_bg,pw,i);
-    break;
-  }
-
-  /* by objective */
-  for(j=0;j<p->objectives;j++){
-    int o_ynum = p2->y_obj_from_panel[j];
-    
-    gdk_lock();
-    if(plot_serialno != p->private->plot_serialno ||
-       map_serialno != p->private->map_serialno) return -1;
-
-    /**** mix Y plane */
-    
-    if(p2->y_planes[o_ynum]){
-      int x;
-      _sv_ucolor_t (*mixfunc)(_sv_ucolor_t,_sv_ucolor_t) = p2->mappings[j].mixfunc;
-      _sv_ucolor_t *rect = p2->y_planes[o_ynum] + i*pw;
-      memcpy(work_pl,rect,sizeof(work_pl));
-      
-      gdk_unlock();
-      for(x=0;x<pw;x++)
-	work_bg[x] = mixfunc(work_pl[x],work_bg[x]);
-    }else
-      gdk_unlock();
-
-    /**** mix Z plane */
-    
-    /**** mix vector plane */
-
-  }
-
-  gdk_lock();
-  if(plot_serialno != p->private->plot_serialno ||
-     map_serialno != p->private->map_serialno) return -1;
-
-    // rendered a line, get it on the screen */
-  
-  memcpy(plot->datarect+pw*i, work_bg, sizeof(work_bg));
-
-  p->private->map_complete_count--;
-
- done:
-  if(p->private->map_complete_count)
-    return 1; // not done yet
-
-  return 0;
-}
-
-static void _sv_panel2d_mark_map_full(sv_panel_t *p);
-
-// enter with lock; returns zero if thread should sleep / get distracted
-static int _sv_panel2d_remap(sv_panel_t *p, _sv_bythread_cache_2d_t *thread_cache){
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  _sv_plot_t *plot = PLOT(p->private->graph);
-
-  if(!plot) goto abort;
-  int ph = plot->y.pixels;
-  int pw = plot->x.pixels;
-  
-  int plot_serialno = p->private->plot_serialno; 
-  int map_serialno = p->private->map_serialno; 
-
-  /* brand new remap indicated by the generic progress indicator being set to 0 */
-  if(p->private->map_progress_count == 0){
-
-    p->private->map_progress_count = 1; // 'in progress'
-    p->private->map_complete_count = p2->y_obj_num * ph; // count down to 0; 0 indicates completion
-
-    // set up Y plane rendering
-    p2->y_next_plane = 0;
-    p2->y_next_line = 0;
-
-    // bg mix
-    p2->bg_next_line = 0;
-    p2->bg_first_line = ph;
-    p2->bg_last_line = 0;
-
-    if(!p2->partial_remap)
-      _sv_panel2d_mark_map_full(p);
-  }
-
-  /* by plane, by line; each plane renders independently */
-  /* Y planes */
-  if(p2->y_planetodo){
-    if(p2->y_next_plane < p2->y_obj_num){
-      int status = _sv_panel2d_resample_render_y_plane_line(p, thread_cache, 
-							    plot_serialno, map_serialno, 
-							    p2->y_next_plane);
-      if(status == -1) goto abort;
-      if(status == 1){
-	p2->y_next_plane++;
-	p2->y_next_line = 0;
-      }
-      return 1;
-    }
-  }else{
-    p->private->map_complete_count = 0;
-  }
-
-  /* renders have been completely dispatched, but are they complete? */
-  /* the below is effectively a a thread join */
-  if(p2->bg_next_line == 0){
-
-    // join still needs to complete....
-    if(p->private->map_complete_count){
-      // nonzero complete count, not finished.  returning zero will cause
-      // this worker thread to sleep or go on to do other things.
-      return 0; 
-    }else{
-      // zero complete count, the planes are done; we can begin
-      // background render.  At least one thread is guaranteed to get
-      // here, which is enough; we can now wake the others [if they were
-      // asleep] and have them look for work here. */
-      p->private->map_complete_count = ph; // [ph] lines to render in bg plane
-      p2->bg_next_line = 0;
-
-      _sv_wake_workers();
-    }
-  }
-
-  /* mix new background, again line by line */
-  if(p2->bg_next_line < ph){
-    int status = _sv_panel2d_render_bg_line(p, plot_serialno, map_serialno);
-    if(status == -1) goto abort;
-    if(p->private->map_complete_count)return status;
-  }else
-    return 0; // nothing left to dispatch
-
-  // entirely finished.
-
-  // remap completed; flush background to screen
-  _sv_plot_expose_request_partial (plot,0,p2->bg_first_line,
-			       pw,p2->bg_last_line - p2->bg_first_line);
-  gdk_flush();
-
-  // clean bg todo list
-  memset(p2->bg_todo,0,ph*sizeof(*p2->bg_todo));
-
-  // clear 'panel in progress' flag
-  p2->partial_remap = 0;
-  _sv_panel_clean_map(p);
-  return 0;
-
- abort:
-  // reset progress to 'start over'
-  return 1;
-}
-
-// looks like a cop-out but is actually the correct thing to do; the
-// data *must* be WYSIWYG from panel display.
-static void _sv_panel2d_print_bg(sv_panel_t *p, cairo_t *c){
-  _sv_plot_t *plot = PLOT(p->private->graph);
-
-  if(!plot) return;
-
-  cairo_pattern_t *pattern = cairo_pattern_create_for_surface(plot->back);
-  cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
-  cairo_set_source(c,pattern);
-  cairo_paint(c);
-
-  cairo_pattern_destroy(pattern);
-}
-
-static void _sv_panel2d_print(sv_panel_t *p, cairo_t *c, int w, int h){
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  _sv_plot_t *plot = PLOT(p->private->graph);
-  double pw = p->private->graph->allocation.width;
-  double ph = p->private->graph->allocation.height;
-  double scale;
-  int i;
-  double maxlabelw=0;
-  double y;
-
-  if(w/pw < h/ph)
-    scale = w/pw;
-  else
-    scale = h/ph;
-
-  cairo_matrix_t m;
-  cairo_save(c);
-  cairo_get_matrix(c,&m);
-  cairo_matrix_scale(&m,scale,scale);
-  cairo_set_matrix(c,&m);
-  
-  _sv_plot_print(plot, c, ph*scale, (void(*)(void *, cairo_t *))_sv_panel2d_print_bg, p);
-  cairo_restore(c);
-
-  // find extents widths for objective scale labels
-  cairo_set_font_size(c,10);
-  for(i=0;i<p->objectives;i++){
-    cairo_text_extents_t ex;
-    sv_obj_t *o = p->objective_list[i].o;
-    cairo_text_extents(c, o->name, &ex);
-    if(ex.width > maxlabelw) maxlabelw=ex.width;
-  }
-
-
-  y = ph * scale + 10;
-
-  for(i=0;i<p->objectives;i++){
-    sv_obj_t *o = p->objective_list[i].o;
-    _sv_slider_t *s = p2->range_scales[i];
-    
-    // get scale height
-    double labelh = _sv_slider_print_height(s);
-    cairo_text_extents_t ex;
-    cairo_text_extents (c, o->name, &ex);
-
-    int lx = maxlabelw - ex.width;
-    int ly = labelh/2 + ex.height/2;
-    
-    // print objective labels
-    cairo_set_source_rgb(c,0.,0.,0.);
-    cairo_move_to (c, lx,ly+y);
-    cairo_show_text (c, o->name);
-
-    // draw slider
-    // set translation
-    cairo_save(c);
-    cairo_translate (c, maxlabelw + 10, y);
-    _sv_slider_print(s, c, pw*scale - maxlabelw - 10, labelh);
-    cairo_restore(c);
-
-    y += labelh;
-  }
-
-}
-
-// call while locked
-static void _sv_panel2d_mark_map_plane(sv_panel_t *p, int onum, int y, int z, int v){
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  int ph = p2->y.pixels;
-
-  if(y && p2->y_planetodo){
-    int y_no = p2->y_obj_from_panel[onum];
-    if(y_no>=0 && p2->y_planetodo[y_no])
-      memset(p2->y_planetodo[y_no],1,ph * sizeof(**p2->y_planetodo));
-  }
-  p2->partial_remap = 1;
-}
-
-// call while locked 
-static void _sv_panel2d_mark_map_line_y(sv_panel_t *p, int line){
-  // determine all panel lines this y data line affects
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  int ph = p2->y.pixels;
-  int pw = p2->x.pixels;
-  int dw = p2->x_v.pixels;
-  int dh = p2->y_v.pixels;
-  int i,j;
-
-  p2->partial_remap = 1;
-
-  if(ph!=dh || pw!=dw){
-    /* resampled row computation; may involve multiple data rows */
-    if(p2->y_planetodo){
-      _sv_panel2d_resample_helpers_manage_y(p);
-      
-      for(i=0;i<ph;i++)
-	if(p2->ynumA[i]<=line &&
-	   p2->ynumB[i]>line){
-	  
-	  for(j=0;j<p2->y_obj_num;j++)
-	    if(p2->y_planetodo[j])
-	      p2->y_planetodo[j][i]=1;
-      }
-    }
-  }else{
-    if(p2->y_planetodo)
-      if(line>=0 && line<ph)
-	for(j=0;j<p2->y_obj_num;j++)
-	  if(p2->y_planetodo[j])
-	    p2->y_planetodo[j][line]=1;
-  }
-}
-
-// call while locked
-static void _sv_panel2d_mark_map_full(sv_panel_t *p){
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  int ph = p2->y.pixels;
-  int i,j;
-
-  p2->partial_remap = 1;
-
-  if(p2->y_planetodo){
-    for(j=0;j<p2->y_obj_num;j++){
-      if(p2->y_planetodo[j]){
-	for(i=0;i<ph;i++){
-	  p2->y_planetodo[j][i]=1;
-	}
-      }
-    }
-  }
-}
-
-// enter with lock
-static void _sv_panel2d_update_legend(sv_panel_t *p){  
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  _sv_plot_t *plot = PLOT(p->private->graph);
-
-  if(plot){
-    int i;
-    char buffer[320];
-    int depth = 0;
-    _sv_plot_legend_clear(plot);
-
-    // potentially add each dimension to the legend; add axis
-    // dimensions only if crosshairs are active
-
-    // display decimal precision relative to display scales
-    if(3-_sv_scalespace_decimal_exponent(&p2->x) > depth) 
-      depth = 3-_sv_scalespace_decimal_exponent(&p2->x);
-    if(3-_sv_scalespace_decimal_exponent(&p2->y) > depth) 
-      depth = 3-_sv_scalespace_decimal_exponent(&p2->y);
-    for(i=0;i<p->dimensions;i++){
-      sv_dim_t *d = p->dimension_list[i].d;
-      if( (d!=p->private->x_d && d!=p->private->y_d) ||
-	  plot->cross_active){
-	snprintf(buffer,320,"%s = %+.*f",
-		 p->dimension_list[i].d->legend,
-		 depth,
-		 p->dimension_list[i].d->val);
-	_sv_plot_legend_add(plot,buffer);
-      }
-    }
-    
-    // add each active objective plane to the legend
-    // choose the value under the crosshairs 
-    if(plot->cross_active){
-      // one space 
-      _sv_plot_legend_add(plot,NULL);
-
-      for(i=0;i<p->objectives;i++){
-	
-	if(!_sv_mapping_inactive_p(p2->mappings+i)){
-	  compute_result vals;
-	  _sv_panel2d_compute_point(p,p->objective_list[i].o, plot->selx, plot->sely, &vals);
-	  
-	  if(!isnan(vals.y)){
-	    
-	    snprintf(buffer,320,"%s = %f",
-		     p->objective_list[i].o->name,
-		     vals.y);
-	    _sv_plot_legend_add(plot,buffer);
-	  }
-	}
-      }
-    }
-  }
-}
-
-static void _sv_panel2d_mapchange_callback(GtkWidget *w,gpointer in){
-  sv_obj_list_t *optr = (sv_obj_list_t *)in;
-  //sv_obj_t *o = optr->o;
-  sv_panel_t *p = optr->p;
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  int onum = optr - p->objective_list;
-
-  _sv_undo_push();
-  _sv_undo_suspend();
-
-  _sv_mapping_set_func(&p2->mappings[onum],gtk_combo_box_get_active(GTK_COMBO_BOX(w)));
-  
-  //redraw the map slider
-  _sv_slider_set_gradient(p2->range_scales[onum], &p2->mappings[onum]);
-
-  // in the event the mapping active state changed
-  _sv_panel_dirty_legend(p);
-
-  //redraw the plot
-  _sv_panel2d_mark_map_plane(p,onum,1,0,0);
-  _sv_panel_dirty_map(p);
-  _sv_undo_resume();
-}
-
-static void _sv_panel2d_map_callback(void *in,int buttonstate){
-  sv_obj_list_t *optr = (sv_obj_list_t *)in;
-  //sv_obj_t *o = optr->o;
-  sv_panel_t *p = optr->p;
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  int onum = optr - p->objective_list;
-
-  if(buttonstate == 0){
-    _sv_undo_push();
-    _sv_undo_suspend();
-  }
-
-  // recache alpha del */
-  p2->alphadel[onum] = 
-    _sv_slider_val_to_del(p2->range_scales[onum],
-		      _sv_slider_get_value(p2->range_scales[onum],1));
-
-  // redraw the plot on motion
-  if(buttonstate == 1){
-    _sv_panel2d_mark_map_plane(p,onum,1,0,0);
-    _sv_panel_dirty_map(p);
-  }
-  if(buttonstate == 2)
-    _sv_undo_resume();
-}
-
-static void _sv_panel2d_update_xysel(sv_panel_t *p){
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  int i;
-  // update which x/y buttons are pressable */
-  // enable/disable dimension slider thumbs
-
-  for(i=0;i<p->dimensions;i++){
-    if(p2->dim_xb[i] &&
-       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_xb[i]))){
-      // make the y insensitive
-      if(p2->dim_yb[i])
-	_gtk_widget_set_sensitive_fixup(p2->dim_yb[i],FALSE);
-
-      // set the x dim flag
-      p->private->x_d = p->dimension_list[i].d;
-      p2->x_scale = p->private->dim_scales[i];
-      p2->x_dnum = i;
-    }else{
-      // if there is a y, make it sensitive 
-      if(p2->dim_yb[i])
-	_gtk_widget_set_sensitive_fixup(p2->dim_yb[i],TRUE);
-    }
-    if(p2->dim_yb[i] &&
-       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_yb[i]))){
-      // make the x insensitive
-      if(p2->dim_xb[i])
-	_gtk_widget_set_sensitive_fixup(p2->dim_xb[i],FALSE);
-
-      // set the y dim
-      p->private->y_d = p->dimension_list[i].d;
-      p2->y_scale = p->private->dim_scales[i];
-      p2->y_dnum = i;
-    }else{
-      // if there is a x, make it sensitive 
-      if(p2->dim_xb[i])
-	_gtk_widget_set_sensitive_fixup(p2->dim_xb[i],TRUE);
-    }
-    if((p2->dim_xb[i] &&
-	gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_xb[i]))) ||
-       (p2->dim_yb[i] &&
-	gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_yb[i])))){
-      // make all thumbs visible 
-      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],0,1);
-      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],2,1);
-    }else{
-      // make bracket thumbs invisible */
-      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],0,0);
-      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],2,0);
-    }
-  } 
-}
-
-static int _v_swizzle(int y, int height){
-  int yy = height >> 5;
-  if(y < yy)
-    return (y<<5)+31;
-
-  y -= yy;
-  yy = (height+16) >> 5;
-  if(y < yy)
-    return (y<<5)+15;
-
-  y -= yy;
-  yy = (height+8) >> 4;
-  if(y < yy)
-    return (y<<4)+7;
-
-  y -= yy;
-  yy = (height+4) >> 3;
-  if(y < yy)
-    return (y<<3)+3;
-
-  y -= yy;
-  yy = (height+2) >> 2;
-  if(y < yy)
-    return (y<<2)+1;
-
-  y -= yy;
-  return y<<1;
-}
-
-// assumes data is locked
-static void _sv_panel2d_fast_scale_x(_sv_spinner_t *sp,
-				     int *data, 
-				     int w,
-				     int h,
-				     _sv_scalespace_t new,
-				     _sv_scalespace_t old){
-  int x,y;
-  int work[w];
-  int mapbase[w];
-  int mapdel[w];
-  double old_w = old.pixels;
-  double new_w = new.pixels;
-
-  double old_lo = _sv_scalespace_value(&old,0);
-  double old_hi = _sv_scalespace_value(&old,old_w);
-  double new_lo = _sv_scalespace_value(&new,0);
-  double new_hi = _sv_scalespace_value(&new,new_w);
-  double newscale = (new_hi-new_lo)/new_w;
-  double oldscale = old_w/(old_hi-old_lo);
-  for(x=0;x<w;x++){
-    double xval = (x)*newscale+new_lo;
-    double map = ((xval-old_lo)*oldscale);
-    int base = (int)floor(map);
-    int del = rint((map - floor(map))*64.f);
-    /* hack to overwhelm roundoff error; this is inside a purely
-       temporary cosmetic approximation anyway*/
-    if(base>0 && del==0){
-      mapbase[x]=base-1;
-      mapdel[x]=64;
-    }else{
-      mapbase[x]=base;
-      mapdel[x]=del;
-    }
-  }
-
-  for(y=0;y<h;y++){
-    int *data_line = data+y*w;
-    _sv_spinner_set_busy(sp);
-    for(x=0;x<w;x++){
-      if(mapbase[x]<0 || mapbase[x]>=(w-1)){
-	work[x]=-1;
-      }else{
-	int base = mapbase[x];
-	int A = data_line[base];
-	int B = data_line[base+1];
-	if(A<0 || B<0)
-	  work[x]=-1;
-	else
-	  work[x]= A + (((B - A)*mapdel[x])>>6);
-	
-      }
-    }
-    memcpy(data_line,work,w*(sizeof(*work)));
-  }   
-}
-
-static void _sv_panel2d_fast_scale_y(_sv_spinner_t *sp,
-				     int *olddata, 
-				     int *newdata, 
-				     int oldw,
-				     int neww,
-				     _sv_scalespace_t new,
-				     _sv_scalespace_t old){
-  int x,y;
-  int w = (oldw<neww?oldw:neww);
-
-  int old_h = old.pixels;
-  int new_h = new.pixels;
-
-  int mapbase[new_h];
-  int mapdel[new_h];
-
-  double old_lo = _sv_scalespace_value(&old,0);
-  double old_hi = _sv_scalespace_value(&old,(double)old_h);
-  double new_lo = _sv_scalespace_value(&new,0);
-  double new_hi = _sv_scalespace_value(&new,(double)new_h);
-  double newscale = (new_hi-new_lo)/new_h;
-  double oldscale = old_h/(old_hi-old_lo);
-  
-  for(y=0;y<new_h;y++){
-    double yval = (y)*newscale+new_lo;
-    double map = ((yval-old_lo)*oldscale);
-    int base = (int)floor(map);
-    int del = rint((map - floor(map))*64.);
-    /* hack to overwhelm roundoff error; this is inside a purely
-       temporary cosmetic approximation anyway */
-    if(base>0 && del==0){
-      mapbase[y]=base-1;
-      mapdel[y]=64;
-    }else{
-      mapbase[y]=base;
-      mapdel[y]=del;
-    }
-  }
-
-  
-  for(y=0;y<new_h;y++){
-    int base = mapbase[y];
-    int *new_column = &newdata[y*neww];
-    _sv_spinner_set_busy(sp);
-
-    if(base<0 || base>=(old_h-1)){
-      for(x=0;x<w;x++)
-	new_column[x] = -1;
-    }else{
-      int del = mapdel[y];
-      int *old_column = &olddata[base*oldw];
-
-      for(x=0;x<w;x++){
-	int A = old_column[x];
-	int B = old_column[x+oldw];
-	if(A<0 || B<0)
-	  new_column[x]=-1;
-	else
-	  new_column[x]= A + (((B-A)*del)>>6);
-      }
-    }
-  }
-}
-
-static void _sv_panel2d_fast_scale(_sv_spinner_t *sp, 
-				   int *newdata, 
-				   _sv_scalespace_t xnew,
-				   _sv_scalespace_t ynew,
-				   int *olddata,
-				   _sv_scalespace_t xold,
-				   _sv_scalespace_t yold){
-  
-  int new_w = xnew.pixels;
-  int new_h = ynew.pixels;
-  int old_w = xold.pixels;
-  int old_h = yold.pixels;
-
-  if(new_w > old_w){
-    _sv_panel2d_fast_scale_y(sp,olddata,newdata,old_w,new_w,ynew,yold);
-    _sv_panel2d_fast_scale_x(sp,newdata,new_w,new_h,xnew,xold);
-  }else{
-    _sv_panel2d_fast_scale_x(sp,olddata,old_w,old_h,xnew,xold);
-    _sv_panel2d_fast_scale_y(sp,olddata,newdata,old_w,new_w,ynew,yold);
-  }
-}
-
-// call only from main gtk thread
-static void _sv_panel2d_mark_recompute(sv_panel_t *p){
-  if(!p->private->realized) return;
-  _sv_plot_t *plot = PLOT(p->private->graph);
-
-  if(plot && GTK_WIDGET_REALIZED(GTK_WIDGET(plot))){
-    _sv_panel_dirty_plot(p);
-  }
-}
-
-static void _sv_panel2d_update_crosshairs(sv_panel_t *p){
-  _sv_plot_t *plot = PLOT(p->private->graph);
-  double x=0,y=0;
-  int i;
-  
-  for(i=0;i<p->dimensions;i++){
-    sv_dim_t *d = p->dimension_list[i].d;
-    if(d == p->private->x_d)
-      x = d->val;
-    if(d == p->private->y_d)
-      y = d->val;
-    
-  }
-  
-  _sv_plot_set_crosshairs(plot,x,y);
-  _sv_panel_dirty_legend(p);
-}
-
-static void _sv_panel2d_center_callback(sv_dim_list_t *dptr){
-  sv_dim_t *d = dptr->d;
-  sv_panel_t *p = dptr->p;
-  int axisp = (d == p->private->x_d || d == p->private->y_d);
-
-  if(!axisp){
-    // mid slider of a non-axis dimension changed, rerender
-    _sv_panel2d_mark_recompute(p);
-  }else{
-    // mid slider of an axis dimension changed, move crosshairs
-    _sv_panel2d_update_crosshairs(p);
-  }
-}
-
-static void _sv_panel2d_bracket_callback(sv_dim_list_t *dptr){
-  sv_dim_t *d = dptr->d;
-  sv_panel_t *p = dptr->p;
-  int axisp = (d == p->private->x_d || d == p->private->y_d);
-
-  if(axisp)
-    _sv_panel2d_mark_recompute(p);
-    
-}
-
-static void _sv_panel2d_dimchange_callback(GtkWidget *button,gpointer in){
-  sv_panel_t *p = (sv_panel_t *)in;
-
-  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))){
-
-    _sv_undo_push();
-    _sv_undo_suspend();
-
-    _sv_plot_unset_box(PLOT(p->private->graph));
-    _sv_panel2d_update_xysel(p);
-
-    _sv_panel2d_clear_pane(p);
-    _sv_panel2d_mark_recompute(p);
-    _sv_panel2d_update_crosshairs(p);
-
-    _sv_undo_resume();
-  }
-}
-
-static void _sv_panel2d_crosshairs_callback(sv_panel_t *p){
-  double x=PLOT(p->private->graph)->selx;
-  double y=PLOT(p->private->graph)->sely;
-  int i;
-  
-  _sv_undo_push();
-  _sv_undo_suspend();
-
-  //plot_snap_crosshairs(PLOT(p->private->graph));
-
-  for(i=0;i<p->dimensions;i++){
-    sv_dim_t *d = p->dimension_list[i].d;
-    if(d == p->private->x_d){
-      _sv_dim_widget_set_thumb(p->private->dim_scales[i],1,x);
-    }
-
-    if(d == p->private->y_d){
-      _sv_dim_widget_set_thumb(p->private->dim_scales[i],1,y);
-    }
-    
-    p->private->oldbox_active = 0;
-  }
-
-  // dimension setting might have enforced granularity restrictions;
-  // have the display reflect that
-  x = p->private->x_d->val;
-  y = p->private->y_d->val;
-
-  _sv_plot_set_crosshairs(PLOT(p->private->graph),x,y);
-
-  _sv_panel_dirty_legend(p);
-  _sv_undo_resume();
-}
-
-static void _sv_panel2d_box_callback(void *in, int state){
-  sv_panel_t *p = (sv_panel_t *)in;
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  _sv_plot_t *plot = PLOT(p->private->graph);
-  
-  switch(state){
-  case 0: // box set
-    _sv_undo_push();
-    _sv_plot_box_vals(plot,p2->oldbox);
-    p->private->oldbox_active = plot->box_active;
-    break;
-  case 1: // box activate
-    _sv_undo_push();
-    _sv_undo_suspend();
-
-    _sv_panel2d_crosshairs_callback(p);
-
-    _sv_dim_widget_set_thumb(p2->x_scale,0,p2->oldbox[0]);
-    _sv_dim_widget_set_thumb(p2->x_scale,2,p2->oldbox[1]);
-    _sv_dim_widget_set_thumb(p2->y_scale,0,p2->oldbox[2]);
-    _sv_dim_widget_set_thumb(p2->y_scale,2,p2->oldbox[3]);
-    p->private->oldbox_active = 0;
-    _sv_undo_resume();
-    break;
-  }
-  _sv_panel_update_menus(p);
-}
-
-void _sv_panel2d_maintain_cache(sv_panel_t *p, _sv_bythread_cache_2d_t *c, int w){
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  
-  /* toplevel initialization */
-  if(c->fout == 0){
-    int i,count=0;
-    
-    /* allocate output temporary buffer */
-    for(i=0;i<p2->used_functions;i++){
-      int fnum = p2->used_function_list[i]->number;
-      sv_func_t *f = _sv_function_list[fnum];
-      count += f->outputs;
-    }
-    c->fout = calloc(count, sizeof(*c->fout));
-
-    /* objective line buffer index */
-    c->y_map = calloc(p2->y_obj_num,sizeof(*c->y_map));
-    for(i=0;i<p2->y_obj_num;i++)
-      c->y_map[i] = calloc(w,sizeof(**c->y_map));
-    c->storage_width = w;
-  }
-  
-  /* anytime the data width changes */
-  if(c->storage_width != w){
-    int i;
-    c->storage_width = w;
-    
-    for(i=0;i<p2->y_obj_num;i++)
-      c->y_map[i] = realloc(c->y_map[i],w*sizeof(**c->y_map));
-
-  }
-}
-
-
-// subtype entry point for plot remaps; lock held
-static int _sv_panel2d_map_redraw(sv_panel_t *p, _sv_bythread_cache_t *c){
-  return _sv_panel2d_remap(p,&c->p2);
-}
-
-// subtype entry point for legend redraws; lock held
-static int _sv_panel2d_legend_redraw(sv_panel_t *p){
-  _sv_plot_t *plot = PLOT(p->private->graph);
-
-  if(p->private->legend_progress_count)return 0;
-  p->private->legend_progress_count++;
-  _sv_panel2d_update_legend(p);
-  _sv_panel_clean_legend(p);
-
-  gdk_unlock();
-  _sv_plot_draw_scales(plot);
-  gdk_lock();
-
-  _sv_plot_expose_request(plot);
-  return 1;
-}
-
-// subtype entry point for recomputation; lock held
-static int _sv_panel2d_compute(sv_panel_t *p,
-			       _sv_bythread_cache_t *c){
-
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  _sv_plot_t *plot;
-  
-  int pw,ph,dw,dh,i,d;
-  int serialno;
-  double x_min, x_max;
-  double y_min, y_max;
-  int x_d=-1, y_d=-1;
-  _sv_scalespace_t sx,sx_v,sx_i;
-  _sv_scalespace_t sy,sy_v,sy_i;
-
-  plot = PLOT(p->private->graph);
-  pw = plot->x.pixels;
-  ph = plot->y.pixels;
-  
-  x_d = p->private->x_d->number;
-  y_d = p->private->y_d->number;
-
-  // beginning of computation init
-  if(p->private->plot_progress_count==0){
-    int remapflag = 0;
-
-    _sv_scalespace_t old_x = p2->x;
-    _sv_scalespace_t old_y = p2->y;
-    _sv_scalespace_t old_xv = p2->x_v;
-    _sv_scalespace_t old_yv = p2->y_v;
-
-    // generate new scales
-    _sv_dim_scales(p->private->x_d, 
-		   p->private->x_d->bracket[0],
-		   p->private->x_d->bracket[1],
-		   pw,pw * p->private->oversample_n / p->private->oversample_d,
-		   plot->scalespacing,
-		   p->private->x_d->legend,
-		   &sx,
-		   &sx_v,
-		   &sx_i);
-    _sv_dim_scales(p->private->y_d, 
-		   p->private->y_d->bracket[1],
-		   p->private->y_d->bracket[0],
-		   ph,ph * p->private->oversample_n / p->private->oversample_d,
-		   plot->scalespacing,
-		   p->private->y_d->legend,
-		   &sy,
-		   &sy_v,
-		   &sy_i);
-    
-    p2->x = sx;
-    p2->x_v = sx_v;
-    p2->x_i = sx_i;
-    p2->y = sy;
-    p2->y_v = sy_v;
-    p2->y_i = sy_i;
-
-    plot->x = sx;
-    plot->y = sy;
-    plot->x_v = sx_v;
-    plot->y_v = sy_v;
-
-    p->private->plot_progress_count++;
-    p->private->plot_serialno++; // we're about to free the old data rectangles
-
-    // realloc/fast scale the current data contents if appropriate
-    if(memcmp(&sx_v,&old_xv,sizeof(sx_v)) || memcmp(&sy_v,&old_yv,sizeof(sy_v))){
-      
-      // maintain data planes
-      for(i=0;i<p2->y_obj_num;i++){
-	// allocate new storage
-	int *newmap = calloc(sx_v.pixels*sy_v.pixels,sizeof(*newmap));
-	int *oldmap = p2->y_map[i];
-	int j;
-	
-	for(j=0;j<sx_v.pixels*sy_v.pixels;j++)
-	  newmap[j]=-1;
-	
-	// zoom scale data in map planes as placeholder for render
-	if(oldmap){
-	  _sv_panel2d_fast_scale(p->private->spinner,newmap, sx_v, sy_v,
-				 oldmap,old_xv, old_yv);
-	  free(oldmap);
-	}
-	p2->y_map[i] = newmap; 
-      }
-      remapflag = 1;
-    }
-
-    // realloc render planes if appropriate
-    if(memcmp(&sx,&old_x,sizeof(sx)) || memcmp(&sy,&old_y,sizeof(sy))){
-      for(i=0;i<p2->y_obj_num;i++){
-
-	// y planes
-	if(p2->y_planes[i])
-	  free(p2->y_planes[i]);
-	p2->y_planes[i] = calloc(sx.pixels*sy.pixels,sizeof(**p2->y_planes));
-
-	// todo lists
-	if(p2->y_planetodo[i])
-	  free(p2->y_planetodo[i]);
-	p2->y_planetodo[i] = calloc(sy.pixels,sizeof(**p2->y_planetodo));
-	
-      }
-
-      if(p2->bg_todo)
-	free(p2->bg_todo);
-      p2->bg_todo=calloc(ph,sizeof(*p2->bg_todo));
-      
-      remapflag = 1;
-    }
-
-    if(remapflag){
-      _sv_panel2d_mark_map_full(p);
-      _sv_panel_dirty_map(p);
-
-      gdk_unlock ();      
-      _sv_plot_draw_scales(plot); // this should happen outside lock
-      gdk_lock ();      
-    }
-
-    _sv_map_set_throttle_time(p); // swallow the first 'throttled' remap which would only be a single line;
-
-    return 1;
-  }else{
-    sx = p2->x;
-    sx_v = p2->x_v;
-    sx_i = p2->x_i;
-    sy = p2->y;
-    sy_v = p2->y_v;
-    sy_i = p2->y_i;
-    serialno = p->private->plot_serialno; 
-  }
-
-  dw = sx_v.pixels;
-  dh = sy_v.pixels;
-
-  if(p->private->plot_progress_count>dh) return 0;
-
-  _sv_panel2d_maintain_cache(p,&c->p2,dw);
-  
-  d = p->dimensions;
-
-  /* render using local dimension array; several threads will be
-     computing objectives */
-  double dim_vals[_sv_dimensions];
-  int y = _v_swizzle(p->private->plot_progress_count-1,dh);
-  p->private->plot_progress_count++;
-
-  x_min = _sv_scalespace_value(&p2->x_i,0);
-  x_max = _sv_scalespace_value(&p2->x_i,dw);
-
-  y_min = _sv_scalespace_value(&p2->y_i,0);
-  y_max = _sv_scalespace_value(&p2->y_i,dh);
-
-  // Initialize local dimension value array
-  for(i=0;i<_sv_dimensions;i++){
-    sv_dim_t *dim = _sv_dimension_list[i];
-    dim_vals[i]=dim->val;
-  }
-
-  /* unlock for computation */
-  gdk_unlock ();
-    
-  dim_vals[y_d]=_sv_scalespace_value(&sy_i, y);
-  _sv_panel2d_compute_line(p, serialno, dw, y, x_d, sx_i, dim_vals, &c->p2);
-
-  gdk_lock ();
-
-  if(p->private->plot_serialno == serialno){
-    p->private->plot_complete_count++;
-    _sv_panel2d_mark_map_line_y(p,y);
-    if(p->private->plot_complete_count>=dh){ 
-      _sv_panel_dirty_map(p);
-      _sv_panel_dirty_legend(p);
-      _sv_panel_clean_plot(p);
-    }else
-      _sv_panel_dirty_map_throttled(p); 
-  }
-
-  return 1;
-}
-
-// only called for resize events
-static void _sv_panel2d_recompute_callback(void *ptr){
-  sv_panel_t *p = (sv_panel_t *)ptr;
-  int i;
-
-  gdk_lock ();
-  _sv_panel2d_mark_recompute(p);
-  _sv_panel2d_compute(p,NULL); // initial scale setup
-
-  // temporary: blank background to checks
-  _sv_plot_t *plot = PLOT(p->private->graph);
-  int pw = plot->x.pixels;
-  int ph = plot->y.pixels;
-  for(i=0;i<ph;i++)
-    render_checks((_sv_ucolor_t *)plot->datarect+pw*i, pw, i);
-  
-  gdk_unlock();
-}
-
-static void _sv_panel2d_undo_log(_sv_panel_undo_t *u, sv_panel_t *p){
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  int i;
-
-  // alloc fields as necessary
-  
-  if(!u->mappings)
-    u->mappings =  calloc(p->objectives,sizeof(*u->mappings));
-  if(!u->scale_vals[0])
-    u->scale_vals[0] =  calloc(p->objectives,sizeof(**u->scale_vals));
-  if(!u->scale_vals[1])
-    u->scale_vals[1] =  calloc(p->objectives,sizeof(**u->scale_vals));
-  if(!u->scale_vals[2])
-    u->scale_vals[2] =  calloc(p->objectives,sizeof(**u->scale_vals));
-
-  // populate undo
-  for(i=0;i<p->objectives;i++){
-    u->mappings[i] = p2->mappings[i].mapnum;
-    u->scale_vals[0][i] = _sv_slider_get_value(p2->range_scales[i],0);
-    u->scale_vals[1][i] = _sv_slider_get_value(p2->range_scales[i],1);
-    u->scale_vals[2][i] = _sv_slider_get_value(p2->range_scales[i],2);
-  }
-  
-  u->x_d = p2->x_dnum;
-  u->y_d = p2->y_dnum;
-  u->box[0] = p2->oldbox[0];
-  u->box[1] = p2->oldbox[1];
-  u->box[2] = p2->oldbox[2];
-  u->box[3] = p2->oldbox[3];
-  u->box_active = p->private->oldbox_active;
-}
-
-static void _sv_panel2d_undo_restore(_sv_panel_undo_t *u, sv_panel_t *p){
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  _sv_plot_t *plot = PLOT(p->private->graph);
-  int i;
-  
-  // go in through widgets
-  for(i=0;i<p->objectives;i++){
-    gtk_combo_box_set_active(GTK_COMBO_BOX(p2->range_pulldowns[i]),u->mappings[i]);
-    _sv_slider_set_value(p2->range_scales[i],0,u->scale_vals[0][i]);
-    _sv_slider_set_value(p2->range_scales[i],1,u->scale_vals[1][i]);
-    _sv_slider_set_value(p2->range_scales[i],2,u->scale_vals[2][i]);
-  }
-
-  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_xb[u->x_d]),TRUE);
-  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_yb[u->y_d]),TRUE);
-
-  _sv_panel2d_update_xysel(p);
-
-  if(u->box_active){
-    p2->oldbox[0] = u->box[0];
-    p2->oldbox[1] = u->box[1];
-    p2->oldbox[2] = u->box[2];
-    p2->oldbox[3] = u->box[3];
-    _sv_plot_box_set(plot,u->box);
-    p->private->oldbox_active = 1;
-  }else{
-    _sv_plot_unset_box(plot);
-    p->private->oldbox_active = 0;
-  }
-}
-
-static void _sv_panel2d_realize(sv_panel_t *p){
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  int i;
-
-  _sv_undo_suspend();
-
-  p->private->toplevel = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-  g_signal_connect_swapped (G_OBJECT (p->private->toplevel), "delete-event",
-			    G_CALLBACK (_sv_clean_exit), (void *)SIGINT);
- 
-  // add border to sides with hbox/padding
-  GtkWidget *borderbox =  gtk_hbox_new(0,0);
-  gtk_container_add (GTK_CONTAINER (p->private->toplevel), borderbox);
-
-  // main layout vbox
-  p->private->topbox = gtk_vbox_new(0,0);
-  gtk_box_pack_start(GTK_BOX(borderbox), p->private->topbox, 1,1,4);
-  gtk_container_set_border_width (GTK_CONTAINER (p->private->toplevel), 1);
-
-  /* spinner, top bar */
-  {
-    GtkWidget *hbox = gtk_hbox_new(0,0);
-    gtk_box_pack_start(GTK_BOX(p->private->topbox), hbox, 0,0,0);
-    gtk_box_pack_end(GTK_BOX(hbox),GTK_WIDGET(p->private->spinner),0,0,0);
-  }
-
-  /* plotbox, graph */
-  {
-    p->private->graph = GTK_WIDGET(_sv_plot_new(_sv_panel2d_recompute_callback,p,
-						(void *)(void *)_sv_panel2d_crosshairs_callback,p,
-						_sv_panel2d_box_callback,p,0)); 
-    p->private->plotbox = p->private->graph;
-    gtk_box_pack_start(GTK_BOX(p->private->topbox), p->private->plotbox, 1,1,2);
-  }
-
-  /* obj box */
-  {
-    p2->obj_table = gtk_table_new(p->objectives, 5, 0);
-    gtk_box_pack_start(GTK_BOX(p->private->topbox), p2->obj_table, 0,0,1);
-
-    /* objective sliders */
-    p2->range_scales = calloc(p->objectives,sizeof(*p2->range_scales));
-    p2->range_pulldowns = calloc(p->objectives,sizeof(*p2->range_pulldowns));
-    p2->alphadel = calloc(p->objectives,sizeof(*p2->alphadel));
-    p2->mappings = calloc(p->objectives,sizeof(*p2->mappings));
-    for(i=0;i<p->objectives;i++){
-      GtkWidget **sl = calloc(3,sizeof(*sl));
-      sv_obj_t *o = p->objective_list[i].o;
-      int lo = o->scale->val_list[0];
-      int hi = o->scale->val_list[o->scale->vals-1];
-      
-      /* label */
-      GtkWidget *label = gtk_label_new(o->name);
-      gtk_misc_set_alignment(GTK_MISC(label),1.,.5);
-      gtk_table_attach(GTK_TABLE(p2->obj_table),label,0,1,i,i+1,
-		       GTK_FILL,0,8,0);
-      
-      /* mapping pulldown */
-      {
-	GtkWidget *menu=_gtk_combo_box_new_markup();
-	int j;
-	for(j=0;j<_sv_mapping_names();j++)
-	  gtk_combo_box_append_text (GTK_COMBO_BOX (menu), _sv_mapping_name(j));
-	gtk_combo_box_set_active(GTK_COMBO_BOX(menu),0);
-	g_signal_connect (G_OBJECT (menu), "changed",
-			  G_CALLBACK (_sv_panel2d_mapchange_callback), p->objective_list+i);
-	gtk_table_attach(GTK_TABLE(p2->obj_table),menu,4,5,i,i+1,
-			 GTK_SHRINK,GTK_SHRINK,0,0);
-	p2->range_pulldowns[i] = menu;
-      }
-
-      /* the range mapping slices/slider */ 
-      sl[0] = _sv_slice_new(_sv_panel2d_map_callback,p->objective_list+i);
-      sl[1] = _sv_slice_new(_sv_panel2d_map_callback,p->objective_list+i);
-      sl[2] = _sv_slice_new(_sv_panel2d_map_callback,p->objective_list+i);
-      
-      gtk_table_attach(GTK_TABLE(p2->obj_table),sl[0],1,2,i,i+1,
-		       GTK_EXPAND|GTK_FILL,0,0,0);
-      gtk_table_attach(GTK_TABLE(p2->obj_table),sl[1],2,3,i,i+1,
-		       GTK_EXPAND|GTK_FILL,0,0,0);
-      gtk_table_attach(GTK_TABLE(p2->obj_table),sl[2],3,4,i,i+1,
-		       GTK_EXPAND|GTK_FILL,0,0,0);
-      p2->range_scales[i] = _sv_slider_new((_sv_slice_t **)sl,3,o->scale->label_list,o->scale->val_list,
-				       o->scale->vals,_SV_SLIDER_FLAG_INDEPENDENT_MIDDLE);
-      gtk_table_set_col_spacing(GTK_TABLE(p2->obj_table),3,5);
-
-      _sv_slice_thumb_set((_sv_slice_t *)sl[0],lo);
-      _sv_slice_thumb_set((_sv_slice_t *)sl[1],lo);
-      _sv_slice_thumb_set((_sv_slice_t *)sl[2],hi);
-      _sv_mapping_setup(&p2->mappings[i],0.,1.,0);
-      _sv_slider_set_gradient(p2->range_scales[i], &p2->mappings[i]);
-    }
-  }
-
-  /* dims */
-  {
-    p2->dim_table = gtk_table_new(p->dimensions,4,0);
-    gtk_box_pack_start(GTK_BOX(p->private->topbox), p2->dim_table, 0,0,4);
-    
-    GtkWidget *first_x = NULL;
-    GtkWidget *first_y = NULL;
-    GtkWidget *pressed_y = NULL;
-    p->private->dim_scales = calloc(p->dimensions,sizeof(*p->private->dim_scales));
-    p2->dim_xb = calloc(p->dimensions,sizeof(*p2->dim_xb));
-    p2->dim_yb = calloc(p->dimensions,sizeof(*p2->dim_yb));
-    
-    for(i=0;i<p->dimensions;i++){
-      sv_dim_t *d = p->dimension_list[i].d;
-      
-      /* label */
-      GtkWidget *label = gtk_label_new(d->legend);
-      gtk_misc_set_alignment(GTK_MISC(label),1.,.5);
-      gtk_table_attach(GTK_TABLE(p2->dim_table),label,0,1,i,i+1,
-		       GTK_FILL,0,5,0);
-      
-      /* x/y radio buttons */
-      if(!(d->flags & SV_DIM_NO_X)){
-	if(first_x)
-	  p2->dim_xb[i] = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(first_x),"X");
-	else{
-	  first_x = p2->dim_xb[i] = gtk_radio_button_new_with_label(NULL,"X");
-	  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_xb[i]),TRUE);
-	}
-	gtk_table_attach(GTK_TABLE(p2->dim_table),p2->dim_xb[i],1,2,i,i+1,
-			 GTK_SHRINK,0,3,0);
-      }
-      
-      if(!(d->flags & SV_DIM_NO_Y)){
-	if(first_y)
-	  p2->dim_yb[i] = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(first_y),"Y");
-	else
-	  first_y = p2->dim_yb[i] = gtk_radio_button_new_with_label(NULL,"Y");
-	if(!pressed_y && p2->dim_xb[i]!=first_x){
-	  pressed_y = p2->dim_yb[i];
-	  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_yb[i]),TRUE);
-	}
-	gtk_table_attach(GTK_TABLE(p2->dim_table),p2->dim_yb[i],2,3,i,i+1,
-			 GTK_SHRINK,0,3,0);
-      }
-      
-      p->private->dim_scales[i] = 
-	_sv_dim_widget_new(p->dimension_list+i,_sv_panel2d_center_callback,_sv_panel2d_bracket_callback);
-      
-      gtk_table_attach(GTK_TABLE(p2->dim_table),
-		       p->private->dim_scales[i]->t,
-		       3,4,i,i+1,
-		       GTK_EXPAND|GTK_FILL,0,0,0);
-      
-    }
-    for(i=0;i<p->dimensions;i++){
-      if(p2->dim_xb[i])
-	g_signal_connect (G_OBJECT (p2->dim_xb[i]), "toggled",
-			  G_CALLBACK (_sv_panel2d_dimchange_callback), p);
-      if(p2->dim_yb[i])
-	g_signal_connect (G_OBJECT (p2->dim_yb[i]), "toggled",
-			  G_CALLBACK (_sv_panel2d_dimchange_callback), p);
-    }
-  }
-
-  _sv_panel2d_update_xysel(p);
-
-  gtk_widget_realize(p->private->toplevel);
-  gtk_widget_realize(p->private->graph);
-  gtk_widget_realize(GTK_WIDGET(p->private->spinner));
-  gtk_widget_show_all(p->private->toplevel);
-  _sv_panel2d_update_xysel(p); // yes, this was already done; however,
-			       // gtk clobbered the event setup on the
-			       // insensitive buttons when it realized
-			       // them.  This call will restore them.
-
-  _sv_undo_resume();
-}
-
-static int _sv_panel2d_save(sv_panel_t *p, xmlNodePtr pn){  
-  _sv_panel2d_t *p2 = p->subtype->p2;
-  int ret=0,i;
-
-  xmlNodePtr n;
-
-  xmlNewProp(pn, (xmlChar *)"type", (xmlChar *)"2d");
-
-  // box
-  if(p->private->oldbox_active){
-    xmlNodePtr boxn = xmlNewChild(pn, NULL, (xmlChar *) "box", NULL);
-    _xmlNewPropF(boxn, "x1", p2->oldbox[0]);
-    _xmlNewPropF(boxn, "x2", p2->oldbox[1]);
-    _xmlNewPropF(boxn, "y1", p2->oldbox[2]);
-    _xmlNewPropF(boxn, "y2", p2->oldbox[3]);
-  }
-
-  // objective map settings
-  for(i=0;i<p->objectives;i++){
-    sv_obj_t *o = p->objective_list[i].o;
-    xmlNodePtr on = xmlNewChild(pn, NULL, (xmlChar *) "objective", NULL);
-    _xmlNewPropI(on, "position", i);
-    _xmlNewPropI(on, "number", o->number);
-    _xmlNewPropS(on, "name", o->name);
-    _xmlNewPropS(on, "type", o->output_types);
-    
-    // right now Y is the only type; the below is Y-specific
-    n = xmlNewChild(on, NULL, (xmlChar *) "y-map", NULL);
-    _xmlNewPropS(n, "color", _sv_mapping_name(p2->mappings[i].mapnum));
-    _xmlNewPropF(n, "low-bracket", _sv_slider_get_value(p2->range_scales[i],0));
-    _xmlNewPropF(n, "alpha", _sv_slider_get_value(p2->range_scales[i],1));
-    _xmlNewPropF(n, "high-bracket", _sv_slider_get_value(p2->range_scales[i],2));
-  }
-
-  // x/y dim selection
-  n = xmlNewChild(pn, NULL, (xmlChar *) "axes", NULL);
-  _xmlNewPropI(n, "xpos", p2->x_dnum);
-  _xmlNewPropI(n, "ypos", p2->y_dnum);
-
-  return ret;
-}
-
-int _sv_panel2d_load(sv_panel_t *p,
-		     _sv_panel_undo_t *u,
-		     xmlNodePtr pn,
-		     int warn){
-  int i;
-
-  // check type
-  _xmlCheckPropS(pn,"type","2d", "Panel %d type mismatch in save file.",p->number,&warn);
-  
-  // box
-  u->box_active = 0;
-  _xmlGetChildPropFPreserve(pn, "box", "x1", &u->box[0]);
-  _xmlGetChildPropFPreserve(pn, "box", "x2", &u->box[1]);
-  _xmlGetChildPropFPreserve(pn, "box", "y1", &u->box[2]);
-  _xmlGetChildPropFPreserve(pn, "box", "y2", &u->box[3]);
-
-  xmlNodePtr n = _xmlGetChildS(pn, "box", NULL, NULL);
-  if(n){
-    u->box_active = 1;
-    xmlFree(n);
-  }
-  
-  // objective map settings
-  for(i=0;i<p->objectives;i++){
-    sv_obj_t *o = p->objective_list[i].o;
-    xmlNodePtr on = _xmlGetChildI(pn, "objective", "position", i);
-    if(!on){
-      _sv_first_load_warning(&warn);
-      fprintf(stderr,"No save data found for panel %d objective \"%s\".\n",p->number, o->name);
-    }else{
-      // check name, type
-      _xmlCheckPropS(on,"name",o->name, "Objectve position %d name mismatch in save file.",i,&warn);
-      _xmlCheckPropS(on,"type",o->output_types, "Objectve position %d type mismatch in save file.",i,&warn);
-      
-      // right now Y is the only type; the below is Y-specific
-      // load maptype, values
-      _xmlGetChildPropFPreserve(on, "y-map", "low-bracket", &u->scale_vals[0][i]);
-      _xmlGetChildPropFPreserve(on, "y-map", "alpha", &u->scale_vals[1][i]);
-      _xmlGetChildPropFPreserve(on, "y-map", "high-bracket", &u->scale_vals[2][i]);
-      _xmlGetChildMap(on, "y-map", "color", _sv_mapping_map(), &u->mappings[i],
-		     "Panel %d objective unknown mapping setting", p->number, &warn);
-
-      xmlFreeNode(on);
-    }
-  }
-
-  // x/y dim selection
-  _xmlGetChildPropIPreserve(pn, "axes", "xpos", &u->x_d);
-  _xmlGetChildPropI(pn, "axes", "ypos", &u->y_d);
-
-  return warn;
-}
-
-sv_panel_t *sv_panel_new_2d(int number,
-			    char *name, 
-			    char *objectivelist,
-			    char *dimensionlist,
-			    unsigned flags){
-  
-  int i,j;
-  sv_panel_t *p = _sv_panel_new(number,name,objectivelist,dimensionlist,flags);
-  if(!p)return NULL;
-
-  _sv_panel2d_t *p2 = calloc(1, sizeof(*p2));
-  int fout_offsets[_sv_functions];
-  
-  p->subtype = 
-    calloc(1, sizeof(*p->subtype)); /* the union is alloced not
-				       embedded as its internal
-				       structure must be hidden */  
-  p->subtype->p2 = p2;
-  p->type = SV_PANEL_2D;
-  p->private->bg_type = SV_BG_CHECKS;
-
-  // verify all the objectives have scales
-  for(i=0;i<p->objectives;i++){
-    if(!p->objective_list[i].o->scale){
-      fprintf(stderr,"All objectives in a 2d panel must have a scale\n");
-      errno = -EINVAL;
-      return NULL;
-    }
-  }
-
-  p->private->realize = _sv_panel2d_realize;
-  p->private->map_action = _sv_panel2d_map_redraw;
-  p->private->legend_action = _sv_panel2d_legend_redraw;
-  p->private->compute_action = _sv_panel2d_compute;
-  p->private->request_compute = _sv_panel2d_mark_recompute;
-  p->private->crosshair_action = _sv_panel2d_crosshairs_callback;
-  p->private->print_action = _sv_panel2d_print;
-  p->private->undo_log = _sv_panel2d_undo_log;
-  p->private->undo_restore = _sv_panel2d_undo_restore;
-  p->private->save_action = _sv_panel2d_save;
-  p->private->load_action = _sv_panel2d_load;
-
-  /* set up helper data structures for rendering */
-
-  /* determine which functions are actually needed; if it's referenced
-     by an objective, it's used.  Precache them in dense form. */
-  {
-    int fn = _sv_functions;
-    int used[fn],count=0,offcount=0;
-    memset(used,0,sizeof(used));
-    memset(fout_offsets,-1,sizeof(fout_offsets));
-    
-    for(i=0;i<p->objectives;i++){
-      sv_obj_t *o = p->objective_list[i].o;
-      for(j=0;j<o->outputs;j++)
-	used[o->function_map[j]]=1;
-    }
-
-    for(i=0;i<fn;i++)
-      if(used[i]){
-	sv_func_t *f = _sv_function_list[i];
-	fout_offsets[i] = offcount;
-	offcount += f->outputs;
-	count++;
-      }
-
-    p2->used_functions = count;
-    p2->used_function_list = calloc(count, sizeof(*p2->used_function_list));
-
-    for(count=0,i=0;i<fn;i++)
-     if(used[i]){
-        p2->used_function_list[count]=_sv_function_list[i];
-	count++;
-      }
-  }
-
-  /* set up computation/render helpers for Y planes */
-
-  /* set up Y object mapping index */
-  {
-    int yobj_count = 0;
-
-    for(i=0;i<p->objectives;i++){
-      sv_obj_t *o = p->objective_list[i].o;
-      if(o->private->y_func) yobj_count++;
-    }
-
-    p2->y_obj_num = yobj_count;
-    p2->y_obj_list = calloc(yobj_count, sizeof(*p2->y_obj_list));
-    p2->y_obj_to_panel = calloc(yobj_count, sizeof(*p2->y_obj_to_panel));
-    p2->y_obj_from_panel = calloc(p->objectives, sizeof(*p2->y_obj_from_panel));
-    
-    yobj_count=0;
-    for(i=0;i<p->objectives;i++){
-      sv_obj_t *o = p->objective_list[i].o;
-      if(o->private->y_func){
-	p2->y_obj_list[yobj_count] = o;
-	p2->y_obj_to_panel[yobj_count] = i;
-	p2->y_obj_from_panel[i] = yobj_count;
-	yobj_count++;
-      }else
-	p2->y_obj_from_panel[i] = -1;
-      
-    }
-  }
-  
-  /* set up function Y output value demultiplex helper */
-  {
-    p2->y_fout_offset = calloc(p2->y_obj_num, sizeof(*p2->y_fout_offset));
-    for(i=0;i<p2->y_obj_num;i++){
-      sv_obj_t *o = p2->y_obj_list[i];
-      int funcnum = o->private->y_func->number;
-      p2->y_fout_offset[i] = fout_offsets[funcnum] + o->private->y_fout;
-    }
-  }
-
-  p2->y_map = calloc(p2->y_obj_num,sizeof(*p2->y_map));
-  p2->y_planetodo = calloc(p2->y_obj_num,sizeof(*p2->y_planetodo));
-  p2->y_planes = calloc(p2->y_obj_num,sizeof(*p2->y_planes));
-
-  return p;
-}

Deleted: trunk/sushivision/panel-2d.h
===================================================================
--- trunk/sushivision/panel-2d.h	2007-10-17 02:35:48 UTC (rev 14004)
+++ trunk/sushivision/panel-2d.h	2007-10-17 03:18:34 UTC (rev 14005)
@@ -1,67 +0,0 @@
-/*
- *
- *     sushivision copyright (C) 2006-2007 Monty <monty at xiph.org>
- *
- *  sushivision is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2, or (at your option)
- *  any later version.
- *   
- *  sushivision is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *   
- *  You should have received a copy of the GNU General Public License
- *  along with sushivision; see the file COPYING.  If not, write to the
- *  Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- * 
- */
-
-typedef struct {
-  pthread_rwlock_t panel_m;
-  pthread_mutex_t  status_m;
-  
-  // mem and data locked by status_m
-  int               recompute_pending;
-  int               dims; 
-  int               w; 
-  int               h;
-  sv_dim_data_t    *dim_data;
-
-  // axis 0 == X 
-  // axis 1 == Y 
-  // axis 2 == Z
-  // >2 == auxiliary axes
-  char            **axis_names;
-  int              *axis_dims;
-  int               axes;
-
-  // locked by panel_m
-  _sv_plane_bg_t *bg; // composite background plane
-
-  int planes;
-  _sv_plane_t **plane_list;
-  int next_plane; 
-
-  GtkWidget *obj_table;
-  GtkWidget *dim_table;
-
-  double oldbox[4];
-
-  GtkWidget **dim_xb; // X axis selector buttons
-  GtkWidget **dim_yb; // Y axis selector buttons
-
-  _sv_dim_widget_t *x_scale; // pointer to current X axis dimwidget
-  _sv_dim_widget_t *y_scale; // pointer to current Y axis dimwidget
-  //_sv_dim_widget_t *z_scale; // pointer to current Z axis dimwidget
-
-} _sv_panel2d_t;
-
-typedef struct {
-  double *fout; 
-  int fout_size;
-
-
-} _sv_bythread_cache_2d_t;

Modified: trunk/sushivision/panel.c
===================================================================
--- trunk/sushivision/panel.c	2007-10-17 02:35:48 UTC (rev 14004)
+++ trunk/sushivision/panel.c	2007-10-17 03:18:34 UTC (rev 14005)
@@ -30,759 +30,2287 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <gtk/gtk.h>
-#include <gdk/gdkkeysyms.h>
 #include <cairo-ft.h>
 #include "internal.h"
 
-extern void _sv_wake_workers(void);
-static void wrap_exit(sv_panel_t *dummy, GtkWidget *dummyw);
-static void wrap_bg(sv_panel_t *p, GtkWidget *w);
-static void wrap_grid(sv_panel_t *p, GtkWidget *w);
-static void wrap_text(sv_panel_t *p, GtkWidget *w);
-static void wrap_res(sv_panel_t *p, GtkWidget *w);
-static void wrap_load(sv_panel_t *p, GtkWidget *dummy);
-static void wrap_save(sv_panel_t *p, GtkWidget *dummy);
-static void _sv_panel_print(sv_panel_t *p, GtkWidget *dummy);
-static void wrap_undo_up(sv_panel_t *p, GtkWidget *dummy);
-static void wrap_undo_down(sv_panel_t *p, GtkWidget *dummy);
-static void wrap_legend(sv_panel_t *p, GtkWidget *dummy);
-static void wrap_escape(sv_panel_t *p, GtkWidget *dummy);
-static void wrap_enter(sv_panel_t *p, GtkWidget *dummy);
 
-static _sv_propmap_t *bgmap[]={
-  &(_sv_propmap_t){"white",SV_BG_WHITE,   "[<i>b</i>]",NULL,wrap_bg},
-  &(_sv_propmap_t){"black",SV_BG_BLACK,   "[<i>b</i>]",NULL,wrap_bg},
-  &(_sv_propmap_t){"checks",SV_BG_CHECKS, "[<i>b</i>]",NULL,wrap_bg},
-  NULL
-};
+// panel.panel_m: rwlock, protects all panel and plane heaps
+// panel.status_m: mutex, protects status variables
 
-static _sv_propmap_t *gridmap[]={
-  &(_sv_propmap_t){"light",_SV_PLOT_GRID_LIGHT,   "[<i>g</i>]",NULL,wrap_grid},
-  &(_sv_propmap_t){"normal",_SV_PLOT_GRID_NORMAL, "[<i>g</i>]",NULL,wrap_grid},
-  &(_sv_propmap_t){"dark",_SV_PLOT_GRID_DARK,     "[<i>g</i>]",NULL,wrap_grid},
-  &(_sv_propmap_t){"tics",_SV_PLOT_GRID_TICS,     "[<i>g</i>]",NULL,wrap_grid},
-  &(_sv_propmap_t){"none",_SV_PLOT_GRID_NONE,     "[<i>g</i>]",NULL,wrap_grid},
-  NULL
-};
+// Plane data in the panels is protected as follows:
+//
+//   Modifications of pointers, heap allocations, etc are protected by 
+//     write-locking panel_m.
+//
+//   Both read and write access to the contents of plane.data and 
+//     plane.image are protected by read-locking panel_m; reads may happen 
+//     at any time, writes are checked only that they've not been superceded 
+//     by a later request.  Writes are not locked against reads at all; a 
+//     read from inconsistent state is temporary and cosmetic only. It will 
+//     always be immediately replaced by complete/correct data when the write
+//     finishes and triggers a flush.
 
-static _sv_propmap_t *textmap[]={
-  &(_sv_propmap_t){"dark",_SV_PLOT_TEXT_DARK,   "[<i>t</i>]",NULL,wrap_text},
-  &(_sv_propmap_t){"light",_SV_PLOT_TEXT_LIGHT, "[<i>t</i>]",NULL,wrap_text},
-  NULL
-};
+//   comp_serialno is used to to verify 'freshness' of operations; anytime a 
+//     thread needs to synchronize with panel/plane data before continuing 
+//     (read or write), it compares the serialno that dispatched it to the 
+//     current serialno.  A mismatch immediately aborts the task in progress 
+//     with STATUS_WORKING.
 
-#define RES_DEF 0
-#define RES_1_32 1
-#define RES_1_16 2
-#define RES_1_8 3
-#define RES_1_4 4
-#define RES_1_2 5
-#define RES_1_1 6
-#define RES_2_1 7
-#define RES_4_1 8
+//   map_serialno performs the same task with respect to remap requests
+//     for a given plane.
 
-// only used for the menus
-static _sv_propmap_t *resmap[]={
-  &(_sv_propmap_t){"default",RES_DEF,  "[<i>m</i>]",NULL,wrap_res},
-  &(_sv_propmap_t){"1:32",RES_1_32,     "[<i>m</i>]",NULL,wrap_res},
-  &(_sv_propmap_t){"1:16",RES_1_16,     "[<i>m</i>]",NULL,wrap_res},
-  &(_sv_propmap_t){"1:8",RES_1_8,      "[<i>m</i>]",NULL,wrap_res},
-  &(_sv_propmap_t){"1:4",RES_1_4,      "[<i>m</i>]",NULL,wrap_res},
-  &(_sv_propmap_t){"1:2",RES_1_2,      "[<i>m</i>]",NULL,wrap_res},
-  &(_sv_propmap_t){"1",RES_1_1,        "[<i>m</i>]",NULL,wrap_res},
-  &(_sv_propmap_t){"2:1",RES_2_1,      "[<i>m</i>]",NULL,wrap_res},
-  &(_sv_propmap_t){"4:1",RES_4_1,      "[<i>m</i>]",NULL,wrap_res},
-  NULL,
-};
+// worker thread process order:
 
-static _sv_propmap_t *crossmap[]={
-  &(_sv_propmap_t){"no",0     ,NULL,NULL,NULL},
-  &(_sv_propmap_t){"yes",1    ,NULL,NULL,NULL},
-  NULL
-};
+// > recompute setup
+// > images resize
+// > scale render
+// > legend render
+// > bg render
+// > expose
+// > data resize
+// > image map render (throttleable)
+// > computation work 
+// > idle             
 
-static _sv_propmap_t *legendmap[]={
-  &(_sv_propmap_t){"none",_SV_PLOT_LEGEND_NONE,       NULL,NULL,NULL},
-  &(_sv_propmap_t){"shadowed",_SV_PLOT_LEGEND_SHADOW, NULL,NULL,NULL},
-  &(_sv_propmap_t){"boxed",_SV_PLOT_LEGEND_BOX,       NULL,NULL,NULL},
-  NULL
-};
+// proceed to later steps only if there's no work immediately
+// dispatchable from earlier steps.
 
-static _sv_propmap_t *menu[]={
-  &(_sv_propmap_t){"Open",0,"[<i>o</i>]",NULL,wrap_load},
-  &(_sv_propmap_t){"Save",1,"[<i>s</i>]",NULL,wrap_save},
-  &(_sv_propmap_t){"Print/Export",2,"[<i>p</i>]",NULL,_sv_panel_print},
+// UI output comes first (UI is never dead, even briefly, and that
+// includes graphs), however progressive UI work is purposely
+// throttled.
 
-  &(_sv_propmap_t){"",3,NULL,NULL,NULL},
+// wake_workers() only explicitly needed when triggering tasks via
+// GDK/API; it is already called implicitly in the main loop whenever
+// a task step completes.
 
-  &(_sv_propmap_t){"Undo",4,"[<i>bksp</i>]",NULL,&wrap_undo_down},
-  &(_sv_propmap_t){"Redo",5,"[<i>space</i>]",NULL,&wrap_undo_up},
-  &(_sv_propmap_t){"Start zoom box",6,"[<i>enter</i>]",NULL,&wrap_enter},
-  &(_sv_propmap_t){"Clear selection",7,"[<i>escape</i>]",NULL,&wrap_escape},
-  &(_sv_propmap_t){"Toggle Legend",8,"[<i>l</i>]",NULL,&wrap_legend},
+static void payload_free(sv_dim_data_t *dd, int dims){
+  for(i=0;i<dims;i++)
+    _sv_dim_data_clear(dd+i);
+  free(dd);
+}
 
-  &(_sv_propmap_t){"",9,NULL,NULL,NULL},
+static int image_resize(sv_plane_t *pl, 
+			sv_panel_t *p){
+  return pl->c.image_resize(pl, p);
+}
 
-  &(_sv_propmap_t){"Background",10,"...",bgmap,NULL},
-  &(_sv_propmap_t){"Text color",11,"...",textmap,NULL},
-  &(_sv_propmap_t){"Grid mode",12,"...",gridmap,NULL},
-  &(_sv_propmap_t){"Sampling",13,"...",resmap,NULL},
+static int data_resize(sv_plane_t *pl, 
+		       sv_panel_t *p){
+  return pl->c.data_resize(pl, p);
+}
 
-  &(_sv_propmap_t){"",14,NULL,NULL,NULL},
+static int image_work(sv_plane_t *pl, 
+		      sv_panel_t *p){
+  return pl->c.image_work(pl, p);
+}
 
-  &(_sv_propmap_t){"Quit",15,"[<i>q</i>]",NULL,&wrap_exit},
+static int data_work(sv_plane_t *pl, 
+		     sv_panel_t *p){
+  return pl->c.data_work(pl, p);
+}
 
-  NULL
-};
+#define STATUS_IDLE 0
+#define STATUS_BUSY 1
+#define STATUS_WORKING 2
 
-static void decide_text_inv(sv_panel_t *p){
-  if(p->private->graph){
-    _sv_plot_t *plot = PLOT(p->private->graph);
-    if(p->private->bg_type == SV_BG_WHITE)
-      _sv_plot_set_bg_invert(plot,_SV_PLOT_TEXT_DARK);
-    else
-      _sv_plot_set_bg_invert(plot,_SV_PLOT_TEXT_LIGHT);
-  }
-}
+static int plane_loop(sv_panel_t *p, int *next, 
+		      int(*function)(_sv_plane_t *, 
+				     sv_panel_t *)){
+  int finishedflag=1;
+  int last = *next;
+  int serialno = p->serialno;
+  do{
+    int i = *next++;
+    if(*next>=p->planes)*next=0;
+    int status = function(p->plane_list[i],p);
+    if(status == STATUS_WORKING) return STATUS_WORKING;
+    if(status != STATUS_IDLE) finishedflag=0;
+  }while(i!=last);
 
-static void recompute_if_running(sv_panel_t *p){
-  if(p->private->realized && p->private->graph)
-    _sv_panel_recompute(p);
+  if(finishedflag)
+    return STATUS_IDLE;
+  return STATUS_BUSY;
 }
 
-static void redraw_if_running(sv_panel_t *p){
-  if(p->private->realized && p->private->graph){
-    _sv_plot_draw_scales(PLOT(p->private->graph));
-    _sv_panel_dirty_map(p);
-    _sv_panel_dirty_legend(p);
-  }
+static int done_working(sv_panel_t *p){
+  pthread_mutex_unlock(p->status_m);
+  pthread_rwlock_unlock(p->panel_m);
+  return STATUS_WORKING;
 }
 
-static void refg_if_running(sv_panel_t *p){
-  if(p->private->realized && p->private->graph){
-    _sv_plot_draw_scales(PLOT(p->private->graph));
-    _sv_panel_dirty_legend(p);
-  }
+static int done_busy(sv_panel_t *p){
+  pthread_mutex_unlock(p->status_m);
+  pthread_rwlock_unlock(p->panel_m);
+  return STATUS_BUSY;
 }
 
-static void wrap_exit(sv_panel_t *dummy, GtkWidget *dummyw){
-  _sv_clean_exit();
+static int done_idle(sv_panel_t *p){
+  pthread_mutex_unlock(p->status_m);
+  pthread_rwlock_unlock(p->panel_m);
+  return STATUS_IDLE;
 }
 
-// precipitated actions perform undo push
-static void wrap_enter(sv_panel_t *p, GtkWidget *dummy){
-  _sv_plot_do_enter(PLOT(p->private->graph));
-}
+int _sv_panel_work(sv_panel_t *p){
+  int i,serialno,status;
+  pthread_rwlock_rdlock(p->panel_m);
+  pthread_mutex_lock(p->status_m);
 
-static void wrap_escape(sv_panel_t *p, GtkWidget *dummy){
-  _sv_undo_push();
-  _sv_undo_suspend();
+  // recomute setup
 
-  _sv_plot_set_crossactive(PLOT(p->private->graph),0);
-  _sv_panel_dirty_legend(p);
+  // plane recompute calls will do nothing if recomputation is not
+  // required.  Even if computation not required, will still request
+  // an image resize; the image resize code will later noop if there's
+  // no need to resize either.
+  if(p->recompute_pending){
+    p->recompute_pending=0;
+    p->comp_serialno++;
+    p->image_resize=1;
+    p->data_resize=1;
+    p->rescale=1;
+    p->relegend=1;
+    p->bgrender=0;
+    p->image_next_plane=0;
+    
+    bg->c.recompute_setup(p->bg, p);
 
-  _sv_undo_resume();
-}
+    for(i=0;i<p->planes;i++)
+      p->plane_list[i]->c.recompute_setup(p->plane_list[i], p);
 
-static void wrap_legend(sv_panel_t *p, GtkWidget *dummy){
-  _sv_undo_push();
-  _sv_undo_suspend();
+    pthread_mutex_unlock(p->status_m);
+    pthread_rwlock_unlock(p->panel_m);
 
-  _sv_plot_toggle_legend(PLOT(p->private->graph));
-  _sv_panel_dirty_legend(p);
+    return STATUS_WORKING;
+  }
 
-  _sv_undo_resume();
-}
+  if(p->relegend_pending){
+    p->relegend=1;
+    p->relegend_pending=0;
+  }
 
-static void set_grid(sv_panel_t *p, int mode){
-  _sv_undo_push();
-  _sv_undo_suspend();
+  serialno = p->comp_serialno;
 
-  _sv_plot_set_grid(PLOT(p->private->graph),mode);
-  _sv_panel_update_menus(p);
-  refg_if_running(p);
+  // image resize
 
-  _sv_undo_resume();
-}
+  // again, each plane checks to see if a resize is really necessary.  If not, does nothing.
+  if(p->image_resize){
+    status = plane_loop(p,&p->image_next_plane,image_resize);
+    if(status == STATUS_WORKING) return done_working(p);
+    if(status == STATUS_IDLE){
+      p->image_resize = 0;
+      p->bgrender = 1;
+    }
+  }
 
-static void wrap_grid(sv_panel_t *p, GtkWidget *w){
-  int pos = _gtk_menu_item_position(w);
-  set_grid(p, gridmap[pos]->value);
-}
+  // legend regeneration
+  if(p->relegend){
+    if(bg_legend(p) == STATUS_IDLE){
+      p->expose=1;
+      p->relegend=0;
+    }
+    return done_working(p);
+  }
 
-static int set_background(sv_panel_t *p,
-			  enum sv_background bg){
+  // axis scale redraw
+  if(p->rescale){
+    if(bg_scale(p) == STATUS_IDLE){
+      p->expose=1;
+      p->rescale=0;
+    }
+    return done_working(p);
+  }
+
+  // need to join on image resizing before proceeding to background redraw
+  if(p->image_resize)
+    return done_busy(p);
   
-  sv_panel_internal_t *pi = p->private;
+  // bg render
+  if(p->bg_render){
+    if(bg_render(p) == STATUS_IDLE){
+      p->expose=1;
+      p->bgrender=0;
+    }
+    return done_working(p);
+  }    
   
-  _sv_undo_push();
-  _sv_undo_suspend();
+  // expose
+  if(p->expose &&
+     !p->relegend &&
+     !p->rescale &&
+     !p->bgrender){
+    // wait till all these ops are done
+    bg_expose(p);
+    return done_working(p);
+  }    
 
-  pi->bg_type = bg;
+  // data resize
+  if(p->data_resize){
+    status = plane_loop(p,&p->data_next_plane,data_resize);
+    if(status == STATUS_WORKING) return done_working(p);
+    if(status == STATUS_IDLE) p->data_resize = 0;
+  }
+
+  // need to join on data resizing before proceeding to map/compute work
+  if(p->data_resize)
+    return done_busy(p);
   
-  decide_text_inv(p);
-  set_grid(p,_SV_PLOT_GRID_NORMAL);
-  redraw_if_running(p);
-  _sv_panel_update_menus(p);
+  // image map render
+  if(p->map_render){
+    status = plane_loop(p,&p->image_next_plane,image_work);
+    if(status == STATUS_WORKING) return done_working(p);
+    if(status == STATUS_IDLE) p->map_render = 0;
+  }
+  
+  // computation work 
+  status = plane_loop(p,&p->data_next_plane,data_work);
+  if(status == STATUS_WORKING) return done_working(p);
+  return done_idle(p);
+}
 
-  _sv_undo_resume();
-  return 0;
+
+
+static int _sv_plane2d_compute_one(){
+
 }
 
-static void wrap_bg(sv_panel_t *p, GtkWidget *w){
-  int pos = _gtk_menu_item_position(w);
-  set_background(p, bgmap[pos]->value);
+static int _sv_plane2d_map_one(){
+
 }
 
-static void cycle_bg(sv_panel_t *p){
-  int menupos = _sv_propmap_pos(bgmap, p->private->bg_type) + 1;
-  if(bgmap[menupos] == NULL) menupos = 0;
-  set_background(p, bgmap[menupos]->value);
+// work order: resize/fast scale -> compute -> plane_render -> composite
+
+// enter unlocked
+static void _sv_planez_compute_line(sv_panel_t *p, 
+				    _sv_plane2d_t *z,
+
+				    int serialno,
+
+				    int dw,
+				    int y,
+				    int x_d, 
+				    _sv_scalespace_t sxi,
+				    double *dim_vals, 
+				    _sv_bythread_cache_2d_t *c){
+
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int i,j;
+  
+  /* cache access is unlocked because the cache is private to this
+     worker thread */
+
+  for(j=0;j<dw;j++){
+    double *fout = c->fout;
+    sv_func_t **f = p2->used_function_list;
+    int *obj_y_off = p2->y_fout_offset;
+    int *onum = p2->y_obj_to_panel;
+    
+    /* by function */
+    dim_vals[x_d] = _sv_scalespace_value(&sxi,j);   
+    for(i=0;i<p2->used_functions;i++){
+      (*f)->callback(dim_vals,fout);
+      fout += (*f)->outputs;
+      f++;
+    }
+    
+    /* process function output by plane type/objective */
+    /* 2d panels currently only care about the Y output value */
+    
+    /* slider map */
+    for(i=0;i<p2->y_obj_num;i++){
+      float val = (float)_sv_slider_val_to_del(p2->range_scales[*onum++], c->fout[*obj_y_off++]);
+      if(isnan(val)){
+	c->y_map[i][j] = -1;
+      }else{
+	if(val<0)val=0;
+	if(val>1)val=1;
+	c->y_map[i][j] = rint(val * (256.f*256.f*256.f));
+      }
+    }
+  }
+
+  gdk_lock ();
+  if(p->private->plot_serialno == serialno){
+    for(j=0;j<p2->y_obj_num;j++){
+      int *d = p2->y_map[j] + y*dw;
+      int *td = c->y_map[j];
+      
+      memcpy(d,td,dw*sizeof(*d));
+      
+    }
+  }
+  gdk_unlock ();
 }
 
-static void cycleB_bg(sv_panel_t *p){
-  int menupos = _sv_propmap_pos(bgmap, p->private->bg_type) - 1;
-  if(menupos<0) menupos = _sv_propmap_last(bgmap);
-  set_background(p, bgmap[menupos]->value);
+// call with lock
+static void _sv_panel2d_clear_pane(sv_panel_t *p){
+
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int pw = p2->x.pixels;
+  int ph = p2->y.pixels;
+  int i;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  
+  for(i=0;i<p2->y_obj_num;i++){
+    // map is freed and nulled to avoid triggering a fast-scale on an empty data pane
+    if(p2->y_map[i])
+      free(p2->y_map[i]);
+    p2->y_map[i]=NULL;
+
+    // free y_planes so that initial remap doesn't waste time on mix
+    // op; they are recreated during remap at the point the y_todo
+    // vector indicates something to do
+    if(p2->y_planes[i])
+      free(p2->y_planes[i]);
+    p2->y_planes[i] = NULL;
+
+    // work vector is merely cleared
+    memset(p2->y_planetodo[i], 0, ph*sizeof(**p2->y_planetodo));
+  }
+
+  // clear the background surface 
+  if(plot->datarect)
+    memset(plot->datarect, 0, ph*pw*sizeof(*plot->datarect));
+
+  // the bg is not marked to be refreshed; computation setup will do
+  // that as part of the fast_scale, even if the fast_scale is
+  // short-circuited to a noop.
 }
 
-static void set_text(sv_panel_t *p, int mode){
-  _sv_undo_push();
-  _sv_undo_suspend();
+typedef struct{
+  double x;
+  double y;
+  double z;
+} compute_result;
 
-  _sv_plot_set_bg_invert(PLOT(p->private->graph),mode);
-  _sv_panel_update_menus(p);
-  refg_if_running(p);
+// used by the legend code. this lets us get away with having only a mapped display pane
+// call with lock
+static void _sv_panel2d_compute_point(sv_panel_t *p,sv_obj_t *o, double x, double y, compute_result *out){
+  double dim_vals[_sv_dimensions];
+  int i,j;
+  int pflag=0;
+  int eflag=0;
 
-  _sv_undo_resume();
+  // fill in dimensions
+  int x_d = p->private->x_d->number;
+  int y_d = p->private->y_d->number;
+
+  for(i=0;i<_sv_dimensions;i++){
+    sv_dim_t *dim = _sv_dimension_list[i];
+    dim_vals[i]=dim->val;
+  }
+
+  gdk_unlock ();
+
+  dim_vals[x_d] = x;
+  dim_vals[y_d] = y;
+
+  *out = (compute_result){NAN,NAN,NAN,NAN,NAN,NAN,NAN,NAN};
+
+  // compute
+  for(i=0;i<_sv_functions;i++){
+    sv_func_t *f = _sv_function_list[i];
+    int compflag = 0;
+    double fout[f->outputs];
+    double val;
+
+    // compute and demultiplex output
+    for(j=0;j<o->outputs;j++){
+      if(o->function_map[j] == i){
+
+	if(!compflag) f->callback(dim_vals,fout);
+	compflag = 1;
+	
+	val = fout[o->output_map[j]];
+	switch(o->output_types[j]){
+	case 'X':
+	  out->x = val;
+	  break;
+	case 'Y':
+	  out->y = val;
+	  break;
+	case 'Z':
+	  out->z = val;
+	  break;
+	case 'E':
+	  if(eflag)
+	    out->e2 = val;
+	  else
+	    out->e1 = val;
+	  eflag = 1;
+	  break;
+	case 'P':
+	  if(pflag)
+	    out->p2 = val;
+	  else
+	    out->p1 = val;
+	  pflag = 1;
+	  break;
+	case 'M':
+	  out->m = val;
+	  break;
+	}
+      }
+    }
+  }
+  gdk_lock ();
+
 }
 
-static void wrap_text(sv_panel_t *p, GtkWidget *w){
-  int pos = _gtk_menu_item_position(w);
-  set_text(p, textmap[pos]->value);
+/* functions that perform actual graphical rendering */
+
+static float _sv_panel2d_resample_helpers_init(_sv_scalespace_t *to, _sv_scalespace_t *from,
+					       unsigned char *delA, unsigned char *delB, 
+					       int *posA, int *posB,
+					       int xymul){
+  int i;
+  int dw = from->pixels;
+  int pw = to->pixels;
+
+  long scalenum = _sv_scalespace_scalenum(to,from);
+  long scaleden = _sv_scalespace_scaleden(to,from);
+  long del = _sv_scalespace_scaleoff(to,from);
+  int bin = del / scaleden;
+  del -= bin * scaleden; 
+  int discscale = (scaleden>scalenum?scalenum:scaleden);
+  int total = xymul*scalenum/discscale;
+
+  for(i=0;i<pw;i++){
+    long del2 = del + scalenum;
+    int sizeceil = (del2 + scaleden - 1)/ scaleden; // ceiling
+    int sizefloor = del2 / scaleden;
+
+    while(bin<0 && del2>scaleden){
+      bin++;
+      del = 0;
+      del2 -= scaleden;
+      sizeceil--;
+    }
+    
+    if(del2 > scaleden && bin>=0 && bin<dw){
+      int rem = total;
+
+      delA[i] = ((xymul * (scaleden - del)) + (discscale>>1)) / discscale;
+      posA[i] = bin;
+      rem -= delA[i];
+      rem -= xymul*(sizeceil-2);
+
+      while(bin+sizeceil>dw){
+	sizeceil--;
+	del2=0;
+      }
+
+      del2 %= scaleden;
+      if(rem<0){
+	delA[i] += rem;
+	delB[i] = 0;
+      }else{
+	delB[i] = rem; // don't leak 
+      }
+      posB[i] = bin+sizeceil;
+
+    }else{
+      if(bin<0 || bin>=dw){
+	delA[i] = 0;
+	posA[i] = 0;
+	delB[i] = 0;
+	posB[i] = 0;
+      }else{
+	delA[i] = xymul;
+	posA[i] = bin;
+	delB[i] = 0;
+	posB[i] = bin+1;
+	if(del2 == scaleden)del2=0;
+      }
+    }
+
+    bin += sizefloor;
+    del = del2;
+  }
+  return (float)xymul/total;
 }
 
-static void cycle_text(sv_panel_t *p){
-  int menupos = _sv_propmap_pos(textmap, PLOT(p->private->graph)->bg_inv) + 1;
-  if(textmap[menupos] == NULL) menupos = 0;
-  set_text(p, textmap[menupos]->value);
+/* x resample helpers are put in the per-thread cache because locking it would
+   be relatively expensive. */
+// call while locked
+static void _sv_panel2d_resample_helpers_manage_x(sv_panel_t *p, _sv_bythread_cache_2d_t *c){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  if(p->private->plot_serialno != c->serialno){
+    int pw = p2->x.pixels;
+    c->serialno = p->private->plot_serialno;
+
+    if(c->xdelA)
+      free(c->xdelA);
+    if(c->xdelB)
+      free(c->xdelB);
+    if(c->xnumA)
+      free(c->xnumA);
+    if(c->xnumB)
+      free(c->xnumB);
+    
+    c->xdelA = calloc(pw,sizeof(*c->xdelA));
+    c->xdelB = calloc(pw,sizeof(*c->xdelB));
+    c->xnumA = calloc(pw,sizeof(*c->xnumA));
+    c->xnumB = calloc(pw,sizeof(*c->xnumB));
+    c->xscalemul = _sv_panel2d_resample_helpers_init(&p2->x, &p2->x_v, c->xdelA, c->xdelB, c->xnumA, c->xnumB, 17);
+  }
 }
 
-static void cycle_grid(sv_panel_t *p){
-  int menupos = _sv_propmap_pos(gridmap, PLOT(p->private->graph)->grid_mode) + 1;
-  if(gridmap[menupos] == NULL) menupos = 0;
-  set_grid(p, gridmap[menupos]->value);
+/* y resample is in the panel struct as per-row access is already locked */
+// call while locked
+static void _sv_panel2d_resample_helpers_manage_y(sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  if(p->private->plot_serialno != p2->resample_serialno){
+    int ph = p2->y.pixels;
+    p2->resample_serialno = p->private->plot_serialno;
+
+    if(p2->ydelA)
+      free(p2->ydelA);
+    if(p2->ydelB)
+      free(p2->ydelB);
+    if(p2->ynumA)
+      free(p2->ynumA);
+    if(p2->ynumB)
+      free(p2->ynumB);
+    
+    p2->ydelA = calloc(ph,sizeof(*p2->ydelA));
+    p2->ydelB = calloc(ph,sizeof(*p2->ydelB));
+    p2->ynumA = calloc(ph,sizeof(*p2->ynumA));
+    p2->ynumB = calloc(ph,sizeof(*p2->ynumB));
+    p2->yscalemul = _sv_panel2d_resample_helpers_init(&p2->y, &p2->y_v, p2->ydelA, p2->ydelB, p2->ynumA, p2->ynumB, 15);
+  }
 }
-static void cycleB_grid(sv_panel_t *p){
-  int menupos = _sv_propmap_pos(gridmap, PLOT(p->private->graph)->grid_mode) - 1;
-  if(menupos<0) menupos = _sv_propmap_last(gridmap);
-  set_grid(p, gridmap[menupos]->value);
+
+static inline void _sv_panel2d_mapping_calc( void (*m)(int,int, _sv_lcolor_t *), 
+					     int low,
+					     float range,
+					     int in, 
+					     int alpha, 
+					     int mul, 
+					     _sv_lcolor_t *outc){
+  if(mul && in>=alpha){
+    int val = rint((in - low) * range);
+    if(val<0)val=0;
+    if(val>65536)val=65536;
+    m(val,mul,outc);
+  }
 }
 
-static void res_set(sv_panel_t *p, int n, int d){
-  if(n != p->private->oversample_n ||
-     d != p->private->oversample_d){
+/* the data rectangle is data width/height mapped deltas.  we render
+   and subsample at the same time. */
+/* return: -1 == abort
+            0 == more work to be done in this plane
+	    1 == plane fully dispatched (possibly not complete) */
 
-    _sv_undo_push();
-    _sv_undo_suspend();
-    
-    p->private->oversample_n = n;
-    p->private->oversample_d = d;
-    _sv_panel_update_menus(p);
-    recompute_if_running(p);
+/* enter with lock */
+static int _sv_panel2d_resample_render_y_plane_line(sv_panel_t *p, _sv_bythread_cache_2d_t *c, 
+						    int plot_serialno, int map_serialno, int y_no){
+  
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int objnum = p2->y_obj_to_panel[y_no]; 
+  int *in_data = p2->y_map[y_no];
+  int ph = p2->y.pixels;
 
-    _sv_undo_resume();
+  if(!in_data || !c){
+    p->private->map_complete_count -= ph;
+    return 1;
   }
+
+  unsigned char *todo = p2->y_planetodo[y_no];
+  int i = p2->y_next_line;
+  int j;
+
+  /* find a row that needs to be updated */
+  while(i<ph && !todo[i]){
+    p->private->map_complete_count--;
+    p2->y_next_line++;
+    i++;
+  }
+
+  if(i == ph) return 1;
+
+  p2->y_next_line++;
+
+  /* row [i] needs to be updated; marshal */
+  _sv_mapping_t *map = p2->mappings+objnum;
+  void (*mapfunc)(int,int, _sv_lcolor_t *) = map->mapfunc;
+  int ol_alpha = rint(p2->alphadel[y_no] * 16777216.f);
+  _sv_ucolor_t *panel = p2->y_planes[y_no];
+  int ol_low = rint(map->low * 16777216.f);
+  float ol_range = map->i_range * (1.f/256.f);
+
+  int pw = p2->x.pixels;
+  int dw = p2->x_v.pixels;
+  int dh = p2->y_v.pixels;
+  _sv_ccolor_t work[pw];
+
+  if(!panel)
+    panel = p2->y_planes[y_no] = calloc(pw*ph, sizeof(**p2->y_planes));
+
+  if(ph!=dh || pw!=dw){
+    /* resampled row computation; may involve multiple data rows */
+
+    _sv_panel2d_resample_helpers_manage_y(p);
+    _sv_panel2d_resample_helpers_manage_x(p,c);
+
+    float idel = p2->yscalemul * c->xscalemul;
+
+    /* by column */
+    int ydelA=p2->ydelA[i];
+    int ydelB=p2->ydelB[i];
+    int ystart=p2->ynumA[i];
+    int yend=p2->ynumB[i];
+    int lh = yend - ystart;
+    int data[lh*dw];
+
+    memcpy(data,in_data+ystart*dw,sizeof(data));
+
+    gdk_unlock();
+
+    unsigned char *xdelA = c->xdelA;
+    unsigned char *xdelB = c->xdelB;
+    int *xnumA = c->xnumA;
+    int *xnumB = c->xnumB;
+      
+    /* by panel col */
+    for(j=0;j<pw;j++){
+      
+      _sv_lcolor_t out = (_sv_lcolor_t){0,0,0,0}; 
+      int xstart = xnumA[j];
+      int xend = xnumB[j];
+      int dx = xstart;
+      int xA = xdelA[j];
+      int xB = xdelB[j];
+      int y = ystart;
+
+      // first line
+      if(y<yend){
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx++], ol_alpha, ydelA*xA, &out);
+	
+	for(; dx < xend-1; dx++)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelA*17, &out);
+	
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelA*xB, &out);
+	y++;
+      }
+
+      // mid lines
+      for(;y<yend-1;y++){
+	dx = xstart += dw;
+	xend += dw;
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx++], ol_alpha, 15*xA, &out);
+	
+	for(; dx < xend-1; dx++)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, 255, &out);
+	
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, 15*xB, &out);
+      }
+      
+      // last line
+      if(y<yend){
+	dx = xstart += dw;
+	xend += dw;
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx++], ol_alpha, ydelB*xA, &out);
+	
+	for(; dx < xend-1; dx++)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelB*17, &out);
+	
+	if(dx<xend)
+	  _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[dx], ol_alpha, ydelB*xB, &out);
+      }
+
+      work[j].a = (u_int32_t)(out.a*idel);
+      work[j].r = (u_int32_t)(out.r*idel);
+      work[j].g = (u_int32_t)(out.g*idel);
+      work[j].b = (u_int32_t)(out.b*idel);
+      
+    }
+
+  }else{
+    /* non-resampling render */
+
+    int data[dw];
+    memcpy(data,in_data+i*dw,sizeof(data));
+    gdk_unlock();      
+
+    for(j=0;j<pw;j++){
+
+      _sv_lcolor_t out = (_sv_lcolor_t){0,0,0,0};
+      _sv_panel2d_mapping_calc(mapfunc, ol_low, ol_range, data[j], ol_alpha, 255, &out);
+	
+      work[j].a = (u_int32_t)(out.a);
+      work[j].r = (u_int32_t)(out.r);
+      work[j].g = (u_int32_t)(out.g);
+      work[j].b = (u_int32_t)(out.b);
+    }
+  }
+
+  gdk_lock ();  
+  if(plot_serialno != p->private->plot_serialno ||
+     map_serialno != p->private->map_serialno)
+    return -1;
+  memcpy(panel+i*pw,work,sizeof(work));
+  p2->bg_todo[i] = 1;
+  p2->y_planetodo[y_no][i] = 0;
+
+  // must be last; it indicates completion
+  p->private->map_complete_count--;
+  return 0;
 }
 
-// a little different; the menu value is not the internal setting
-static void res_set_pos(sv_panel_t *p, int pos){
-  p->private->menu_cursamp = pos;
-  switch(pos){
-  case RES_DEF:
-    res_set(p,p->private->def_oversample_n,p->private->def_oversample_d);
+static void render_checks(_sv_ucolor_t *c, int w, int y){
+  /* default checked background */
+  /* 16x16 'mid-checks' */ 
+  int x,j;
+  
+  int phase = (y>>4)&1;
+  for(x=0;x<w;){
+    u_int32_t phaseval = 0xff505050UL;
+    if(phase) phaseval = 0xff808080UL;
+    for(j=0;j<16 && x<w;j++,x++)
+      c[x].u = phaseval;
+    phase=!phase;
+  }
+}
+
+// enter with lock
+static int _sv_panel2d_render_bg_line(sv_panel_t *p, int plot_serialno, int map_serialno){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  if(plot_serialno != p->private->plot_serialno ||
+     map_serialno != p->private->map_serialno) return -1;
+  
+  int ph = p2->y.pixels;
+  int pw = p2->x.pixels;
+  unsigned char *todo = p2->bg_todo;
+  int i = p2->bg_next_line,j;
+  _sv_ucolor_t work_bg[pw];
+  _sv_ucolor_t work_pl[pw];
+  int bgmode = p->private->bg_type;
+
+  /* find a row that needs to be updated */
+  while(i<ph && !todo[i]){
+    p->private->map_complete_count--;
+    p2->bg_next_line++;
+    i++;
+  }
+
+  if(i == ph)
+    goto done;
+
+  if(i < p2->bg_first_line) p2->bg_first_line = i;
+  if(i+1 > p2->bg_last_line) p2->bg_last_line = i+1;
+  p2->bg_next_line++;
+
+  /* gray background checks */
+  gdk_unlock();
+
+  switch(bgmode){
+  case SV_BG_WHITE:
+    for(j=0;j<pw;j++)
+      work_bg[j].u = 0xffffffffU;
     break;
-  case RES_1_32:
-    res_set(p,1,32);
+  case SV_BG_BLACK:
+    for(j=0;j<pw;j++)
+      work_bg[j].u = 0xff000000U;
     break;
-  case RES_1_16:
-    res_set(p,1,16);
+  default:
+    render_checks(work_bg,pw,i);
     break;
-  case RES_1_8:
-    res_set(p,1,8);
-    break;
-  case RES_1_4:
-    res_set(p,1,4);
-    break;
-  case RES_1_2:
-    res_set(p,1,2);
-    break;
-  case RES_1_1:
-    res_set(p,1,1);
-    break;
-  case RES_2_1:
-    res_set(p,2,1);
-    break;
-  case RES_4_1:
-    res_set(p,4,1);
-    break;
   }
-}
 
-static void wrap_res(sv_panel_t *p, GtkWidget *w){
-  int pos = _gtk_menu_item_position(w);
-  res_set_pos(p, resmap[pos]->value);
-}
+  /* by objective */
+  for(j=0;j<p->objectives;j++){
+    int o_ynum = p2->y_obj_from_panel[j];
+    
+    gdk_lock();
+    if(plot_serialno != p->private->plot_serialno ||
+       map_serialno != p->private->map_serialno) return -1;
 
-static void cycle_res(sv_panel_t *p){
-  int menupos = _sv_propmap_pos(resmap, p->private->menu_cursamp) + 1;
-  if(resmap[menupos] == NULL) menupos = 0;
-  res_set_pos(p, resmap[menupos]->value);
+    /**** mix Y plane */
+    
+    if(p2->y_planes[o_ynum]){
+      int x;
+      _sv_ucolor_t (*mixfunc)(_sv_ucolor_t,_sv_ucolor_t) = p2->mappings[j].mixfunc;
+      _sv_ucolor_t *rect = p2->y_planes[o_ynum] + i*pw;
+      memcpy(work_pl,rect,sizeof(work_pl));
+      
+      gdk_unlock();
+      for(x=0;x<pw;x++)
+	work_bg[x] = mixfunc(work_pl[x],work_bg[x]);
+    }else
+      gdk_unlock();
+
+    /**** mix Z plane */
+    
+    /**** mix vector plane */
+
+  }
+
+  gdk_lock();
+  if(plot_serialno != p->private->plot_serialno ||
+     map_serialno != p->private->map_serialno) return -1;
+
+    // rendered a line, get it on the screen */
+  
+  memcpy(plot->datarect+pw*i, work_bg, sizeof(work_bg));
+
+  p->private->map_complete_count--;
+
+ done:
+  if(p->private->map_complete_count)
+    return 1; // not done yet
+
+  return 0;
 }
 
-static void cycleB_res(sv_panel_t *p){
-  int menupos = _sv_propmap_pos(resmap, p->private->menu_cursamp) - 1;
-  if(menupos<0) menupos = _sv_propmap_last(resmap);
-  res_set_pos(p, resmap[menupos]->value);
+static void _sv_panel2d_mark_map_full(sv_panel_t *p);
+
+// enter with lock; returns zero if thread should sleep / get distracted
+static int _sv_panel2d_remap(sv_panel_t *p, _sv_bythread_cache_2d_t *thread_cache){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(!plot) goto abort;
+  int ph = plot->y.pixels;
+  int pw = plot->x.pixels;
+  
+  int plot_serialno = p->private->plot_serialno; 
+  int map_serialno = p->private->map_serialno; 
+
+  /* brand new remap indicated by the generic progress indicator being set to 0 */
+  if(p->private->map_progress_count == 0){
+
+    p->private->map_progress_count = 1; // 'in progress'
+    p->private->map_complete_count = p2->y_obj_num * ph; // count down to 0; 0 indicates completion
+
+    // set up Y plane rendering
+    p2->y_next_plane = 0;
+    p2->y_next_line = 0;
+
+    // bg mix
+    p2->bg_next_line = 0;
+    p2->bg_first_line = ph;
+    p2->bg_last_line = 0;
+
+    if(!p2->partial_remap)
+      _sv_panel2d_mark_map_full(p);
+  }
+
+  /* by plane, by line; each plane renders independently */
+  /* Y planes */
+  if(p2->y_planetodo){
+    if(p2->y_next_plane < p2->y_obj_num){
+      int status = _sv_panel2d_resample_render_y_plane_line(p, thread_cache, 
+							    plot_serialno, map_serialno, 
+							    p2->y_next_plane);
+      if(status == -1) goto abort;
+      if(status == 1){
+	p2->y_next_plane++;
+	p2->y_next_line = 0;
+      }
+      return 1;
+    }
+  }else{
+    p->private->map_complete_count = 0;
+  }
+
+  /* renders have been completely dispatched, but are they complete? */
+  /* the below is effectively a a thread join */
+  if(p2->bg_next_line == 0){
+
+    // join still needs to complete....
+    if(p->private->map_complete_count){
+      // nonzero complete count, not finished.  returning zero will cause
+      // this worker thread to sleep or go on to do other things.
+      return 0; 
+    }else{
+      // zero complete count, the planes are done; we can begin
+      // background render.  At least one thread is guaranteed to get
+      // here, which is enough; we can now wake the others [if they were
+      // asleep] and have them look for work here. */
+      p->private->map_complete_count = ph; // [ph] lines to render in bg plane
+      p2->bg_next_line = 0;
+
+      _sv_wake_workers();
+    }
+  }
+
+  /* mix new background, again line by line */
+  if(p2->bg_next_line < ph){
+    int status = _sv_panel2d_render_bg_line(p, plot_serialno, map_serialno);
+    if(status == -1) goto abort;
+    if(p->private->map_complete_count)return status;
+  }else
+    return 0; // nothing left to dispatch
+
+  // entirely finished.
+
+  // remap completed; flush background to screen
+  _sv_plot_expose_request_partial (plot,0,p2->bg_first_line,
+			       pw,p2->bg_last_line - p2->bg_first_line);
+  gdk_flush();
+
+  // clean bg todo list
+  memset(p2->bg_todo,0,ph*sizeof(*p2->bg_todo));
+
+  // clear 'panel in progress' flag
+  p2->partial_remap = 0;
+  _sv_panel_clean_map(p);
+  return 0;
+
+ abort:
+  // reset progress to 'start over'
+  return 1;
 }
 
-static GtkPrintSettings *printset=NULL;
-static void _begin_print_handler (GtkPrintOperation *op,
-				  GtkPrintContext   *context,
-				  gpointer           dummy){
+// looks like a cop-out but is actually the correct thing to do; the
+// data *must* be WYSIWYG from panel display.
+static void _sv_panel2d_print_bg(sv_panel_t *p, cairo_t *c){
+  _sv_plot_t *plot = PLOT(p->private->graph);
 
-  gtk_print_operation_set_n_pages(op,1);
+  if(!plot) return;
 
+  cairo_pattern_t *pattern = cairo_pattern_create_for_surface(plot->back);
+  cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST);
+  cairo_set_source(c,pattern);
+  cairo_paint(c);
+
+  cairo_pattern_destroy(pattern);
 }
 
-static void _print_handler(GtkPrintOperation *operation,
-			   GtkPrintContext   *context,
-			   gint               page_nr,
-			   gpointer           user_data){
+static void _sv_panel2d_print(sv_panel_t *p, cairo_t *c, int w, int h){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  double pw = p->private->graph->allocation.width;
+  double ph = p->private->graph->allocation.height;
+  double scale;
+  int i;
+  double maxlabelw=0;
+  double y;
 
-  cairo_t *c;
-  gdouble w, h;
-  sv_panel_t *p = (sv_panel_t *)user_data;
+  if(w/pw < h/ph)
+    scale = w/pw;
+  else
+    scale = h/ph;
 
-  c = gtk_print_context_get_cairo_context (context);
-  w = gtk_print_context_get_width (context);
-  h = gtk_print_context_get_height (context);
+  cairo_matrix_t m;
+  cairo_save(c);
+  cairo_get_matrix(c,&m);
+  cairo_matrix_scale(&m,scale,scale);
+  cairo_set_matrix(c,&m);
   
-  p->private->print_action(p,c,w,h);
+  _sv_plot_print(plot, c, ph*scale, (void(*)(void *, cairo_t *))_sv_panel2d_print_bg, p);
+  cairo_restore(c);
+
+  // find extents widths for objective scale labels
+  cairo_set_font_size(c,10);
+  for(i=0;i<p->objectives;i++){
+    cairo_text_extents_t ex;
+    sv_obj_t *o = p->objective_list[i].o;
+    cairo_text_extents(c, o->name, &ex);
+    if(ex.width > maxlabelw) maxlabelw=ex.width;
+  }
+
+
+  y = ph * scale + 10;
+
+  for(i=0;i<p->objectives;i++){
+    sv_obj_t *o = p->objective_list[i].o;
+    _sv_slider_t *s = p2->range_scales[i];
+    
+    // get scale height
+    double labelh = _sv_slider_print_height(s);
+    cairo_text_extents_t ex;
+    cairo_text_extents (c, o->name, &ex);
+
+    int lx = maxlabelw - ex.width;
+    int ly = labelh/2 + ex.height/2;
+    
+    // print objective labels
+    cairo_set_source_rgb(c,0.,0.,0.);
+    cairo_move_to (c, lx,ly+y);
+    cairo_show_text (c, o->name);
+
+    // draw slider
+    // set translation
+    cairo_save(c);
+    cairo_translate (c, maxlabelw + 10, y);
+    _sv_slider_print(s, c, pw*scale - maxlabelw - 10, labelh);
+    cairo_restore(c);
+
+    y += labelh;
+  }
+
 }
 
-static void _sv_panel_print(sv_panel_t *p, GtkWidget *dummy){
-  GtkPrintOperation *op = gtk_print_operation_new ();
+// call while locked
+static void _sv_panel2d_mark_map_plane(sv_panel_t *p, int onum, int y, int z, int v){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int ph = p2->y.pixels;
 
-  if (printset != NULL) 
-    gtk_print_operation_set_print_settings (op, printset);
-  
-  g_signal_connect (op, "begin-print", 
-		    G_CALLBACK (_begin_print_handler), p);
-  g_signal_connect (op, "draw-page", 
-		    G_CALLBACK (_print_handler), p);
+  if(y && p2->y_planetodo){
+    int y_no = p2->y_obj_from_panel[onum];
+    if(y_no>=0 && p2->y_planetodo[y_no])
+      memset(p2->y_planetodo[y_no],1,ph * sizeof(**p2->y_planetodo));
+  }
+  p2->partial_remap = 1;
+}
 
-  GError *err;
-  GtkPrintOperationResult ret = gtk_print_operation_run (op,GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
-							 NULL,&err);
+// call while locked 
+static void _sv_panel2d_mark_map_line_y(sv_panel_t *p, int line){
+  // determine all panel lines this y data line affects
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int ph = p2->y.pixels;
+  int pw = p2->x.pixels;
+  int dw = p2->x_v.pixels;
+  int dh = p2->y_v.pixels;
+  int i,j;
 
-  if (ret == GTK_PRINT_OPERATION_RESULT_ERROR) {
-    GtkWidget *error_dialog = gtk_message_dialog_new (NULL,0,GTK_MESSAGE_ERROR,
-					   GTK_BUTTONS_CLOSE,
-					   "Error printing file:\n%s",
-					   err->message);
-    g_signal_connect (error_dialog, "response", 
-		      G_CALLBACK (gtk_widget_destroy), NULL);
-    gtk_widget_show (error_dialog);
-    g_error_free (err);
-  }else if (ret == GTK_PRINT_OPERATION_RESULT_APPLY){
-    if (printset != NULL)
-      g_object_unref (printset);
-    printset = g_object_ref (gtk_print_operation_get_print_settings (op));
+  p2->partial_remap = 1;
+
+  if(ph!=dh || pw!=dw){
+    /* resampled row computation; may involve multiple data rows */
+    if(p2->y_planetodo){
+      _sv_panel2d_resample_helpers_manage_y(p);
+      
+      for(i=0;i<ph;i++)
+	if(p2->ynumA[i]<=line &&
+	   p2->ynumB[i]>line){
+	  
+	  for(j=0;j<p2->y_obj_num;j++)
+	    if(p2->y_planetodo[j])
+	      p2->y_planetodo[j][i]=1;
+      }
+    }
+  }else{
+    if(p2->y_planetodo)
+      if(line>=0 && line<ph)
+	for(j=0;j<p2->y_obj_num;j++)
+	  if(p2->y_planetodo[j])
+	    p2->y_planetodo[j][line]=1;
   }
-  g_object_unref (op);
 }
 
-static void wrap_undo_down(sv_panel_t *p, GtkWidget *dummy){
-  _sv_undo_down();
+// call while locked
+static void _sv_panel2d_mark_map_full(sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int ph = p2->y.pixels;
+  int i,j;
+
+  p2->partial_remap = 1;
+
+  if(p2->y_planetodo){
+    for(j=0;j<p2->y_obj_num;j++){
+      if(p2->y_planetodo[j]){
+	for(i=0;i<ph;i++){
+	  p2->y_planetodo[j][i]=1;
+	}
+      }
+    }
+  }
 }
-static void wrap_undo_up(sv_panel_t *p, GtkWidget *dummy){
-  _sv_undo_up();
+
+// enter with lock
+static void _sv_panel2d_update_legend(sv_panel_t *p){  
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(plot){
+    int i;
+    char buffer[320];
+    int depth = 0;
+    _sv_plot_legend_clear(plot);
+
+    // potentially add each dimension to the legend; add axis
+    // dimensions only if crosshairs are active
+
+    // display decimal precision relative to display scales
+    if(3-_sv_scalespace_decimal_exponent(&p2->x) > depth) 
+      depth = 3-_sv_scalespace_decimal_exponent(&p2->x);
+    if(3-_sv_scalespace_decimal_exponent(&p2->y) > depth) 
+      depth = 3-_sv_scalespace_decimal_exponent(&p2->y);
+    for(i=0;i<p->dimensions;i++){
+      sv_dim_t *d = p->dimension_list[i].d;
+      if( (d!=p->private->x_d && d!=p->private->y_d) ||
+	  plot->cross_active){
+	snprintf(buffer,320,"%s = %+.*f",
+		 p->dimension_list[i].d->legend,
+		 depth,
+		 p->dimension_list[i].d->val);
+	_sv_plot_legend_add(plot,buffer);
+      }
+    }
+    
+    // add each active objective plane to the legend
+    // choose the value under the crosshairs 
+    if(plot->cross_active){
+      // one space 
+      _sv_plot_legend_add(plot,NULL);
+
+      for(i=0;i<p->objectives;i++){
+	
+	if(!_sv_mapping_inactive_p(p2->mappings+i)){
+	  compute_result vals;
+	  _sv_panel2d_compute_point(p,p->objective_list[i].o, plot->selx, plot->sely, &vals);
+	  
+	  if(!isnan(vals.y)){
+	    
+	    snprintf(buffer,320,"%s = %f",
+		     p->objective_list[i].o->name,
+		     vals.y);
+	    _sv_plot_legend_add(plot,buffer);
+	  }
+	}
+      }
+    }
+  }
 }
 
-static void wrap_save(sv_panel_t *p, GtkWidget *dummy){
-  GtkWidget *dialog = gtk_file_chooser_dialog_new ("Save",
-						   NULL,
-						   GTK_FILE_CHOOSER_ACTION_SAVE,
-						   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-						   GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
-						   NULL);
+static void _sv_panel2d_mapchange_callback(GtkWidget *w,gpointer in){
+  sv_obj_list_t *optr = (sv_obj_list_t *)in;
+  //sv_obj_t *o = optr->o;
+  sv_panel_t *p = optr->p;
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int onum = optr - p->objective_list;
 
-  gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
-  gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), _sv_cwdname, NULL);
-  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), _sv_dirname);
-  gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), _sv_filebase);
+  _sv_undo_push();
+  _sv_undo_suspend();
 
-  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT){
-    if(_sv_filebase)free(_sv_filebase);
-    if(_sv_filename)free(_sv_filename);
-    if(_sv_dirname)free(_sv_dirname);
+  _sv_mapping_set_func(&p2->mappings[onum],gtk_combo_box_get_active(GTK_COMBO_BOX(w)));
+  
+  //redraw the map slider
+  _sv_slider_set_gradient(p2->range_scales[onum], &p2->mappings[onum]);
 
-    _sv_filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
-    _sv_dirname = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog));
-    _sv_filebase = g_path_get_basename(_sv_filename);
-    _sv_main_save();
+  // in the event the mapping active state changed
+  _sv_panel_dirty_legend(p);
+
+  //redraw the plot
+  _sv_panel2d_mark_map_plane(p,onum,1,0,0);
+  _sv_panel_dirty_map(p);
+  _sv_undo_resume();
+}
+
+static void _sv_panel2d_map_callback(void *in,int buttonstate){
+  sv_obj_list_t *optr = (sv_obj_list_t *)in;
+  //sv_obj_t *o = optr->o;
+  sv_panel_t *p = optr->p;
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int onum = optr - p->objective_list;
+
+  if(buttonstate == 0){
+    _sv_undo_push();
+    _sv_undo_suspend();
   }
 
-  gtk_widget_destroy (dialog);
+  // recache alpha del */
+  p2->alphadel[onum] = 
+    _sv_slider_val_to_del(p2->range_scales[onum],
+		      _sv_slider_get_value(p2->range_scales[onum],1));
 
+  // redraw the plot on motion
+  if(buttonstate == 1){
+    _sv_panel2d_mark_map_plane(p,onum,1,0,0);
+    _sv_panel_dirty_map(p);
+  }
+  if(buttonstate == 2)
+    _sv_undo_resume();
 }
 
-static void wrap_load(sv_panel_t *p, GtkWidget *dummy){
-  GtkWidget *dialog = gtk_file_chooser_dialog_new ("Open",
-						   NULL,
-						   GTK_FILE_CHOOSER_ACTION_OPEN,
-						   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
-						   GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
-						   NULL);
+static void _sv_panel2d_update_xysel(sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int i;
+  // update which x/y buttons are pressable */
+  // enable/disable dimension slider thumbs
 
-  gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), _sv_cwdname, NULL);
-  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), _sv_dirname);
+  for(i=0;i<p->dimensions;i++){
+    if(p2->dim_xb[i] &&
+       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_xb[i]))){
+      // make the y insensitive
+      if(p2->dim_yb[i])
+	_gtk_widget_set_sensitive_fixup(p2->dim_yb[i],FALSE);
 
-  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT){
-    char *temp_filebase = _sv_filebase;
-    char *temp_filename = _sv_filename;
-    char *temp_dirname = _sv_dirname;
-    _sv_filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
-    _sv_dirname = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog));
-    _sv_filebase = g_path_get_basename(_sv_filename);
+      // set the x dim flag
+      p->private->x_d = p->dimension_list[i].d;
+      p2->x_scale = p->private->dim_scales[i];
+      p2->x_dnum = i;
+    }else{
+      // if there is a y, make it sensitive 
+      if(p2->dim_yb[i])
+	_gtk_widget_set_sensitive_fixup(p2->dim_yb[i],TRUE);
+    }
+    if(p2->dim_yb[i] &&
+       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_yb[i]))){
+      // make the x insensitive
+      if(p2->dim_xb[i])
+	_gtk_widget_set_sensitive_fixup(p2->dim_xb[i],FALSE);
 
-    if(_sv_main_load()){
+      // set the y dim
+      p->private->y_d = p->dimension_list[i].d;
+      p2->y_scale = p->private->dim_scales[i];
+      p2->y_dnum = i;
+    }else{
+      // if there is a x, make it sensitive 
+      if(p2->dim_xb[i])
+	_gtk_widget_set_sensitive_fixup(p2->dim_xb[i],TRUE);
+    }
+    if((p2->dim_xb[i] &&
+	gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_xb[i]))) ||
+       (p2->dim_yb[i] &&
+	gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(p2->dim_yb[i])))){
+      // make all thumbs visible 
+      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],0,1);
+      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],2,1);
+    }else{
+      // make bracket thumbs invisible */
+      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],0,0);
+      _sv_dim_widget_set_thumb_active(p->private->dim_scales[i],2,0);
+    }
+  } 
+}
 
-      free(_sv_filebase);
-      free(_sv_filename);
-      free(_sv_dirname);
+static int _v_swizzle(int y, int height){
+  int yy = height >> 5;
+  if(y < yy)
+    return (y<<5)+31;
 
-      _sv_filebase = temp_filebase;
-      _sv_filename = temp_filename;
-      _sv_dirname = temp_dirname;
+  y -= yy;
+  yy = (height+16) >> 5;
+  if(y < yy)
+    return (y<<5)+15;
 
+  y -= yy;
+  yy = (height+8) >> 4;
+  if(y < yy)
+    return (y<<4)+7;
+
+  y -= yy;
+  yy = (height+4) >> 3;
+  if(y < yy)
+    return (y<<3)+3;
+
+  y -= yy;
+  yy = (height+2) >> 2;
+  if(y < yy)
+    return (y<<2)+1;
+
+  y -= yy;
+  return y<<1;
+}
+
+// assumes data is locked
+static void _sv_panel2d_fast_scale_x(_sv_spinner_t *sp,
+				     int *data, 
+				     int w,
+				     int h,
+				     _sv_scalespace_t new,
+				     _sv_scalespace_t old){
+  int x,y;
+  int work[w];
+  int mapbase[w];
+  int mapdel[w];
+  double old_w = old.pixels;
+  double new_w = new.pixels;
+
+  double old_lo = _sv_scalespace_value(&old,0);
+  double old_hi = _sv_scalespace_value(&old,old_w);
+  double new_lo = _sv_scalespace_value(&new,0);
+  double new_hi = _sv_scalespace_value(&new,new_w);
+  double newscale = (new_hi-new_lo)/new_w;
+  double oldscale = old_w/(old_hi-old_lo);
+  for(x=0;x<w;x++){
+    double xval = (x)*newscale+new_lo;
+    double map = ((xval-old_lo)*oldscale);
+    int base = (int)floor(map);
+    int del = rint((map - floor(map))*64.f);
+    /* hack to overwhelm roundoff error; this is inside a purely
+       temporary cosmetic approximation anyway*/
+    if(base>0 && del==0){
+      mapbase[x]=base-1;
+      mapdel[x]=64;
     }else{
-      free(temp_filebase);
-      free(temp_filename);
-      free(temp_dirname);
+      mapbase[x]=base;
+      mapdel[x]=del;
     }
   }
 
-  gtk_widget_destroy (dialog);
+  for(y=0;y<h;y++){
+    int *data_line = data+y*w;
+    _sv_spinner_set_busy(sp);
+    for(x=0;x<w;x++){
+      if(mapbase[x]<0 || mapbase[x]>=(w-1)){
+	work[x]=-1;
+      }else{
+	int base = mapbase[x];
+	int A = data_line[base];
+	int B = data_line[base+1];
+	if(A<0 || B<0)
+	  work[x]=-1;
+	else
+	  work[x]= A + (((B - A)*mapdel[x])>>6);
+	
+      }
+    }
+    memcpy(data_line,work,w*(sizeof(*work)));
+  }   
+}
 
+static void _sv_panel2d_fast_scale_y(_sv_spinner_t *sp,
+				     int *olddata, 
+				     int *newdata, 
+				     int oldw,
+				     int neww,
+				     _sv_scalespace_t new,
+				     _sv_scalespace_t old){
+  int x,y;
+  int w = (oldw<neww?oldw:neww);
+
+  int old_h = old.pixels;
+  int new_h = new.pixels;
+
+  int mapbase[new_h];
+  int mapdel[new_h];
+
+  double old_lo = _sv_scalespace_value(&old,0);
+  double old_hi = _sv_scalespace_value(&old,(double)old_h);
+  double new_lo = _sv_scalespace_value(&new,0);
+  double new_hi = _sv_scalespace_value(&new,(double)new_h);
+  double newscale = (new_hi-new_lo)/new_h;
+  double oldscale = old_h/(old_hi-old_lo);
+  
+  for(y=0;y<new_h;y++){
+    double yval = (y)*newscale+new_lo;
+    double map = ((yval-old_lo)*oldscale);
+    int base = (int)floor(map);
+    int del = rint((map - floor(map))*64.);
+    /* hack to overwhelm roundoff error; this is inside a purely
+       temporary cosmetic approximation anyway */
+    if(base>0 && del==0){
+      mapbase[y]=base-1;
+      mapdel[y]=64;
+    }else{
+      mapbase[y]=base;
+      mapdel[y]=del;
+    }
+  }
+
+  
+  for(y=0;y<new_h;y++){
+    int base = mapbase[y];
+    int *new_column = &newdata[y*neww];
+    _sv_spinner_set_busy(sp);
+
+    if(base<0 || base>=(old_h-1)){
+      for(x=0;x<w;x++)
+	new_column[x] = -1;
+    }else{
+      int del = mapdel[y];
+      int *old_column = &olddata[base*oldw];
+
+      for(x=0;x<w;x++){
+	int A = old_column[x];
+	int B = old_column[x+oldw];
+	if(A<0 || B<0)
+	  new_column[x]=-1;
+	else
+	  new_column[x]= A + (((B-A)*del)>>6);
+      }
+    }
+  }
 }
 
-void _sv_panel_update_menus(sv_panel_t *p){
+static void _sv_panel2d_fast_scale(_sv_spinner_t *sp, 
+				   int *newdata, 
+				   _sv_scalespace_t xnew,
+				   _sv_scalespace_t ynew,
+				   int *olddata,
+				   _sv_scalespace_t xold,
+				   _sv_scalespace_t yold){
+  
+  int new_w = xnew.pixels;
+  int new_h = ynew.pixels;
+  int old_w = xold.pixels;
+  int old_h = yold.pixels;
 
-  // is undo active?
-  if(!_sv_undo_stack ||
-     !_sv_undo_level){
-    gtk_widget_set_sensitive(_gtk_menu_get_item(GTK_MENU(p->private->popmenu),4),FALSE);
+  if(new_w > old_w){
+    _sv_panel2d_fast_scale_y(sp,olddata,newdata,old_w,new_w,ynew,yold);
+    _sv_panel2d_fast_scale_x(sp,newdata,new_w,new_h,xnew,xold);
   }else{
-    gtk_widget_set_sensitive(_gtk_menu_get_item(GTK_MENU(p->private->popmenu),4),TRUE);
+    _sv_panel2d_fast_scale_x(sp,olddata,old_w,old_h,xnew,xold);
+    _sv_panel2d_fast_scale_y(sp,olddata,newdata,old_w,new_w,ynew,yold);
   }
+}
 
-  // is redo active?
-  if(!_sv_undo_stack ||
-     !_sv_undo_stack[_sv_undo_level] ||
-     !_sv_undo_stack[_sv_undo_level+1]){
-    gtk_widget_set_sensitive(_gtk_menu_get_item(GTK_MENU(p->private->popmenu),5),FALSE);
-  }else{
-    gtk_widget_set_sensitive(_gtk_menu_get_item(GTK_MENU(p->private->popmenu),5),TRUE);
+// call only from main gtk thread
+static void _sv_panel2d_mark_recompute(sv_panel_t *p){
+  if(!p->private->realized) return;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+
+  if(plot && GTK_WIDGET_REALIZED(GTK_WIDGET(plot))){
+    _sv_panel_dirty_plot(p);
   }
+}
 
-  // are we starting or enacting a zoom box?
-  if(p->private->oldbox_active){ 
-    _gtk_menu_alter_item_label(GTK_MENU(p->private->popmenu),6,"Zoom to box");
+static void _sv_panel2d_update_crosshairs(sv_panel_t *p){
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  double x=0,y=0;
+  int i;
+  
+  for(i=0;i<p->dimensions;i++){
+    sv_dim_t *d = p->dimension_list[i].d;
+    if(d == p->private->x_d)
+      x = d->val;
+    if(d == p->private->y_d)
+      y = d->val;
+    
+  }
+  
+  _sv_plot_set_crosshairs(plot,x,y);
+  _sv_panel_dirty_legend(p);
+}
+
+static void _sv_panel2d_center_callback(sv_dim_list_t *dptr){
+  sv_dim_t *d = dptr->d;
+  sv_panel_t *p = dptr->p;
+  int axisp = (d == p->private->x_d || d == p->private->y_d);
+
+  if(!axisp){
+    // mid slider of a non-axis dimension changed, rerender
+    _sv_panel2d_mark_recompute(p);
   }else{
-    _gtk_menu_alter_item_label(GTK_MENU(p->private->popmenu),6,"Start zoom box");
+    // mid slider of an axis dimension changed, move crosshairs
+    _sv_panel2d_update_crosshairs(p);
   }
+}
 
-  // make sure menu reflects plot configuration
-  _gtk_menu_alter_item_right(GTK_MENU(p->private->popmenu),
-			     _sv_propmap_label_pos(menu,"Background"),
-			     bgmap[_sv_propmap_pos(bgmap,p->private->bg_type)]->left);
+static void _sv_panel2d_bracket_callback(sv_dim_list_t *dptr){
+  sv_dim_t *d = dptr->d;
+  sv_panel_t *p = dptr->p;
+  int axisp = (d == p->private->x_d || d == p->private->y_d);
 
-  _gtk_menu_alter_item_right(GTK_MENU(p->private->popmenu),
-			     _sv_propmap_label_pos(menu,"Text color"),
-			     textmap[_sv_propmap_pos(textmap,PLOT(p->private->graph)->bg_inv)]->left);
+  if(axisp)
+    _sv_panel2d_mark_recompute(p);
+    
+}
 
-  _gtk_menu_alter_item_right(GTK_MENU(p->private->popmenu),
-			     _sv_propmap_label_pos(menu,"Grid mode"),
-			     gridmap[_sv_propmap_pos(gridmap,PLOT(p->private->graph)->grid_mode)]->left);
-   {
-    char buffer[80];
-    snprintf(buffer,60,"%d:%d",p->private->oversample_n,p->private->oversample_d);
-    if(p->private->def_oversample_n == p->private->oversample_n &&
-       p->private->def_oversample_d == p->private->oversample_d)
-      strcat(buffer," (default)");
-    _gtk_menu_alter_item_right(GTK_MENU(p->private->popmenu),
-			       _sv_propmap_label_pos(menu,"Sampling"),buffer);
+static void _sv_panel2d_dimchange_callback(GtkWidget *button,gpointer in){
+  sv_panel_t *p = (sv_panel_t *)in;
+
+  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))){
+
+    _sv_undo_push();
+    _sv_undo_suspend();
+
+    _sv_plot_unset_box(PLOT(p->private->graph));
+    _sv_panel2d_update_xysel(p);
+
+    _sv_panel2d_clear_pane(p);
+    _sv_panel2d_mark_recompute(p);
+    _sv_panel2d_update_crosshairs(p);
+
+    _sv_undo_resume();
   }
 }
 
-static gboolean panel_keypress(GtkWidget *widget,
-				 GdkEventKey *event,
-				 gpointer in){
-  sv_panel_t *p = (sv_panel_t *)in;
-  //  sv_panel2d_t *p2 = (sv_panel2d_t *)p->internal;
+static void _sv_panel2d_crosshairs_callback(sv_panel_t *p){
+  double x=PLOT(p->private->graph)->selx;
+  double y=PLOT(p->private->graph)->sely;
+  int i;
   
-  // check if the widget with focus is an Entry
-  GtkWidget *focused = gtk_window_get_focus(GTK_WINDOW(widget));
-  int entryp = (focused?GTK_IS_ENTRY(focused):0);
+  _sv_undo_push();
+  _sv_undo_suspend();
 
-  // don't swallow modified keypresses
-  if(event->state&GDK_MOD1_MASK) return FALSE;
-  if(event->state&GDK_CONTROL_MASK)return FALSE;
+  //plot_snap_crosshairs(PLOT(p->private->graph));
 
-  switch(event->keyval){
-  case GDK_Home:case GDK_KP_Begin:
-  case GDK_End:case GDK_KP_End:
-  case GDK_Up:case GDK_KP_Up:
-  case GDK_Down:case GDK_KP_Down:
-  case GDK_Left:case GDK_KP_Left:
-  case GDK_Right:case GDK_KP_Right:
-  case GDK_minus:case GDK_KP_Subtract:
-  case GDK_plus:case GDK_KP_Add:
-  case GDK_period:case GDK_KP_Decimal:
-  case GDK_0:case GDK_KP_0:
-  case GDK_1:case GDK_KP_1:
-  case GDK_2:case GDK_KP_2:
-  case GDK_3:case GDK_KP_3:
-  case GDK_4:case GDK_KP_4:
-  case GDK_5:case GDK_KP_5:
-  case GDK_6:case GDK_KP_6:
-  case GDK_7:case GDK_KP_7:
-  case GDK_8:case GDK_KP_8:
-  case GDK_9:case GDK_KP_9:
-  case GDK_Tab:case GDK_KP_Tab:
-  case GDK_ISO_Left_Tab:
-  case GDK_Delete:case GDK_KP_Delete:
-  case GDK_Insert:case GDK_KP_Insert:
-    return FALSE;
+  for(i=0;i<p->dimensions;i++){
+    sv_dim_t *d = p->dimension_list[i].d;
+    if(d == p->private->x_d){
+      _sv_dim_widget_set_thumb(p->private->dim_scales[i],1,x);
+    }
+
+    if(d == p->private->y_d){
+      _sv_dim_widget_set_thumb(p->private->dim_scales[i],1,y);
+    }
+    
+    p->private->oldbox_active = 0;
   }
+
+  // dimension setting might have enforced granularity restrictions;
+  // have the display reflect that
+  x = p->private->x_d->val;
+  y = p->private->y_d->val;
+
+  _sv_plot_set_crosshairs(PLOT(p->private->graph),x,y);
+
+  _sv_panel_dirty_legend(p);
+  _sv_undo_resume();
+}
+
+static void _sv_panel2d_box_callback(void *in, int state){
+  sv_panel_t *p = (sv_panel_t *)in;
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
   
-  if(entryp){
-    // we still filter, but differently
-    switch(event->keyval){
-    case GDK_BackSpace:
-    case GDK_e:case GDK_E:
-    case GDK_Return:case GDK_ISO_Enter:
-      return FALSE;
-    }
+  switch(state){
+  case 0: // box set
+    _sv_undo_push();
+    _sv_plot_box_vals(plot,p2->oldbox);
+    p->private->oldbox_active = plot->box_active;
+    break;
+  case 1: // box activate
+    _sv_undo_push();
+    _sv_undo_suspend();
+
+    _sv_panel2d_crosshairs_callback(p);
+
+    _sv_dim_widget_set_thumb(p2->x_scale,0,p2->oldbox[0]);
+    _sv_dim_widget_set_thumb(p2->x_scale,2,p2->oldbox[1]);
+    _sv_dim_widget_set_thumb(p2->y_scale,0,p2->oldbox[2]);
+    _sv_dim_widget_set_thumb(p2->y_scale,2,p2->oldbox[3]);
+    p->private->oldbox_active = 0;
+    _sv_undo_resume();
+    break;
   }
-        
-  /* non-control keypresses */
-  switch(event->keyval){
-  case GDK_b:
-    cycle_bg(p);
-    return TRUE;
-  case GDK_B:
-    cycleB_bg(p);
-    return TRUE;
-  case GDK_t:case GDK_T:
-    cycle_text(p);
-    return TRUE;
-  case GDK_g:
-    cycle_grid(p);
-    return TRUE;
-  case GDK_G:
-    cycleB_grid(p);
-    return TRUE;
-  case GDK_m:
-    cycle_res(p);
-    return TRUE;
-  case GDK_M:
-    cycleB_res(p);
-    return TRUE;
+  _sv_panel_update_menus(p);
+}
 
-  case GDK_s:
-    wrap_save(p,NULL);
-    return TRUE;
-  case GDK_o:
-    wrap_load(p,NULL);
-    return TRUE;
+void _sv_panel2d_maintain_cache(sv_panel_t *p, _sv_bythread_cache_2d_t *c, int w){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  
+  /* toplevel initialization */
+  if(c->fout == 0){
+    int i,count=0;
     
-   case GDK_Escape:
-     wrap_escape(p,NULL);
-    return TRUE;
+    /* allocate output temporary buffer */
+    for(i=0;i<p2->used_functions;i++){
+      int fnum = p2->used_function_list[i]->number;
+      sv_func_t *f = _sv_function_list[fnum];
+      count += f->outputs;
+    }
+    c->fout = calloc(count, sizeof(*c->fout));
 
-  case GDK_Return:case GDK_ISO_Enter:
-    wrap_enter(p,NULL);
-    return TRUE;
-   
-  case GDK_Q:
-  case GDK_q:
-    // quit
-    _sv_clean_exit();
-    return TRUE;
+    /* objective line buffer index */
+    c->y_map = calloc(p2->y_obj_num,sizeof(*c->y_map));
+    for(i=0;i<p2->y_obj_num;i++)
+      c->y_map[i] = calloc(w,sizeof(**c->y_map));
+    c->storage_width = w;
+  }
+  
+  /* anytime the data width changes */
+  if(c->storage_width != w){
+    int i;
+    c->storage_width = w;
     
-  case GDK_BackSpace:
-    // undo 
-    _sv_undo_down();
-    return TRUE;
-    
-  case GDK_r:
-  case GDK_space:
-    // redo/forward
-    _sv_undo_up();
-    return TRUE;
+    for(i=0;i<p2->y_obj_num;i++)
+      c->y_map[i] = realloc(c->y_map[i],w*sizeof(**c->y_map));
 
-  case GDK_p:
-    _sv_panel_print(p,NULL);
-    return TRUE;
+  }
+}
 
-  case GDK_l:
-    wrap_legend(p,NULL);
-    return TRUE;
-  } 
 
-  return FALSE;
+// subtype entry point for plot remaps; lock held
+static int _sv_panel2d_map_redraw(sv_panel_t *p, _sv_bythread_cache_t *c){
+  return _sv_panel2d_remap(p,&c->p2);
 }
 
-void _sv_panel_realize(sv_panel_t *p){
-  if(p && !p->private->realized){
-    p->private->realize(p);
+// subtype entry point for legend redraws; lock held
+static int _sv_panel2d_legend_redraw(sv_panel_t *p){
+  _sv_plot_t *plot = PLOT(p->private->graph);
 
-    g_signal_connect (G_OBJECT (p->private->toplevel), "key-press-event",
-		      G_CALLBACK (panel_keypress), p);
-    gtk_window_set_title (GTK_WINDOW (p->private->toplevel), p->name);
+  if(p->private->legend_progress_count)return 0;
+  p->private->legend_progress_count++;
+  _sv_panel2d_update_legend(p);
+  _sv_panel_clean_legend(p);
 
-    p->private->realized=1;
+  gdk_unlock();
+  _sv_plot_draw_scales(plot);
+  gdk_lock();
 
-    // generic things that happen in all panel realizations...
+  _sv_plot_expose_request(plot);
+  return 1;
+}
 
-    // text black or white in the plot?
-    decide_text_inv(p);
-    p->private->popmenu = _gtk_menu_new_twocol(p->private->toplevel, menu, p);
-    _sv_panel_update_menus(p);
+// subtype entry point for recomputation; lock held
+static int _sv_panel2d_compute(sv_panel_t *p,
+			       _sv_bythread_cache_t *c){
+
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot;
+  
+  int pw,ph,dw,dh,i,d;
+  int serialno;
+  double x_min, x_max;
+  double y_min, y_max;
+  int x_d=-1, y_d=-1;
+  _sv_scalespace_t sx,sx_v,sx_i;
+  _sv_scalespace_t sy,sy_v,sy_i;
+
+  plot = PLOT(p->private->graph);
+  pw = plot->x.pixels;
+  ph = plot->y.pixels;
+  
+  x_d = p->private->x_d->number;
+  y_d = p->private->y_d->number;
+
+  // beginning of computation init
+  if(p->private->plot_progress_count==0){
+    int remapflag = 0;
+
+    _sv_scalespace_t old_x = p2->x;
+    _sv_scalespace_t old_y = p2->y;
+    _sv_scalespace_t old_xv = p2->x_v;
+    _sv_scalespace_t old_yv = p2->y_v;
+
+    // generate new scales
+    _sv_dim_scales(p->private->x_d, 
+		   p->private->x_d->bracket[0],
+		   p->private->x_d->bracket[1],
+		   pw,pw * p->private->oversample_n / p->private->oversample_d,
+		   plot->scalespacing,
+		   p->private->x_d->legend,
+		   &sx,
+		   &sx_v,
+		   &sx_i);
+    _sv_dim_scales(p->private->y_d, 
+		   p->private->y_d->bracket[1],
+		   p->private->y_d->bracket[0],
+		   ph,ph * p->private->oversample_n / p->private->oversample_d,
+		   plot->scalespacing,
+		   p->private->y_d->legend,
+		   &sy,
+		   &sy_v,
+		   &sy_i);
     
+    p2->x = sx;
+    p2->x_v = sx_v;
+    p2->x_i = sx_i;
+    p2->y = sy;
+    p2->y_v = sy_v;
+    p2->y_i = sy_i;
+
+    plot->x = sx;
+    plot->y = sy;
+    plot->x_v = sx_v;
+    plot->y_v = sy_v;
+
+    p->private->plot_progress_count++;
+    p->private->plot_serialno++; // we're about to free the old data rectangles
+
+    // realloc/fast scale the current data contents if appropriate
+    if(memcmp(&sx_v,&old_xv,sizeof(sx_v)) || memcmp(&sy_v,&old_yv,sizeof(sy_v))){
+      
+      // maintain data planes
+      for(i=0;i<p2->y_obj_num;i++){
+	// allocate new storage
+	int *newmap = calloc(sx_v.pixels*sy_v.pixels,sizeof(*newmap));
+	int *oldmap = p2->y_map[i];
+	int j;
+	
+	for(j=0;j<sx_v.pixels*sy_v.pixels;j++)
+	  newmap[j]=-1;
+	
+	// zoom scale data in map planes as placeholder for render
+	if(oldmap){
+	  _sv_panel2d_fast_scale(p->private->spinner,newmap, sx_v, sy_v,
+				 oldmap,old_xv, old_yv);
+	  free(oldmap);
+	}
+	p2->y_map[i] = newmap; 
+      }
+      remapflag = 1;
+    }
+
+    // realloc render planes if appropriate
+    if(memcmp(&sx,&old_x,sizeof(sx)) || memcmp(&sy,&old_y,sizeof(sy))){
+      for(i=0;i<p2->y_obj_num;i++){
+
+	// y planes
+	if(p2->y_planes[i])
+	  free(p2->y_planes[i]);
+	p2->y_planes[i] = calloc(sx.pixels*sy.pixels,sizeof(**p2->y_planes));
+
+	// todo lists
+	if(p2->y_planetodo[i])
+	  free(p2->y_planetodo[i]);
+	p2->y_planetodo[i] = calloc(sy.pixels,sizeof(**p2->y_planetodo));
+	
+      }
+
+      if(p2->bg_todo)
+	free(p2->bg_todo);
+      p2->bg_todo=calloc(ph,sizeof(*p2->bg_todo));
+      
+      remapflag = 1;
+    }
+
+    if(remapflag){
+      _sv_panel2d_mark_map_full(p);
+      _sv_panel_dirty_map(p);
+
+      gdk_unlock ();      
+      _sv_plot_draw_scales(plot); // this should happen outside lock
+      gdk_lock ();      
+    }
+
+    _sv_map_set_throttle_time(p); // swallow the first 'throttled' remap which would only be a single line;
+
+    return 1;
+  }else{
+    sx = p2->x;
+    sx_v = p2->x_v;
+    sx_i = p2->x_i;
+    sy = p2->y;
+    sy_v = p2->y_v;
+    sy_i = p2->y_i;
+    serialno = p->private->plot_serialno; 
   }
-}
 
-void _sv_map_set_throttle_time(sv_panel_t *p){
-  struct timeval now;
-  gettimeofday(&now,NULL);
+  dw = sx_v.pixels;
+  dh = sy_v.pixels;
 
-  p->private->last_map_throttle = now.tv_sec*1000 + now.tv_usec/1000;
-}
+  if(p->private->plot_progress_count>dh) return 0;
 
-static int test_throttle_time(sv_panel_t *p){
-  struct timeval now;
-  long test;
-  gettimeofday(&now,NULL);
+  _sv_panel2d_maintain_cache(p,&c->p2,dw);
   
-  test = now.tv_sec*1000 + now.tv_usec/1000;
-  if(p->private->last_map_throttle + 500 < test) 
-    return 1;
+  d = p->dimensions;
 
-  return 0;  
-}
+  /* render using local dimension array; several threads will be
+     computing objectives */
+  double dim_vals[_sv_dimensions];
+  int y = _v_swizzle(p->private->plot_progress_count-1,dh);
+  p->private->plot_progress_count++;
 
-/* request a recomputation with full setup (eg, scales,
-   etc) */
-void _sv_panel_recompute(sv_panel_t *p){
-  gdk_threads_enter ();
-  p->private->request_compute(p);
-  gdk_threads_leave ();
-}
+  x_min = _sv_scalespace_value(&p2->x_i,0);
+  x_max = _sv_scalespace_value(&p2->x_i,dw);
 
-/* use these to request a render/compute action after everything has
-   already been set up to perform the op; the above full recompute
-   request will eventually trigger a call here to kick off the actual
-   computation.  Do panel subtype-specific setup, then wake workers
-   with one of the below */
-void _sv_panel_dirty_plot(sv_panel_t *p){
-  gdk_threads_enter ();
-  p->private->plot_active = 1;
-  p->private->plot_serialno++;
-  p->private->plot_progress_count=0;
-  p->private->plot_complete_count=0;
-  gdk_threads_leave ();
-  _sv_wake_workers();
+  y_min = _sv_scalespace_value(&p2->y_i,0);
+  y_max = _sv_scalespace_value(&p2->y_i,dh);
+
+  // Initialize local dimension value array
+  for(i=0;i<_sv_dimensions;i++){
+    sv_dim_t *dim = _sv_dimension_list[i];
+    dim_vals[i]=dim->val;
+  }
+
+  /* unlock for computation */
+  gdk_unlock ();
+    
+  dim_vals[y_d]=_sv_scalespace_value(&sy_i, y);
+  _sv_panel2d_compute_line(p, serialno, dw, y, x_d, sx_i, dim_vals, &c->p2);
+
+  gdk_lock ();
+
+  if(p->private->plot_serialno == serialno){
+    p->private->plot_complete_count++;
+    _sv_panel2d_mark_map_line_y(p,y);
+    if(p->private->plot_complete_count>=dh){ 
+      _sv_panel_dirty_map(p);
+      _sv_panel_dirty_legend(p);
+      _sv_panel_clean_plot(p);
+    }else
+      _sv_panel_dirty_map_throttled(p); 
+  }
+
+  return 1;
 }
 
-void _sv_panel_dirty_map(sv_panel_t *p){
-  gdk_threads_enter ();
-  p->private->map_active = 1;
-  p->private->map_serialno++;
-  p->private->map_progress_count=0;
-  p->private->map_complete_count=0;
-  gdk_threads_leave ();
-  _sv_wake_workers();
+// only called for resize events
+static void _sv_panel2d_recompute_callback(void *ptr){
+  sv_panel_t *p = (sv_panel_t *)ptr;
+  int i;
+
+  gdk_lock ();
+  _sv_panel2d_mark_recompute(p);
+  _sv_panel2d_compute(p,NULL); // initial scale setup
+
+  // temporary: blank background to checks
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  int pw = plot->x.pixels;
+  int ph = plot->y.pixels;
+  for(i=0;i<ph;i++)
+    render_checks((_sv_ucolor_t *)plot->datarect+pw*i, pw, i);
+  
+  gdk_unlock();
 }
 
-void _sv_panel_dirty_map_throttled(sv_panel_t *p){
-  gdk_threads_enter ();
-  if(!p->private->map_active && test_throttle_time(p)){
-     _sv_panel_dirty_map(p);
+static void _sv_panel2d_undo_log(_sv_panel_undo_t *u, sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int i;
+
+  // alloc fields as necessary
+  
+  if(!u->mappings)
+    u->mappings =  calloc(p->objectives,sizeof(*u->mappings));
+  if(!u->scale_vals[0])
+    u->scale_vals[0] =  calloc(p->objectives,sizeof(**u->scale_vals));
+  if(!u->scale_vals[1])
+    u->scale_vals[1] =  calloc(p->objectives,sizeof(**u->scale_vals));
+  if(!u->scale_vals[2])
+    u->scale_vals[2] =  calloc(p->objectives,sizeof(**u->scale_vals));
+
+  // populate undo
+  for(i=0;i<p->objectives;i++){
+    u->mappings[i] = p2->mappings[i].mapnum;
+    u->scale_vals[0][i] = _sv_slider_get_value(p2->range_scales[i],0);
+    u->scale_vals[1][i] = _sv_slider_get_value(p2->range_scales[i],1);
+    u->scale_vals[2][i] = _sv_slider_get_value(p2->range_scales[i],2);
   }
-  gdk_threads_leave ();
+  
+  u->x_d = p2->x_dnum;
+  u->y_d = p2->y_dnum;
+  u->box[0] = p2->oldbox[0];
+  u->box[1] = p2->oldbox[1];
+  u->box[2] = p2->oldbox[2];
+  u->box[3] = p2->oldbox[3];
+  u->box_active = p->private->oldbox_active;
 }
 
-void _sv_panel_dirty_legend(sv_panel_t *p){
-  gdk_threads_enter ();
-  p->private->legend_active = 1;
-  p->private->legend_serialno++;
-  p->private->legend_progress_count=0;
-  p->private->legend_complete_count=0;
-  gdk_threads_leave ();
-  _sv_wake_workers();
-}
+static void _sv_panel2d_undo_restore(_sv_panel_undo_t *u, sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  _sv_plot_t *plot = PLOT(p->private->graph);
+  int i;
+  
+  // go in through widgets
+  for(i=0;i<p->objectives;i++){
+    gtk_combo_box_set_active(GTK_COMBO_BOX(p2->range_pulldowns[i]),u->mappings[i]);
+    _sv_slider_set_value(p2->range_scales[i],0,u->scale_vals[0][i]);
+    _sv_slider_set_value(p2->range_scales[i],1,u->scale_vals[1][i]);
+    _sv_slider_set_value(p2->range_scales[i],2,u->scale_vals[2][i]);
+  }
 
-/* use these to signal a computation is completed */
-void _sv_panel_clean_plot(sv_panel_t *p){
-  gdk_threads_enter ();
-  p->private->plot_active = 0;
-  gdk_threads_leave ();
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_xb[u->x_d]),TRUE);
+  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_yb[u->y_d]),TRUE);
+
+  _sv_panel2d_update_xysel(p);
+
+  if(u->box_active){
+    p2->oldbox[0] = u->box[0];
+    p2->oldbox[1] = u->box[1];
+    p2->oldbox[2] = u->box[2];
+    p2->oldbox[3] = u->box[3];
+    _sv_plot_box_set(plot,u->box);
+    p->private->oldbox_active = 1;
+  }else{
+    _sv_plot_unset_box(plot);
+    p->private->oldbox_active = 0;
+  }
 }
 
-void _sv_panel_clean_map(sv_panel_t *p){
-  gdk_threads_enter ();
-  p->private->map_active = 0;
-  gdk_threads_leave ();
+static void _sv_panel2d_realize(sv_panel_t *p){
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int i;
+
+  _sv_undo_suspend();
+
+  p->private->toplevel = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  g_signal_connect_swapped (G_OBJECT (p->private->toplevel), "delete-event",
+			    G_CALLBACK (_sv_clean_exit), (void *)SIGINT);
+ 
+  // add border to sides with hbox/padding
+  GtkWidget *borderbox =  gtk_hbox_new(0,0);
+  gtk_container_add (GTK_CONTAINER (p->private->toplevel), borderbox);
+
+  // main layout vbox
+  p->private->topbox = gtk_vbox_new(0,0);
+  gtk_box_pack_start(GTK_BOX(borderbox), p->private->topbox, 1,1,4);
+  gtk_container_set_border_width (GTK_CONTAINER (p->private->toplevel), 1);
+
+  /* spinner, top bar */
+  {
+    GtkWidget *hbox = gtk_hbox_new(0,0);
+    gtk_box_pack_start(GTK_BOX(p->private->topbox), hbox, 0,0,0);
+    gtk_box_pack_end(GTK_BOX(hbox),GTK_WIDGET(p->private->spinner),0,0,0);
+  }
+
+  /* plotbox, graph */
+  {
+    p->private->graph = GTK_WIDGET(_sv_plot_new(_sv_panel2d_recompute_callback,p,
+						(void *)(void *)_sv_panel2d_crosshairs_callback,p,
+						_sv_panel2d_box_callback,p,0)); 
+    p->private->plotbox = p->private->graph;
+    gtk_box_pack_start(GTK_BOX(p->private->topbox), p->private->plotbox, 1,1,2);
+  }
+
+  /* obj box */
+  {
+    p2->obj_table = gtk_table_new(p->objectives, 5, 0);
+    gtk_box_pack_start(GTK_BOX(p->private->topbox), p2->obj_table, 0,0,1);
+
+    /* objective sliders */
+    p2->range_scales = calloc(p->objectives,sizeof(*p2->range_scales));
+    p2->range_pulldowns = calloc(p->objectives,sizeof(*p2->range_pulldowns));
+    p2->alphadel = calloc(p->objectives,sizeof(*p2->alphadel));
+    p2->mappings = calloc(p->objectives,sizeof(*p2->mappings));
+    for(i=0;i<p->objectives;i++){
+      GtkWidget **sl = calloc(3,sizeof(*sl));
+      sv_obj_t *o = p->objective_list[i].o;
+      int lo = o->scale->val_list[0];
+      int hi = o->scale->val_list[o->scale->vals-1];
+      
+      /* label */
+      GtkWidget *label = gtk_label_new(o->name);
+      gtk_misc_set_alignment(GTK_MISC(label),1.,.5);
+      gtk_table_attach(GTK_TABLE(p2->obj_table),label,0,1,i,i+1,
+		       GTK_FILL,0,8,0);
+      
+      /* mapping pulldown */
+      {
+	GtkWidget *menu=_gtk_combo_box_new_markup();
+	int j;
+	for(j=0;j<_sv_mapping_names();j++)
+	  gtk_combo_box_append_text (GTK_COMBO_BOX (menu), _sv_mapping_name(j));
+	gtk_combo_box_set_active(GTK_COMBO_BOX(menu),0);
+	g_signal_connect (G_OBJECT (menu), "changed",
+			  G_CALLBACK (_sv_panel2d_mapchange_callback), p->objective_list+i);
+	gtk_table_attach(GTK_TABLE(p2->obj_table),menu,4,5,i,i+1,
+			 GTK_SHRINK,GTK_SHRINK,0,0);
+	p2->range_pulldowns[i] = menu;
+      }
+
+      /* the range mapping slices/slider */ 
+      sl[0] = _sv_slice_new(_sv_panel2d_map_callback,p->objective_list+i);
+      sl[1] = _sv_slice_new(_sv_panel2d_map_callback,p->objective_list+i);
+      sl[2] = _sv_slice_new(_sv_panel2d_map_callback,p->objective_list+i);
+      
+      gtk_table_attach(GTK_TABLE(p2->obj_table),sl[0],1,2,i,i+1,
+		       GTK_EXPAND|GTK_FILL,0,0,0);
+      gtk_table_attach(GTK_TABLE(p2->obj_table),sl[1],2,3,i,i+1,
+		       GTK_EXPAND|GTK_FILL,0,0,0);
+      gtk_table_attach(GTK_TABLE(p2->obj_table),sl[2],3,4,i,i+1,
+		       GTK_EXPAND|GTK_FILL,0,0,0);
+      p2->range_scales[i] = _sv_slider_new((_sv_slice_t **)sl,3,o->scale->label_list,o->scale->val_list,
+				       o->scale->vals,_SV_SLIDER_FLAG_INDEPENDENT_MIDDLE);
+      gtk_table_set_col_spacing(GTK_TABLE(p2->obj_table),3,5);
+
+      _sv_slice_thumb_set((_sv_slice_t *)sl[0],lo);
+      _sv_slice_thumb_set((_sv_slice_t *)sl[1],lo);
+      _sv_slice_thumb_set((_sv_slice_t *)sl[2],hi);
+      _sv_mapping_setup(&p2->mappings[i],0.,1.,0);
+      _sv_slider_set_gradient(p2->range_scales[i], &p2->mappings[i]);
+    }
+  }
+
+  /* dims */
+  {
+    p2->dim_table = gtk_table_new(p->dimensions,4,0);
+    gtk_box_pack_start(GTK_BOX(p->private->topbox), p2->dim_table, 0,0,4);
+    
+    GtkWidget *first_x = NULL;
+    GtkWidget *first_y = NULL;
+    GtkWidget *pressed_y = NULL;
+    p->private->dim_scales = calloc(p->dimensions,sizeof(*p->private->dim_scales));
+    p2->dim_xb = calloc(p->dimensions,sizeof(*p2->dim_xb));
+    p2->dim_yb = calloc(p->dimensions,sizeof(*p2->dim_yb));
+    
+    for(i=0;i<p->dimensions;i++){
+      sv_dim_t *d = p->dimension_list[i].d;
+      
+      /* label */
+      GtkWidget *label = gtk_label_new(d->legend);
+      gtk_misc_set_alignment(GTK_MISC(label),1.,.5);
+      gtk_table_attach(GTK_TABLE(p2->dim_table),label,0,1,i,i+1,
+		       GTK_FILL,0,5,0);
+      
+      /* x/y radio buttons */
+      if(!(d->flags & SV_DIM_NO_X)){
+	if(first_x)
+	  p2->dim_xb[i] = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(first_x),"X");
+	else{
+	  first_x = p2->dim_xb[i] = gtk_radio_button_new_with_label(NULL,"X");
+	  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_xb[i]),TRUE);
+	}
+	gtk_table_attach(GTK_TABLE(p2->dim_table),p2->dim_xb[i],1,2,i,i+1,
+			 GTK_SHRINK,0,3,0);
+      }
+      
+      if(!(d->flags & SV_DIM_NO_Y)){
+	if(first_y)
+	  p2->dim_yb[i] = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(first_y),"Y");
+	else
+	  first_y = p2->dim_yb[i] = gtk_radio_button_new_with_label(NULL,"Y");
+	if(!pressed_y && p2->dim_xb[i]!=first_x){
+	  pressed_y = p2->dim_yb[i];
+	  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(p2->dim_yb[i]),TRUE);
+	}
+	gtk_table_attach(GTK_TABLE(p2->dim_table),p2->dim_yb[i],2,3,i,i+1,
+			 GTK_SHRINK,0,3,0);
+      }
+      
+      p->private->dim_scales[i] = 
+	_sv_dim_widget_new(p->dimension_list+i,_sv_panel2d_center_callback,_sv_panel2d_bracket_callback);
+      
+      gtk_table_attach(GTK_TABLE(p2->dim_table),
+		       p->private->dim_scales[i]->t,
+		       3,4,i,i+1,
+		       GTK_EXPAND|GTK_FILL,0,0,0);
+      
+    }
+    for(i=0;i<p->dimensions;i++){
+      if(p2->dim_xb[i])
+	g_signal_connect (G_OBJECT (p2->dim_xb[i]), "toggled",
+			  G_CALLBACK (_sv_panel2d_dimchange_callback), p);
+      if(p2->dim_yb[i])
+	g_signal_connect (G_OBJECT (p2->dim_yb[i]), "toggled",
+			  G_CALLBACK (_sv_panel2d_dimchange_callback), p);
+    }
+  }
+
+  _sv_panel2d_update_xysel(p);
+
+  gtk_widget_realize(p->private->toplevel);
+  gtk_widget_realize(p->private->graph);
+  gtk_widget_realize(GTK_WIDGET(p->private->spinner));
+  gtk_widget_show_all(p->private->toplevel);
+  _sv_panel2d_update_xysel(p); // yes, this was already done; however,
+			       // gtk clobbered the event setup on the
+			       // insensitive buttons when it realized
+			       // them.  This call will restore them.
+
+  _sv_undo_resume();
 }
 
-void _sv_panel_clean_legend(sv_panel_t *p){
-  gdk_threads_enter ();
-  p->private->legend_active = 0;
-  gdk_threads_leave ();
+static int _sv_panel2d_save(sv_panel_t *p, xmlNodePtr pn){  
+  _sv_panel2d_t *p2 = p->subtype->p2;
+  int ret=0,i;
+
+  xmlNodePtr n;
+
+  xmlNewProp(pn, (xmlChar *)"type", (xmlChar *)"2d");
+
+  // box
+  if(p->private->oldbox_active){
+    xmlNodePtr boxn = xmlNewChild(pn, NULL, (xmlChar *) "box", NULL);
+    _xmlNewPropF(boxn, "x1", p2->oldbox[0]);
+    _xmlNewPropF(boxn, "x2", p2->oldbox[1]);
+    _xmlNewPropF(boxn, "y1", p2->oldbox[2]);
+    _xmlNewPropF(boxn, "y2", p2->oldbox[3]);
+  }
+
+  // objective map settings
+  for(i=0;i<p->objectives;i++){
+    sv_obj_t *o = p->objective_list[i].o;
+    xmlNodePtr on = xmlNewChild(pn, NULL, (xmlChar *) "objective", NULL);
+    _xmlNewPropI(on, "position", i);
+    _xmlNewPropI(on, "number", o->number);
+    _xmlNewPropS(on, "name", o->name);
+    _xmlNewPropS(on, "type", o->output_types);
+    
+    // right now Y is the only type; the below is Y-specific
+    n = xmlNewChild(on, NULL, (xmlChar *) "y-map", NULL);
+    _xmlNewPropS(n, "color", _sv_mapping_name(p2->mappings[i].mapnum));
+    _xmlNewPropF(n, "low-bracket", _sv_slider_get_value(p2->range_scales[i],0));
+    _xmlNewPropF(n, "alpha", _sv_slider_get_value(p2->range_scales[i],1));
+    _xmlNewPropF(n, "high-bracket", _sv_slider_get_value(p2->range_scales[i],2));
+  }
+
+  // x/y dim selection
+  n = xmlNewChild(pn, NULL, (xmlChar *) "axes", NULL);
+  _xmlNewPropI(n, "xpos", p2->x_dnum);
+  _xmlNewPropI(n, "ypos", p2->y_dnum);
+
+  return ret;
 }
 
-int sv_panel_oversample(int number,
-			int numer,
-			int denom){
+int _sv_panel2d_load(sv_panel_t *p,
+		     _sv_panel_undo_t *u,
+		     xmlNodePtr pn,
+		     int warn){
+  int i;
+
+  // check type
+  _xmlCheckPropS(pn,"type","2d", "Panel %d type mismatch in save file.",p->number,&warn);
   
-  if(number<0){
-    fprintf(stderr,"sv_panel_background: Panel number must be >= 0\n");
-    return -EINVAL;
+  // box
+  u->box_active = 0;
+  _xmlGetChildPropFPreserve(pn, "box", "x1", &u->box[0]);
+  _xmlGetChildPropFPreserve(pn, "box", "x2", &u->box[1]);
+  _xmlGetChildPropFPreserve(pn, "box", "y1", &u->box[2]);
+  _xmlGetChildPropFPreserve(pn, "box", "y2", &u->box[3]);
+
+  xmlNodePtr n = _xmlGetChildS(pn, "box", NULL, NULL);
+  if(n){
+    u->box_active = 1;
+    xmlFree(n);
   }
+  
+  // objective map settings
+  for(i=0;i<p->objectives;i++){
+    sv_obj_t *o = p->objective_list[i].o;
+    xmlNodePtr on = _xmlGetChildI(pn, "objective", "position", i);
+    if(!on){
+      _sv_first_load_warning(&warn);
+      fprintf(stderr,"No save data found for panel %d objective \"%s\".\n",p->number, o->name);
+    }else{
+      // check name, type
+      _xmlCheckPropS(on,"name",o->name, "Objectve position %d name mismatch in save file.",i,&warn);
+      _xmlCheckPropS(on,"type",o->output_types, "Objectve position %d type mismatch in save file.",i,&warn);
+      
+      // right now Y is the only type; the below is Y-specific
+      // load maptype, values
+      _xmlGetChildPropFPreserve(on, "y-map", "low-bracket", &u->scale_vals[0][i]);
+      _xmlGetChildPropFPreserve(on, "y-map", "alpha", &u->scale_vals[1][i]);
+      _xmlGetChildPropFPreserve(on, "y-map", "high-bracket", &u->scale_vals[2][i]);
+      _xmlGetChildMap(on, "y-map", "color", _sv_mapping_map(), &u->mappings[i],
+		     "Panel %d objective unknown mapping setting", p->number, &warn);
 
-  if(number>_sv_panels || !_sv_panel_list[number]){
-    fprintf(stderr,"sv_panel_background: Panel number %d does not exist\n",number);
-    return -EINVAL;
+      xmlFreeNode(on);
+    }
   }
+
+  // x/y dim selection
+  _xmlGetChildPropIPreserve(pn, "axes", "xpos", &u->x_d);
+  _xmlGetChildPropI(pn, "axes", "ypos", &u->y_d);
+
+  return warn;
+}
+
+sv_panel_t *sv_panel_new_2d(int number,
+			    char *name, 
+			    char *objectivelist,
+			    char *dimensionlist,
+			    unsigned flags){
   
-  sv_panel_t *p = _sv_panel_list[number];
-  sv_panel_internal_t *pi = p->private;
+  int i,j;
+  sv_panel_t *p = _sv_panel_new(number,name,objectivelist,dimensionlist,flags);
+  if(!p)return NULL;
 
-  if(denom == 0){
-    fprintf(stderr,"sv_panel_oversample: A denominator of zero is invalid\n");
-    return -EINVAL;
+  _sv_panel2d_t *p2 = calloc(1, sizeof(*p2));
+  int fout_offsets[_sv_functions];
+  
+  p->subtype = 
+    calloc(1, sizeof(*p->subtype)); /* the union is alloced not
+				       embedded as its internal
+				       structure must be hidden */  
+  p->subtype->p2 = p2;
+  p->type = SV_PANEL_2D;
+  p->private->bg_type = SV_BG_CHECKS;
+
+  // verify all the objectives have scales
+  for(i=0;i<p->objectives;i++){
+    if(!p->objective_list[i].o->scale){
+      fprintf(stderr,"All objectives in a 2d panel must have a scale\n");
+      errno = -EINVAL;
+      return NULL;
+    }
   }
 
-  pi->def_oversample_n = pi->oversample_n = numer;
-  pi->def_oversample_d = pi->oversample_d = denom;
-  recompute_if_running(p);
-  return 0;
+  p->private->realize = _sv_panel2d_realize;
+  p->private->map_action = _sv_panel2d_map_redraw;
+  p->private->legend_action = _sv_panel2d_legend_redraw;
+  p->private->compute_action = _sv_panel2d_compute;
+  p->private->request_compute = _sv_panel2d_mark_recompute;
+  p->private->crosshair_action = _sv_panel2d_crosshairs_callback;
+  p->private->print_action = _sv_panel2d_print;
+  p->private->undo_log = _sv_panel2d_undo_log;
+  p->private->undo_restore = _sv_panel2d_undo_restore;
+  p->private->save_action = _sv_panel2d_save;
+  p->private->load_action = _sv_panel2d_load;
+
+  /* set up helper data structures for rendering */
+
+  /* determine which functions are actually needed; if it's referenced
+     by an objective, it's used.  Precache them in dense form. */
+  {
+    int fn = _sv_functions;
+    int used[fn],count=0,offcount=0;
+    memset(used,0,sizeof(used));
+    memset(fout_offsets,-1,sizeof(fout_offsets));
+    
+    for(i=0;i<p->objectives;i++){
+      sv_obj_t *o = p->objective_list[i].o;
+      for(j=0;j<o->outputs;j++)
+	used[o->function_map[j]]=1;
+    }
+
+    for(i=0;i<fn;i++)
+      if(used[i]){
+	sv_func_t *f = _sv_function_list[i];
+	fout_offsets[i] = offcount;
+	offcount += f->outputs;
+	count++;
+      }
+
+    p2->used_functions = count;
+    p2->used_function_list = calloc(count, sizeof(*p2->used_function_list));
+
+    for(count=0,i=0;i<fn;i++)
+     if(used[i]){
+        p2->used_function_list[count]=_sv_function_list[i];
+	count++;
+      }
+  }
+
+  /* set up computation/render helpers for Y planes */
+
+  /* set up Y object mapping index */
+  {
+    int yobj_count = 0;
+
+    for(i=0;i<p->objectives;i++){
+      sv_obj_t *o = p->objective_list[i].o;
+      if(o->private->y_func) yobj_count++;
+    }
+
+    p2->y_obj_num = yobj_count;
+    p2->y_obj_list = calloc(yobj_count, sizeof(*p2->y_obj_list));
+    p2->y_obj_to_panel = calloc(yobj_count, sizeof(*p2->y_obj_to_panel));
+    p2->y_obj_from_panel = calloc(p->objectives, sizeof(*p2->y_obj_from_panel));
+    
+    yobj_count=0;
+    for(i=0;i<p->objectives;i++){
+      sv_obj_t *o = p->objective_list[i].o;
+      if(o->private->y_func){
+	p2->y_obj_list[yobj_count] = o;
+	p2->y_obj_to_panel[yobj_count] = i;
+	p2->y_obj_from_panel[i] = yobj_count;
+	yobj_count++;
+      }else
+	p2->y_obj_from_panel[i] = -1;
+      
+    }
+  }
+  
+  /* set up function Y output value demultiplex helper */
+  {
+    p2->y_fout_offset = calloc(p2->y_obj_num, sizeof(*p2->y_fout_offset));
+    for(i=0;i<p2->y_obj_num;i++){
+      sv_obj_t *o = p2->y_obj_list[i];
+      int funcnum = o->private->y_func->number;
+      p2->y_fout_offset[i] = fout_offsets[funcnum] + o->private->y_fout;
+    }
+  }
+
+  p2->y_map = calloc(p2->y_obj_num,sizeof(*p2->y_map));
+  p2->y_planetodo = calloc(p2->y_obj_num,sizeof(*p2->y_planetodo));
+  p2->y_planes = calloc(p2->y_obj_num,sizeof(*p2->y_planes));
+
+  return p;
 }
 
+
+void _sv_panel_realize(sv_panel_t *p){
+  if(p && !p->private->realized){
+    p->private->realize(p);
+
+    g_signal_connect (G_OBJECT (p->private->toplevel), "key-press-event",
+		      G_CALLBACK (panel_keypress), p);
+    gtk_window_set_title (GTK_WINDOW (p->private->toplevel), p->name);
+
+    p->private->realized=1;
+
+    // generic things that happen in all panel realizations...
+
+    // text black or white in the plot?
+    decide_text_inv(p);
+    p->private->popmenu = _gtk_menu_new_twocol(p->private->toplevel, menu, p);
+    _sv_panel_update_menus(p);
+    
+  }
+}
+
 int sv_panel_background(int number,
 			enum sv_background bg){
 
@@ -800,22 +2328,15 @@
   return set_background(p,bg);
 }
 
-sv_panel_t * _sv_panel_new(int number,
-			   char *name, 
-			   char *objectivelist,
-			   char *dimensionlist,
-			   unsigned flags){
+sv_panel_t *sv_panel_new(char *decl, 
+			 char *objlist,
+			 char *dimlist){
 
   sv_panel_t *p;
-  _sv_tokenlist *dim_tokens;
-  _sv_tokenlist *obj_tokens;
+  sv_tokenlist *dim_tokens;
+  sv_tokenlist *obj_tokens;
   int i;
 
-  if(number<0){
-    fprintf(stderr,"Panel number must be >= 0\n");
-    errno = -EINVAL;
-    return NULL;
-  }
 
   if(number<_sv_panels){
     if(_sv_panel_list[number]!=NULL){

Added: trunk/sushivision/panel.h
===================================================================
--- trunk/sushivision/panel.h	                        (rev 0)
+++ trunk/sushivision/panel.h	2007-10-17 03:18:34 UTC (rev 14005)
@@ -0,0 +1,66 @@
+/*
+ *
+ *     sushivision copyright (C) 2006-2007 Monty <monty at xiph.org>
+ *
+ *  sushivision is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *   
+ *  sushivision is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *   
+ *  You should have received a copy of the GNU General Public License
+ *  along with sushivision; see the file COPYING.  If not, write to the
+ *  Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * 
+ */
+
+struct sv_panel {
+  pthread_rwlock_t panel_m;
+  pthread_mutex_t  status_m;
+
+  int number;
+  char *name;
+  char *legend;
+
+  int dimensions;
+  int *dimension_list;
+
+  int objectives;
+  int *objective_list;
+
+  // axis 0 == X 
+  // axis 1 == Y 
+  // axis 2 == Z
+  // >2 == auxiliary axes
+  char            **axis_names;
+  int              *axis_dims;
+  int               axes;
+
+  // mem and data locked by status_m
+  int               recompute_pending;
+  int               dims; 
+  int               w; 
+  int               h;
+  sv_dim_data_t    *dim_data;
+
+  // locked by panel_m
+  sv_plane_bg_t *bg; // composite background plane
+  int planes;
+  sv_plane_t **plane_list;
+  int next_plane; 
+
+  // UI objects
+  GtkWidget *obj_table;
+  GtkWidget *dim_table;
+
+  //sv_dimwidget_t *dw;
+  //sv_objwidget_t *ow;
+
+  double selectbox[4]; // x1,y1,x2,y2
+
+} sv_panel_t;

Modified: trunk/sushivision/sushivision.h
===================================================================
--- trunk/sushivision/sushivision.h	2007-10-17 02:35:48 UTC (rev 14004)
+++ trunk/sushivision/sushivision.h	2007-10-17 03:18:34 UTC (rev 14005)
@@ -22,58 +22,29 @@
 #ifndef _SUSHIVISION_
 #define _SUSHIVISION_
 
-typedef struct sv_scale sv_scale_t;
 typedef struct sv_panel sv_panel_t;
 typedef struct sv_dim   sv_dim_t;
-typedef struct _sv_obj   sv_obj_t;
-typedef struct _sv_scale_internal sv_scale_internal_t;
-typedef struct _sv_dim_internal sv_dim_internal_t;
-typedef union  _sv_dim_subtype sv_dim_subtype_t;
-typedef struct _sv_panel_internal sv_panel_internal_t;
-typedef union  _sv_panel_subtype sv_panel_subtype_t;
+typedef struct sv_obj   sv_obj_t;
 
-int sv_submain(int argc, char *argv[]);
-int sv_atexit(void);
-int sv_save(char *filename);
-int sv_load(char *filename);
-
 /* toplevel ******************************************************/
+
 extern int sv_init(void);
 extern int sv_go(void);
 extern int sv_join(void);
+extern int sv_save(char *filename);
+extern int sv_load(char *filename);
 
-/* scales ********************************************************/
-
-struct sv_scale{
-  int vals;
-  double *val_list;
-  char **label_list; 
-  char *legend;
-
-  sv_scale_internal_t *private;
-  unsigned flags;
-};
-
-sv_scale_t        *sv_scale_new (char *name,
-				 char *values);
-
-sv_scale_t       *sv_scale_copy (sv_scale_t *s);
-
-void              sv_scale_free (sv_scale_t *s);
-
 /* dimensions ****************************************************/
 
 sv_dim_t            *sv_dim_new (char *decl);
 
 sv_dim_t                *sv_dim (char *name);
 
-int            sv_dim_set_scale (sv_scale_t *scale);
+int                sv_dim_scale (char *format);
 
-int           sv_dim_make_scale (char *format);
+int                sv_dim_value (double val);
 
-int            sv_dim_set_value (double val);
-
-int          sv_dim_set_bracket (double lo,
+int              sv_dim_bracket (double lo,
 				 double hi);
 
 int       sv_dim_callback_value (int (*callback)(sv_dim_t *, void*),
@@ -82,96 +53,31 @@
 /* objectives ****************************************************/
 int                  sv_obj_new (char *decl,
 				 void (*function)(double *,double *),
-				 char *input_map,
-				 char *output_map);
+				 char *inputs,
+				 char *outputs);
 
-int                      sv_obj (char *name);
+sv_obj_t                *sv_obj (char *name);
 
-int            sv_obj_set_scale (sv_scale_t *scale);
+int                sv_obj_scale (char *format);
 
-int           sv_obj_make_scale (char *format);
-
 /* panels ********************************************************/
 
-enum sv_panel_type     { SV_PANEL_1D, 
-			 SV_PANEL_2D, 
-			 SV_PANEL_XY };
-enum sv_background     { SV_BG_WHITE, 
-			 SV_BG_BLACK, 
-			 SV_BG_CHECKS };
+sv_panel_t        *sv_panel_new (char *decl,
+				 char *obj,
+				 char *dim);
 
-#define SV_PANEL_FLIP 0x4 
+sv_panel_t            *sv_panel (char *name);
 
-typedef struct {
-  sv_dim_t *d;
-  sv_panel_t *p;
-} sv_dim_list_t;
 
-typedef struct {
-  sv_obj_t *o;
-  sv_panel_t *p;
-} sv_obj_list_t;
- 
-struct sv_panel {
-  int number;
-  char *name;
-  enum sv_panel_type type;
-
-  int dimensions;
-  sv_dim_list_t *dimension_list;
-  int objectives;
-  sv_obj_list_t *objective_list;
-
-  unsigned flags;
-
-  sv_panel_subtype_t *subtype;
-  sv_panel_internal_t *private;
-};
-
-sv_panel_t     *sv_panel_new_1d (int number,
-				 char *name,
-				 sv_scale_t *y_scale,
-				 char *objectives,
-				 char *dimensions,	
-				 unsigned flags);
-
-sv_panel_t     *sv_panel_new_xy (int number,
-				 char *name,
-				 sv_scale_t *x_scale,
-				 sv_scale_t *y_scale,
-				 char *objectives,
-				 char *dimensions,	
-				 unsigned flags);
-
-sv_panel_t     *sv_panel_new_2d (int number,
-				 char *name, 
-				 char *objectives,
-				 char *dimensions,
-				 unsigned flags);
-
-int sv_panel_callback_recompute (sv_panel_t *p,
-				 int (*callback)(sv_panel_t *p,void *data),
+int sv_panel_callback_recompute (int (*callback)(sv_panel_t *p,void *data),
 				 void *data);
 
-int       sv_panel_set_resample (sv_panel_t *p,
-				 int numerator,
+int           sv_panel_resample (int numerator,
 				 int denominator);
 
-int     sv_panel_set_background (sv_panel_t *p,
-				 enum sv_background bg);
+int         sv_panel_background (char *background);
 
-int           sv_panel_set_axis (sv_panel_t *p,
-				 char axis,
+int               sv_panel_axis (char *axis,
 				 sv_dim_t *d);
 
-int       sv_panel_get_resample (sv_panel_t *p,
-				 int *numerator,
-				 int *denominator);
-
-int     sv_panel_get_background (sv_panel_t *p,
-				 enum sv_background *bg);
-
-sv_dim_t     *sv_panel_get_axis (sv_panel_t *p, char axis);
-				    
-
 #endif



More information about the commits mailing list