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