1 /*-- parcoords.c --*/
2 /*
3  * ggobi
4  * Copyright (C) AT&T, Duncan Temple Lang, Dianne Cook 1999-2005
5  *
6  * ggobi is free software; you may use, redistribute, and/or modify it
7  * under the terms of the Eclipse Public License, which is distributed
8  * with the source code and displayed on the ggobi web site,
9  * www.ggobi.org.  For more information, contact the authors:
10  *
11  *   Deborah F. Swayne   dfs@research.att.com
12  *   Di Cook             dicook@iastate.edu
13  *   Duncan Temple Lang  duncan@wald.ucdavis.edu
14  *   Andreas Buja        andreas.buja@wharton.upenn.edu
15 */
16 
17 #include <gtk/gtk.h>
18 #include "vars.h"
19 #include "externs.h"
20 
21 #include "parcoordsClass.h"
22 
23 #define WIDTH   150
24 #define HEIGHT  300
25 
26 
27 /*--------------------------------------------------------------------*/
28 /*                   Options section                                  */
29 /*--------------------------------------------------------------------*/
30 
31 static const gchar* parcoords_ui =
32 "<ui>"
33 "	<menubar>"
34 "		<menu action='Options'>"
35 "			<menuitem action='ShowPoints'/>"
36 "			<menuitem action='ShowLines'/>"
37 "		</menu>"
38 "	</menubar>"
39 "</ui>";
40 
41 
42 void
parcoords_reset_arrangement(displayd * display,gint arrangement,ggobid * gg)43 parcoords_reset_arrangement (displayd *display, gint arrangement, ggobid *gg) {
44   GList *l;
45   GtkWidget *frame, *w;
46   splotd *sp;
47   gint x, y, width, height, depth;
48   gint wframe, hframe;
49   GdkWindow *window;
50 
51   if (display->cpanel.parcoords_arrangement == arrangement)
52     return;
53 
54   for (l=display->splots; l; l=l->next) {
55     w = ((splotd *) l->data)->da;
56     gtk_widget_ref (w);
57     gtk_container_remove (GTK_CONTAINER (gg->parcoords.arrangement_box), w);
58   }
59 
60   frame = gg->parcoords.arrangement_box->parent;
61 
62   // Resize the enclosing window according to the new arrangement.
63   window = gtk_widget_get_parent_window (frame);
64   gdk_window_get_geometry(window, &x, &y, &width, &height, &depth);
65   wframe = (arrangement == ARRANGE_ROW) ? MAX(width,height) :
66     MIN(width,height);
67   hframe = (arrangement == ARRANGE_ROW) ? MIN(width,height) :
68     MAX(width,height);
69   gdk_window_resize(window, wframe, hframe);
70 
71   gtk_widget_destroy (gg->parcoords.arrangement_box);
72 
73   if (arrangement == ARRANGE_ROW)
74     gg->parcoords.arrangement_box = gtk_hbox_new (true, 0);
75   else
76     gg->parcoords.arrangement_box = gtk_vbox_new (true, 0);
77   gtk_container_add (GTK_CONTAINER (frame), gg->parcoords.arrangement_box);
78 
79   display->p1d_orientation = (arrangement == ARRANGE_ROW) ? VERTICAL :
80                                                             HORIZONTAL;
81 
82   for (l=display->splots; l; l=l->next) {
83     sp = (splotd *) l->data;
84     gtk_box_pack_start (GTK_BOX (gg->parcoords.arrangement_box),
85                         sp->da, true, true, 0);
86     gtk_widget_unref (sp->da);  /*-- keep the ref_count appropriate --*/
87   }
88 
89   /*-- position the display toward the lower left of the main window --*/
90   display_set_position (GGOBI_WINDOW_DISPLAY(display), gg);
91 
92   gtk_widget_show_all (gg->parcoords.arrangement_box);
93 
94   display_tailpipe (display, FULL, gg);
95 
96   varpanel_refresh (display, gg);
97 }
98 
99 #define MAXNPCPLOTS 5
100 
101 
102 displayd *
parcoords_new_with_vars(gboolean use_window,gboolean missing_p,gint nvars,gint * vars,GGobiData * d,ggobid * gg)103 parcoords_new_with_vars(gboolean use_window, gboolean missing_p, gint nvars,
104          gint *vars, GGobiData *d, ggobid *gg)
105 {
106 	return(parcoords_new(NULL, use_window, missing_p, nvars, vars, d, gg));
107 }
108 
109 displayd *
parcoords_new(displayd * display,gboolean use_window,gboolean missing_p,gint nvars,gint * vars,GGobiData * d,ggobid * gg)110 parcoords_new (displayd *display, gboolean use_window, gboolean missing_p,
111          gint nvars, gint *vars, GGobiData *d, ggobid *gg)
112 {
113   GtkWidget *vbox, *frame;
114   gint i;
115   splotd *sp;
116   gint nplots;
117   gint arrangement = ARRANGE_ROW;  /*-- default initial orientation --*/
118   gint width, screenwidth;
119   gint height, screenheight;
120 
121   if(!display)
122     display = g_object_new(GGOBI_TYPE_PAR_COORDS_DISPLAY, NULL);
123 
124   display_set_values(display, d, gg);
125 
126   GGOBI_WINDOW_DISPLAY(display)->useWindow = use_window;
127 
128   if (nvars == 0) {
129     nplots = MIN (d->ncols, sessionOptions->info->numParCoordsVars);
130     if(nplots < 0) {
131       nplots = d->ncols;
132     }
133 
134     /* Initialize using the plotted variables in the current display,
135        if appropriate */
136     if (gg->current_display != NULL && gg->current_display != display &&
137         gg->current_display->d == d &&
138         GGOBI_IS_EXTENDED_DISPLAY(gg->current_display))
139     {
140       gint j, k, nplotted_vars;
141       gint *plotted_vars = (gint *) g_malloc(d->ncols * sizeof(gint));
142       displayd *dsp = gg->current_display;
143 
144       nplotted_vars = GGOBI_EXTENDED_DISPLAY_GET_CLASS(dsp)->plotted_vars_get(dsp, plotted_vars, d, gg);
145 
146       nplots = MAX (nplots, nplotted_vars);
147       for (j=0; j<nplotted_vars; j++)
148         vars[j] = plotted_vars[j];
149       j = nplotted_vars;
150       for (k=0; k<d->ncols; k++) {
151         if (!in_vector(k, plotted_vars, nplotted_vars)) {
152           vars[j] = k;
153           j++;
154           if (j == nplots)
155             break;
156         }
157       }
158       g_free (plotted_vars);
159 
160     } else {
161 
162       for (i=0; i<nplots; i++)
163         vars[i] = i;
164     }
165   } else {
166     nplots = nvars;  /* and we assume the argument vars is populated */
167   }
168 
169   parcoords_cpanel_init (&display->cpanel, gg);
170 
171   /*-- make sure the initial plot doesn't spill outside the screen --*/
172   /*-- this should know about the display borders, but it doesn't --*/
173   /*-- at the moment, the arrangement is forced to be ARRANGE_ROW --*/
174   width = WIDTH;
175   height = HEIGHT;
176   if (arrangement == ARRANGE_ROW) {
177     screenwidth = gdk_screen_width();
178     while (nplots * width > screenwidth) {
179       width -= 10;
180     }
181   } else {
182     screenheight = gdk_screen_height();
183     while (nplots * height > screenheight) {
184       height -= 10;
185     }
186   }
187 
188   if(GGOBI_IS_WINDOW_DISPLAY(display) && GGOBI_WINDOW_DISPLAY(display)->useWindow)
189      display_window_init (GGOBI_WINDOW_DISPLAY(display), nplots*width, height, 3, gg);
190 
191 /*
192  * Add the main menu bar
193 */
194   vbox = GTK_WIDGET(display);
195   gtk_container_set_border_width (GTK_CONTAINER (vbox), 1);
196   display->menu_manager = display_menu_manager_create(display);
197 
198   if(GGOBI_IS_WINDOW_DISPLAY(display) && GGOBI_WINDOW_DISPLAY(display)->window) {
199     gtk_container_add (GTK_CONTAINER (GGOBI_WINDOW_DISPLAY(display)->window), vbox);
200     //gg->parcoords.accel_group = gtk_accel_group_new ();
201     display->menubar = create_menu_bar(display->menu_manager, parcoords_ui,
202 			     GGOBI_WINDOW_DISPLAY(display)->window);
203 
204     /*-- add a tooltip to the file menu --*/
205     /* - tooltips are generally not done for toplevel menus
206 	w = gtk_item_factory_get_widget (factory, "<main>/File");
207     gtk_tooltips_set_tip (GTK_TOOLTIPS (gg->tips),
208 			  gtk_menu_get_attach_widget (GTK_MENU(w)),
209 			  "File menu for this display", NULL);
210 	*/
211     /*
212      * After creating the menubar, and populating the file menu,
213      * add the Options and Link menus another way
214      */
215     /*parcoords_display_menus_make (display, gg->parcoords.accel_group,
216                                 G_CALLBACK(display_options_cb),
217 				  display->menubar, gg);*/
218     gtk_box_pack_start (GTK_BOX (vbox), display->menubar, false, true, 0);
219   }
220 
221 
222 /*
223  * splots in a box in a frame -- either a vbox or an hbox.
224 */
225   frame = gtk_frame_new (NULL);
226   //gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
227   gtk_container_set_border_width (GTK_CONTAINER (frame), 5);
228   gtk_box_pack_start (GTK_BOX (vbox), frame, true, true, 1);
229 
230 /*
231  * this is the box that would have to change from horizontal to vertical
232  * when the plot arrangement changes
233 */
234   gg->parcoords.arrangement_box = gtk_hbox_new (true, 0);
235   gtk_container_add (GTK_CONTAINER (frame), gg->parcoords.arrangement_box);
236 
237   display->splots = NULL;
238 
239   /*-- --*/
240 
241   for (i = 0; i < nplots; i++) {
242     sp = ggobi_parcoords_splot_new(display, gg);
243     sp->p1dvar = vars[i];
244 
245 /*
246     if (sub_plots == NULL) {
247       sp = splot_new (display, width, height, gg);
248       sp->p1dvar = i;
249     } else
250        sp = sub_plots[i];
251 */
252 
253     display->splots = g_list_append (display->splots, (gpointer) sp);
254     gtk_box_pack_start (GTK_BOX (gg->parcoords.arrangement_box),
255       sp->da, true, true, 0);
256   }
257 
258   if(GGOBI_WINDOW_DISPLAY(display)->window)
259      gtk_widget_show_all (GGOBI_WINDOW_DISPLAY(display)->window);
260 
261   return display;
262 }
263 
264 
265 
266 static gboolean
parcoords_var_selected(gint jvar,displayd * display)267 parcoords_var_selected (gint jvar, displayd *display)
268 {
269   gboolean selected = false;
270   splotd *s;
271   GList *l = display->splots;
272   while (l) {
273     s = (splotd *) l->data;
274     if (s->p1dvar == jvar) {
275       selected = true;
276       break;
277     }
278     l = l->next ;
279   }
280   return selected;
281 }
282 
283 
284 gboolean
parcoords_varsel(cpaneld * cpanel,splotd * sp,gint jvar,gint * jvar_prev,ggobid * gg)285 parcoords_varsel (cpaneld *cpanel, splotd *sp, gint jvar, gint *jvar_prev,
286   ggobid *gg)
287 {
288   gboolean status = false;
289 
290   status = parcoords_add_delete_splot(cpanel, sp, jvar, jvar_prev, gg, gg->current_display);
291 
292   return(status);
293 }
294 
295 gboolean
parcoords_add_delete_splot(cpaneld * cpanel,splotd * sp,gint jvar,gint * jvar_prev,ggobid * gg,displayd * display)296 parcoords_add_delete_splot(cpaneld *cpanel, splotd *sp, gint jvar, gint *jvar_prev, ggobid *gg, displayd *display)
297 {
298   gboolean redraw = true;
299   gint nplots = g_list_length (display->splots);
300   gint k;
301   gint indx = -1, new_indx;
302   GList *l, *ltofree = NULL;
303   splotd *sp_jvar = NULL, *s, *sp_new;
304   GtkWidget *box;
305 
306   /* sp = gg->current_splot
307      jvar = the variable being deleted or added
308   */
309 
310   /*-- Delete a plot  --*/
311   if (parcoords_var_selected (jvar, display)) {
312 
313     if (nplots > 1) {
314 
315       k = 0;
316       l = display->splots;
317       while (l) {
318 	s = (splotd *) l->data;
319 	if (s->p1dvar == jvar) {
320           ltofree = l;
321 	  sp_jvar = s;
322 	  indx = k;
323 	  break;
324 	}
325         l = l->next;
326         k++;
327       }
328 
329       if (indx == -1 || ltofree == NULL || sp_jvar == NULL)
330         return false; // No plot was found.
331 
332       /*-- Delete the plot from the list without freeing it. --*/
333       display->splots = g_list_remove_link (display->splots, ltofree);
334       //display->splots = g_list_remove_link (display->splots,
335       //  (gpointer) sp_jvar);
336       nplots--;
337 
338       /*
339        * If the plot being removed is the current plot, reset
340        * gg->current_splot.
341       */
342       if (sp_jvar == gg->current_splot) {
343         sp_event_handlers_toggle (sp_jvar, off, cpanel->pmode, cpanel->imode);
344 
345         new_indx = (indx == 0) ? 0 : MIN (nplots-1, indx);
346         s = (splotd *) g_list_nth_data (display->splots, new_indx);
347         /* just for insurance, to handle the unforeseen */
348         if (s == NULL) {
349           s = (splotd *) g_list_nth_data (display->splots, 0);
350         }
351         display->current_splot = gg->current_splot = s;
352 
353         /* If we're brushing, is this ok? */
354         sp_event_handlers_toggle (s, on, cpanel->pmode, cpanel->imode);
355       }
356       // Try to get all the event handlers toggled before the plot is freed.
357       gdk_flush();
358       splot_free (sp_jvar, display, gg);
359       g_list_free(ltofree);  // Free the list element that pointed to sp_jvar.
360     }
361 
362   } else /* Append a new variable */ {
363 
364     sp_new = ggobi_parcoords_splot_new (display, gg);
365     sp_new->p1dvar = jvar;
366     box = (sp->da)->parent;
367     gtk_box_pack_start (GTK_BOX (box), sp_new->da, true, true, 0);
368     display->splots = g_list_append (display->splots,
369       (gpointer) sp_new);
370     gtk_widget_show (sp_new->da);
371 
372     /* I don't think it's possible to initialize brushing until the
373        data has run through the pipeline.  Since I can't cleanly add a
374        plot in brushing mode, I think it's best to switch back to the
375        default mode.  -- dfs
376        */
377     GGOBI(full_viewmode_set)(EXTENDED_DISPLAY_PMODE, DEFAULT_IMODE, gg);
378 
379     redraw = true;
380   }
381 
382   return redraw;
383 }
384 
385 /*--------------------------------------------------------------------*/
386 /*               The whiskers, ie the parallel coordinate lines       */
387 /*--------------------------------------------------------------------*/
388 
389 static void
sp_rewhisker(splotd * sp_prev,splotd * sp,splotd * sp_next,ggobid * gg)390 sp_rewhisker (splotd *sp_prev, splotd *sp, splotd *sp_next, ggobid *gg) {
391   gint i, k, m;
392   displayd *display = (displayd *) sp->displayptr;
393   cpaneld *cpanel = (cpaneld *) &display->cpanel;
394   GGobiData *d = display->d;
395   gboolean draw_whisker;
396 
397   for (k=0; k<d->nrows_in_plot; k++) {
398     i = d->rows_in_plot.els[k];
399     m = 2*i;
400 
401     /*-- if it's the leftmost plot, don't draw the left whisker --*/
402     if (sp_prev == NULL)
403       draw_whisker = false;
404     /*-- .. also if we're not drawing missings, and an endpoint is missing --*/
405     else if (!d->missings_show_p &&
406             (ggobi_data_is_missing(d, i, sp->p1dvar) || ggobi_data_is_missing(d, i, sp_prev->p1dvar)))
407     {
408       draw_whisker = false;
409     }
410     else
411       draw_whisker = true;
412 
413     /* --- left (or top) whisker  --- */
414     if (cpanel->parcoords_arrangement == ARRANGE_ROW) {
415 
416       if (draw_whisker) {
417         sp->whiskers[m].x1 = 0;
418         sp->whiskers[m].y1 = (sp_prev->screen[i].y + sp->screen[i].y) / 2;
419         sp->whiskers[m].x2 = sp->screen[i].x;
420         sp->whiskers[m].y2 = sp->screen[i].y;
421       } else {
422         sp->whiskers[m].x1 = sp->screen[i].x;
423         sp->whiskers[m].y1 = sp->screen[i].y;
424         sp->whiskers[m].x2 = sp->screen[i].x;
425         sp->whiskers[m].y2 = sp->screen[i].y;
426       }
427 
428     } else {  /* ARRANGE_COLUMN */
429       if (draw_whisker) {
430         sp->whiskers[m].x1 = (sp_prev->screen[i].x + sp->screen[i].x) / 2;
431         sp->whiskers[m].y1 = 0;
432         sp->whiskers[m].x2 = sp->screen[i].x;
433         sp->whiskers[m].y2 = sp->screen[i].y;
434       } else {
435         sp->whiskers[m].x1 = sp->screen[i].x;
436         sp->whiskers[m].y1 = sp->screen[i].y;
437         sp->whiskers[m].x2 = sp->screen[i].x;
438         sp->whiskers[m].y2 = sp->screen[i].y;
439       }
440     }
441 
442     m++;
443 
444     /*-- if it's the rightmost plot, don't draw the right whisker --*/
445     if (sp_next == NULL)
446       draw_whisker = false;
447     /*-- .. also if we're not drawing missings, and an endpoint is missing --*/
448     else if (!d->missings_show_p &&
449             (ggobi_data_is_missing(d, i, sp->p1dvar) || ggobi_data_is_missing(d, i, sp_next->p1dvar)))
450     {
451       draw_whisker = false;
452     }
453     else
454       draw_whisker = true;
455 
456     /* --- right (or bottom) whisker --- */
457     if (cpanel->parcoords_arrangement == ARRANGE_ROW) {
458 
459       if (draw_whisker) {
460         sp->whiskers[m].x1 = sp->screen[i].x;
461         sp->whiskers[m].y1 = sp->screen[i].y;
462         sp->whiskers[m].x2 = sp->max.x;
463         sp->whiskers[m].y2 = (sp->screen[i].y + sp_next->screen[i].y) / 2;
464       } else {
465         sp->whiskers[m].x1 = sp->screen[i].x;
466         sp->whiskers[m].y1 = sp->screen[i].y;
467         sp->whiskers[m].x2 = sp->screen[i].x;
468         sp->whiskers[m].y2 = sp->screen[i].y;
469       }
470     } else {  /* ARRANGE_COLUMN */
471       if (draw_whisker) {
472         sp->whiskers[m].x1 = sp->screen[i].x;
473         sp->whiskers[m].y1 = sp->screen[i].y;
474         sp->whiskers[m].x2 = (sp->screen[i].x + sp_next->screen[i].x) / 2;
475         sp->whiskers[m].y2 = sp->max.y;
476       } else {
477         sp->whiskers[m].x1 = sp->screen[i].x;
478         sp->whiskers[m].y1 = sp->screen[i].y;
479         sp->whiskers[m].x2 = sp->screen[i].x;
480         sp->whiskers[m].y2 = sp->screen[i].y;
481       }
482     }
483   }
484 }
485 
486 /*-- set the positions of the whiskers for sp and prev_sp --*/
487 void
sp_whiskers_make(splotd * sp,displayd * display,ggobid * gg)488 sp_whiskers_make (splotd *sp, displayd *display, ggobid *gg) {
489   GList *splist;
490   splotd *splot;
491   splotd *sp_prev = NULL, *sp_prev_prev = NULL, *sp_next = NULL;
492 
493   for (splist = display->splots; splist; splist = splist->next) {
494     splot = (splotd *) splist->data;
495 
496     if (splot == sp) {
497       sp_next = (splist->next == NULL) ? NULL : (splotd *) splist->next->data;
498       sp_prev = (splist->prev == NULL) ? NULL : (splotd *) splist->prev->data;
499       if (sp_prev != NULL)
500         sp_prev_prev = (splist->prev->prev == NULL) ? NULL :
501                         (splotd *) splist->prev->prev->data;
502     }
503   }
504 
505   if (sp_prev != NULL) {
506     sp_rewhisker (sp_prev_prev, sp_prev, sp, gg);
507   }
508 
509   if (sp_next == NULL) {
510     sp_rewhisker (sp_prev, sp, NULL, gg);
511   }
512 }
513