1 /* brush.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 <string.h>
18 #include <stdlib.h>
19 #include <math.h>
20 
21 #include <gtk/gtk.h>
22 #include "vars.h"
23 #include "externs.h"
24 
25 /* */
26 static gboolean active_paint_points (splotd *, GGobiData * d, ggobid * gg);
27 static gboolean active_paint_edges (splotd *, GGobiData * e, ggobid * gg);
28 static gboolean build_symbol_vectors (cpaneld *, GGobiData *, ggobid *);
29 /* */
30 
31 /*----------------------------------------------------------------------*/
32 /*             Glyph utility: called in read_data                       */
33 /*----------------------------------------------------------------------*/
34 
35 /* convert a glyph size into a line width */
36 gint
lwidth_from_gsize(gint size)37 lwidth_from_gsize (gint size)
38 {
39   return ((size < 3) ? 0 : (size - 2) * 2);
40 }
41 
42 /* This refers to the stored ltype, apparently */
43 gint
ltype_from_gtype(gint gtype)44 ltype_from_gtype (gint gtype)
45 {
46   gint ltype;
47 
48   /* These ltypes are the three EDGETYPES; should be an enum */
49 
50   if (gtype == FC || gtype == FR)
51     ltype = SOLID;
52   else if (gtype == OC || gtype == OR)
53     ltype = WIDE_DASH;
54   else
55     ltype = NARROW_DASH;
56 
57   return ltype;
58 }
59 
60 /* sets dashes and returns a gtk line attribute */
61 gint
set_lattribute_from_ltype(gint ltype,ggobid * gg)62 set_lattribute_from_ltype (gint ltype, ggobid * gg)
63 {
64   gint8 dash_list[2];
65   gint lattr = GDK_LINE_SOLID;
66 
67   switch (ltype) {    /* one of the three EDGETYPES; should be an enum */
68   case SOLID:
69     lattr = GDK_LINE_SOLID;
70     break;
71   case WIDE_DASH:
72     lattr = GDK_LINE_ON_OFF_DASH;
73     dash_list[0] = 8;
74     dash_list[1] = 2;
75     gdk_gc_set_dashes (gg->plot_GC, 0, dash_list, 2);
76     break;
77   case NARROW_DASH:
78     lattr = GDK_LINE_ON_OFF_DASH;
79     dash_list[0] = 4;
80     dash_list[1] = 2;
81     gdk_gc_set_dashes (gg->plot_GC, 0, dash_list, 2);
82     break;
83   }
84   return lattr;
85 }
86 
87 void
find_glyph_type_and_size(gint gid,glyphd * glyph)88 find_glyph_type_and_size (gint gid, glyphd * glyph)
89 {
90 /*  gid ranges from 0:42, type from 0 to 6, size from 0 to 7 */
91   if (gid == 0) {
92     glyph->type = glyph->size = 0;  /*-- single-pixel point --*/
93   }
94   else {
95     glyph->type = ((gid - 1) / (gint) (NGLYPHSIZES)) + 1;
96     glyph->size = ((gid - 1) % (gint) (NGLYPHSIZES));
97   }
98 }
99 
100 /*----------------------------------------------------------------------*/
101 
102 /*-- called by brush_motion, brush_mode_cb, and in the api --*/
103 gboolean
brush_once(gboolean force,splotd * sp,ggobid * gg)104 brush_once (gboolean force, splotd * sp, ggobid * gg)
105 {
106 /*
107  * Determine which bins the brush is currently sitting in.
108  * bin0 is the bin which contains of the upper left corner of the
109  * brush; bin1 is the one containing of the lower right corner.
110 */
111   displayd *display = sp->displayptr;
112   GGobiData *d = display->d;
113   GGobiData *e = display->e;
114 
115   brush_coords *brush_pos = &sp->brush_pos;
116 
117   icoords *bin0 = &d->brush.bin0;
118   icoords *bin1 = &d->brush.bin1;
119 
120   gboolean changed = false;
121   cpaneld *cpanel = &display->cpanel;
122 
123   if (force) {  /*-- force the bin to be the entire screen --*/
124     bin0->x = 0;
125     bin0->y = 0;
126     bin1->x = d->brush.nbins - 1;
127     bin1->y = d->brush.nbins - 1;
128 
129   }
130   else {
131 
132     gint ulx = MIN (brush_pos->x1, brush_pos->x2);
133     gint uly = MIN (brush_pos->y1, brush_pos->y2);
134     gint lrx = MAX (brush_pos->x1, brush_pos->x2);
135     gint lry = MAX (brush_pos->y1, brush_pos->y2);
136 
137     if (!point_in_which_bin (ulx, uly, &bin0->x, &bin0->y, d, sp)) {
138       bin0->x = MAX (bin0->x, 0);
139       bin0->x = MIN (bin0->x, d->brush.nbins - 1);
140       bin0->y = MAX (bin0->y, 0);
141       bin0->y = MIN (bin0->y, d->brush.nbins - 1);
142     }
143     if (!point_in_which_bin (lrx, lry, &bin1->x, &bin1->y, d, sp)) {
144       bin1->x = MAX (bin1->x, 0);
145       bin1->x = MIN (bin1->x, d->brush.nbins - 1);
146       bin1->y = MAX (bin1->y, 0);
147       bin1->y = MIN (bin1->y, d->brush.nbins - 1);
148     }
149   }
150 
151 /*
152  * Now paint.
153 */
154   if (!cpanel->br.edge_targets /*cpanel->br.point_targets*/) {
155     changed = active_paint_points (sp, d, gg);
156   }
157 
158   if (cpanel->br.edge_targets) {
159     if (e != NULL)
160       changed = active_paint_edges (sp, e, gg);
161   }
162 
163   return (changed);
164 }
165 
166 void
brush_prev_vectors_update(GGobiData * d,ggobid * gg)167 brush_prev_vectors_update (GGobiData * d, ggobid * gg)
168 {
169   gint m, i;
170 
171   g_assert (d->color.nels == d->nrows);
172 
173   if (d->color_prev.nels < d->nrows) {
174     vectors_realloc (&d->color_prev, d->nrows);
175     vectorb_realloc (&d->hidden_prev, d->nrows);
176     vectorg_realloc (&d->glyph_prev, d->nrows);
177   }
178 
179   for (m = 0; m < d->nrows_in_plot; m++) {
180     i = d->rows_in_plot.els[m];
181     d->color_prev.els[i] = d->color.els[i];
182     d->hidden_prev.els[i] = d->hidden.els[i];
183     d->glyph_prev.els[i].size = d->glyph.els[i].size;
184     d->glyph_prev.els[i].type = d->glyph.els[i].type;
185   }
186 }
187 
188 void
brush_undo(splotd * sp,GGobiData * d,ggobid * gg)189 brush_undo (splotd * sp, GGobiData * d, ggobid * gg)
190 {
191   gint m, i;
192   if (!d)
193     return;
194 
195   g_assert (d->color.nels == d->nrows);
196 
197   for (m = 0; m < d->nrows_in_plot; m++) {
198     i = d->rows_in_plot.els[m];
199     d->color.els[i] = d->color_now.els[i] = d->color_prev.els[i];
200     d->hidden.els[i] = d->hidden_now.els[i] = d->hidden_prev.els[i];
201     d->glyph.els[i].type = d->glyph_now.els[i].type =
202       d->glyph_prev.els[i].type;
203     d->glyph.els[i].size = d->glyph_now.els[i].size =
204       d->glyph_prev.els[i].size;
205   }
206 }
207 
208 void
reinit_transient_brushing(displayd * dsp,ggobid * gg)209 reinit_transient_brushing (displayd * dsp, ggobid * gg)
210 {
211 /*
212  * If a new variable is selected or a variable is transformed
213  * during transient brushing, we may want to restore all points to
214  * the permanent value.  After calling this routine, re-execute
215  * brush_once() to brush the points that are now underneath the brush.
216  * For now, don't make the same change for persistent brushing.
217 */
218   gint i, m, k;
219   GGobiData *d = dsp->d;
220   GGobiData *e = dsp->e;
221   cpaneld *cpanel = &dsp->cpanel;
222   gboolean point_painting_p = (cpanel->br.point_targets != br_off);
223   gboolean edge_painting_p = (cpanel->br.edge_targets != br_off);
224 
225   g_assert (d->color.nels == d->nrows);
226 
227   if (point_painting_p) {
228     for (m = 0; m < d->nrows_in_plot; m++) {
229       i = d->rows_in_plot.els[m];
230       d->color_now.els[i] = d->color.els[i];
231       d->glyph_now.els[i].type = d->glyph.els[i].type;
232       d->glyph_now.els[i].size = d->glyph.els[i].size;
233       d->hidden_now.els[i] = d->hidden.els[i];
234     }
235   }
236   if (edge_painting_p && e != NULL) {
237     for (k = 0; k < e->edge.n; k++) {
238       e->color_now.els[k] = e->color.els[k];
239       e->glyph_now.els[k].type = e->glyph.els[k].type;
240       e->glyph_now.els[k].size = e->glyph.els[k].size;
241       e->hidden_now.els[k] = e->hidden.els[k];
242     }
243   }
244 }
245 
246 void
brush_set_pos(gint x,gint y,splotd * sp)247 brush_set_pos (gint x, gint y, splotd * sp)
248 {
249   brush_coords *brush = &sp->brush_pos;
250   brush_coords *obrush = &sp->brush_pos_o;
251   gint xdist = brush->x2 - brush->x1;
252   gint ydist = brush->y2 - brush->y1;
253 
254   /*-- copy the current coordinates to the backup brush structure --*/
255   obrush->x1 = brush->x1;
256   obrush->y1 = brush->y1;
257   obrush->x2 = brush->x2;
258   obrush->y2 = brush->y2;
259 
260   /*
261    * (x2,y2) is the corner that's moving.
262    */
263   brush->x1 = x - xdist;
264   brush->x2 = x;
265   brush->y1 = y - ydist;
266   brush->y2 = y;
267 }
268 
269 static gboolean
binning_permitted(displayd * display,ggobid * gg)270 binning_permitted (displayd * display, ggobid * gg)
271 {
272   GGobiData *e = display->e;
273   gboolean permitted = true;
274 
275   if (gg->linkby_cv)
276     return (false);
277 
278   if (GGOBI_IS_EXTENDED_DISPLAY (display)) {
279     /* If there is a function to determine this, call it. Otherwise just
280        get the value of binning_ok in the class. */
281     GGobiExtendedDisplayClass *klass;
282     klass = GGOBI_EXTENDED_DISPLAY_GET_CLASS (display);
283     if (klass->binningPermitted) {
284       return (klass->binningPermitted (display));
285     }
286     return (klass->binning_ok);
287   }
288 
289 
290   /*-- if we're drawing edges --*/
291   if (e != NULL && e->edge.n > 0) {
292     if (display->options.edges_undirected_show_p ||
293         display->options.edges_directed_show_p ||
294         display->options.whiskers_show_p) {
295       permitted = false;
296     }
297   }
298 
299   return permitted;
300 }
301 
302 gboolean
brush_once_and_redraw(gboolean binningp,splotd * sp,displayd * display,ggobid * gg)303 brush_once_and_redraw (gboolean binningp, splotd * sp, displayd * display,
304                        ggobid * gg)
305 {
306   cpaneld *cpanel = &display->cpanel;
307   gboolean changed = false;
308 
309   if (cpanel->br.brush_on_p) {
310     changed = brush_once (!binningp, sp, gg);
311 
312     if (binningp && binning_permitted (display, gg)) {
313       if (changed) {
314         splot_redraw (sp, BINNED, gg);
315         if (cpanel->br.updateAlways_p) {
316           displays_plot (sp, FULL, gg);
317         }
318 
319       }
320       else {    /*-- just redraw the brush --*/
321         splot_redraw (sp, QUICK, gg);
322       }
323 
324     }
325     else {                      /* no binning */
326       splot_redraw (sp, FULL, gg);
327       if (cpanel->br.updateAlways_p)
328         displays_plot (sp, FULL, gg);
329     }
330 
331   }
332   else {    /*-- we're not brushing, and we just need to redraw the brush --*/
333     splot_redraw (sp, QUICK, gg);
334   }
335 
336   return (changed);
337 }
338 
339 gboolean
brush_motion(icoords * mouse,gboolean button1_p,gboolean button2_p,cpaneld * cpanel,splotd * sp,ggobid * gg)340 brush_motion (icoords * mouse, gboolean button1_p, gboolean button2_p,
341               cpaneld * cpanel, splotd * sp, ggobid * gg)
342 {
343   displayd *display = sp->displayptr;
344   brush_coords *brush_pos = &sp->brush_pos;
345 
346   if (button1_p) {
347     brush_set_pos (mouse->x, mouse->y, sp);
348   }
349   else if (button2_p) {
350     brush_pos->x2 = mouse->x;
351     brush_pos->y2 = mouse->y;
352   }
353 
354   return (brush_once_and_redraw (true, sp, display, gg)); /* binning permitted */
355 }
356 
357 
358 static gboolean
under_brush(gint k,splotd * sp)359 under_brush (gint k, splotd * sp)
360 /*
361  * Determine whether point k is under the brush.
362 */
363 {
364   brush_coords *brush_pos = &sp->brush_pos;
365   gint pt;
366   gint x1 = MIN (brush_pos->x1, brush_pos->x2);
367   gint x2 = MAX (brush_pos->x1, brush_pos->x2);
368   gint y1 = MIN (brush_pos->y1, brush_pos->y2);
369   gint y2 = MAX (brush_pos->y1, brush_pos->y2);
370 
371   pt = (sp->screen[k].x <= x2 && sp->screen[k].y <= y2 &&
372         sp->screen[k].x >= x1 && sp->screen[k].y >= y1) ? 1 : 0;
373   return (pt);
374 }
375 
376 
377 /*----------------------------------------------------------------------*/
378 /*                      Dealing with the brush                          */
379 /*----------------------------------------------------------------------*/
380 
381 static void
brush_boundaries_set(cpaneld * cpanel,icoords * obin0,icoords * obin1,icoords * imin,icoords * imax,GGobiData * d,ggobid * gg)382 brush_boundaries_set (cpaneld * cpanel,
383                       icoords * obin0, icoords * obin1,
384                       icoords * imin, icoords * imax, GGobiData * d,
385                       ggobid * gg)
386 {
387   icoords *bin0 = &d->brush.bin0;
388   icoords *bin1 = &d->brush.bin1;
389 
390   if (cpanel->br.mode == BR_TRANSIENT) {
391     imin->x = MIN (bin0->x, obin0->x);
392     imin->y = MIN (bin0->y, obin0->y);
393     imax->x = MAX (bin1->x, obin1->x);
394     imax->y = MAX (bin1->y, obin1->y);
395   }
396   else {
397     imin->x = bin0->x;
398     imin->y = bin0->y;
399     imax->x = bin1->x;
400     imax->y = bin1->y;
401   }
402 }
403 
404 void
brush_draw_label(splotd * sp,GdkDrawable * drawable,GGobiData * d,ggobid * gg)405 brush_draw_label (splotd * sp, GdkDrawable * drawable, GGobiData * d,
406                   ggobid * gg)
407 {
408   PangoRectangle rect;
409   PangoLayout *layout =
410     gtk_widget_create_pango_layout (GTK_WIDGET (sp->da), NULL);
411 
412   if (d->npts_under_brush > 0) {
413     gchar *str = g_strdup_printf ("%d", d->npts_under_brush);
414     layout_text (layout, str, &rect);
415     gdk_draw_layout (drawable, gg->plot_GC,
416                      sp->max.x - rect.width - 5, 5, layout);
417     g_free (str);
418   }
419   g_object_unref (G_OBJECT (layout));
420 }
421 
422 void
brush_draw_brush(splotd * sp,GdkDrawable * drawable,GGobiData * d,ggobid * gg)423 brush_draw_brush (splotd * sp, GdkDrawable * drawable, GGobiData * d,
424                   ggobid * gg)
425 {
426 /*
427  * Use brush_pos to draw the brush.
428  */
429   displayd *display = sp->displayptr;
430   cpaneld *cpanel = &display->cpanel;
431   gboolean point_painting_p = (cpanel->br.point_targets != br_off);
432   gboolean edge_painting_p = (cpanel->br.edge_targets != br_off);
433   gboolean selection_p = !point_painting_p && !edge_painting_p;
434   colorschemed *scheme = gg->activeColorScheme;
435 
436   brush_coords *brush_pos = &sp->brush_pos;
437   gint x1 = MIN (brush_pos->x1, brush_pos->x2);
438   gint x2 = MAX (brush_pos->x1, brush_pos->x2);
439   gint y1 = MIN (brush_pos->y1, brush_pos->y2);
440   gint y2 = MAX (brush_pos->y1, brush_pos->y2);
441 
442   if (cpanel->br.mode == BR_TRANSIENT) {
443     gint8 dash_list[2];
444     gdk_gc_set_line_attributes (gg->plot_GC,
445                                 0, GDK_LINE_ON_OFF_DASH, GDK_CAP_ROUND,
446                                 GDK_JOIN_ROUND);
447     /* just selection: set special dash pattern and draw like points */
448     if (selection_p) {
449       dash_list[0] = 2;
450       dash_list[1] = 2;
451     } else { /* otherwise set to default */
452       dash_list[0] = 4;
453       dash_list[1] = 4;
454     }
455     gdk_gc_set_dashes(gg->plot_GC, 0, dash_list, 2);
456   }
457 
458   if (point_painting_p || selection_p) {
459 
460     /* set brush color for points */
461     if (cpanel->br.point_targets == br_shadow) {
462       gdk_gc_set_foreground (gg->plot_GC, &scheme->rgb_hidden);
463     }
464     else if (cpanel->br.point_targets == br_unshadow) {
465       gdk_gc_set_foreground (gg->plot_GC, &scheme->rgb_accent);
466     }
467     else if ((scheme->rgb[gg->color_id].red != scheme->rgb_bg.red ||
468               scheme->rgb[gg->color_id].blue != scheme->rgb_bg.blue ||
469               scheme->rgb[gg->color_id].green != scheme->rgb_bg.green) &&
470              !selection_p) {
471       gdk_gc_set_foreground (gg->plot_GC, &scheme->rgb[gg->color_id]);
472     }
473     else {
474       /* for selection (at least) */
475       gdk_gc_set_foreground (gg->plot_GC, &scheme->rgb_accent);
476     }
477 
478     gdk_draw_rectangle (drawable, gg->plot_GC, false,
479                         x1, y1, (x2 > x1) ? (x2 - x1) : (x1 - x2),
480                         (y2 > y1) ? (y2 - y1) : (y1 - y2));
481     /* Mark the corner to which the cursor will be attached */
482     gdk_draw_rectangle (drawable, gg->plot_GC, true,
483                         brush_pos->x2 - 1, brush_pos->y2 - 1, 2, 2);
484 
485     /*
486      * highlight brush: but only in the current display
487      */
488     if (cpanel->br.brush_on_p && display == gg->current_display) {
489       gdk_draw_rectangle (drawable, gg->plot_GC, false,
490                           x1 - 1, y1 - 1,
491                           (x2 > x1) ? (x2 - x1 + 2) : (x1 - x2 + 2),
492                           (y2 > y1) ? (y2 - y1 + 2) : (y1 - y2 + 2));
493 
494       /* Mark the corner to which the cursor will be attached */
495       gdk_draw_rectangle (drawable, gg->plot_GC, true,
496                           brush_pos->x2 - 2, brush_pos->y2 - 2, 4, 4);
497     }
498   }
499 
500   if (edge_painting_p) {
501 
502     /* set brush color for edges */
503     if (cpanel->br.edge_targets == br_shadow) {
504       gdk_gc_set_foreground (gg->plot_GC, &scheme->rgb_hidden);
505     }
506     else if (cpanel->br.point_targets == br_unshadow) {
507       gdk_gc_set_foreground (gg->plot_GC, &scheme->rgb_accent);
508     }
509     else if ((scheme->rgb[gg->color_id].red != scheme->rgb_bg.red) ||
510              (scheme->rgb[gg->color_id].blue != scheme->rgb_bg.blue) ||
511              (scheme->rgb[gg->color_id].green != scheme->rgb_bg.green)) {
512       gdk_gc_set_foreground (gg->plot_GC, &scheme->rgb[gg->color_id]);
513     }
514     else {
515       /* I don't remember what this is for ... -- dfs */
516       gdk_gc_set_foreground (gg->plot_GC, &scheme->rgb_accent);
517     }
518 
519     gdk_draw_line (drawable, gg->plot_GC,
520                    x1 + (x2 - x1) / 2, y1, x1 + (x2 - x1) / 2, y2);
521     gdk_draw_line (drawable, gg->plot_GC,
522                    x1, y1 + (y2 - y1) / 2, x2, y1 + (y2 - y1) / 2);
523 
524     if (cpanel->br.brush_on_p) {
525       gdk_draw_line (drawable, gg->plot_GC,
526                      x1 + (x2 - x1) / 2 + 1, y1, x1 + (x2 - x1) / 2 + 1, y2);
527       gdk_draw_line (drawable, gg->plot_GC,
528                      x1, y1 + (y2 - y1) / 2 + 1, x2, y1 + (y2 - y1) / 2 + 1);
529     }
530 
531   }
532 
533   if (cpanel->br.mode == BR_TRANSIENT)
534     gdk_gc_set_line_attributes (gg->plot_GC,
535                                 0, GDK_LINE_SOLID, GDK_CAP_ROUND,
536                                 GDK_JOIN_ROUND);
537 }
538 
539 /*----------------------------------------------------------------------*/
540 /*                      Glyph brushing                                  */
541 /*----------------------------------------------------------------------*/
542 
543 /*
544  * This currently isn't using the same arguments as its
545  * _color_ and _hidden_ cousins because we can't brush on line
546  * type yet.
547 */
548 gboolean
update_glyph_vectors(gint i,gboolean changed,gboolean * hit_by_brush,GGobiData * d,ggobid * gg)549 update_glyph_vectors (gint i, gboolean changed, gboolean * hit_by_brush,
550                       GGobiData * d, ggobid * gg)
551 {
552   cpaneld *cpanel = &gg->current_display->cpanel;
553   gboolean doit = true;
554 
555 /* setting the value of changed */
556   if (!changed) {
557     if (hit_by_brush[i]) {
558       doit = (d->glyph_now.els[i].size != gg->glyph_id.size ||
559               d->glyph_now.els[i].type != gg->glyph_id.type);
560 
561     }
562     else {
563       doit = (d->glyph_now.els[i].size != d->glyph.els[i].size ||
564               d->glyph_now.els[i].type != d->glyph.els[i].type);
565     }
566   }
567 /* */
568 
569   if (doit) {
570     if (hit_by_brush[i]) {
571       switch (cpanel->br.mode) {
572 
573       case BR_PERSISTENT:
574         d->glyph.els[i].size = d->glyph_now.els[i].size = gg->glyph_id.size;
575         d->glyph.els[i].type = d->glyph_now.els[i].type = gg->glyph_id.type;
576         break;
577 
578       case BR_TRANSIENT:
579         d->glyph_now.els[i].size = gg->glyph_id.size;
580         d->glyph_now.els[i].type = gg->glyph_id.type;
581         break;
582       }
583     }
584     else {
585       d->glyph_now.els[i].size = d->glyph.els[i].size;
586       d->glyph_now.els[i].type = d->glyph.els[i].type;
587     }
588   }
589 
590   return (doit);
591 }
592 
593 /*----------------------------------------------------------------------*/
594 /*                      Color brushing                                  */
595 /*----------------------------------------------------------------------*/
596 
597 gboolean
update_color_vectors(gint i,gboolean changed,gboolean * hit_by_brush,GGobiData * d,ggobid * gg)598 update_color_vectors (gint i, gboolean changed, gboolean * hit_by_brush,
599                       GGobiData * d, ggobid * gg)
600 {
601   cpaneld *cpanel = &gg->current_display->cpanel;
602   gboolean doit = true;
603 
604 /* setting the value of doit */
605   if (!changed) {
606     if (hit_by_brush[i]) {
607       /*-- if persistent, compare against color instead of color_now --*/
608       doit = (cpanel->br.mode == BR_TRANSIENT) ?
609         (d->color_now.els[i] != gg->color_id) :
610         (d->color.els[i] != gg->color_id);
611     } else {
612       doit = (d->color_now.els[i] != d->color.els[i]);  /*-- ?? --*/
613     }
614   }
615 /* */
616 
617   /*
618    * If doit is false, it's guaranteed that there will be no change.
619    */
620   if (doit) {
621     if (hit_by_brush[i]) {
622       switch (cpanel->br.mode) {
623       case BR_PERSISTENT:
624         d->color.els[i] = d->color_now.els[i] = gg->color_id;
625         break;
626       case BR_TRANSIENT:
627         d->color_now.els[i] = gg->color_id;
628         break;
629       }
630     }
631     else
632       d->color_now.els[i] = d->color.els[i];
633   }
634 
635   return (doit);
636 }
637 
638 /*----------------------------------------------------------------------*/
639 /*                    Shadow brushing                                   */
640 /*----------------------------------------------------------------------*/
641 
642 gboolean
update_hidden_vectors(gint i,gboolean changed,gboolean * hit_by_brush,GGobiData * d,ggobid * gg)643 update_hidden_vectors (gint i, gboolean changed, gboolean * hit_by_brush,
644                        GGobiData * d, ggobid * gg)
645 {
646   cpaneld *cpanel = &gg->current_display->cpanel;
647   gboolean doit = true;
648 
649   /*
650    * First find out if this will result in a change; this in
651    * order to be able to return that information.
652    */
653   if (!changed) {
654     if (hit_by_brush[i])
655       /* This test covers the case where a transient brush has first
656        * covered all the points of interest, and then persistent
657        * brushing has been activated. -- dfs */
658       doit = d->hidden_now.els[i] != true ||
659         (cpanel->br.mode == BR_PERSISTENT && d->hidden.els[i] != true);
660     else
661       doit = (d->hidden_now.els[i] != d->hidden.els[i]);
662   }
663   /* */
664 
665 /*
666  * If doit is false, it's guaranteed that there will be no change.
667 */
668 
669   if (doit) {
670     if (hit_by_brush[i]) {
671       switch (cpanel->br.mode) {
672       case BR_PERSISTENT:
673         d->hidden.els[i] = d->hidden_now.els[i] = true;
674         break;
675       case BR_TRANSIENT:
676         d->hidden_now.els[i] = true;
677         break;
678       }
679     }
680     else
681       d->hidden_now.els[i] = d->hidden.els[i];
682   }
683 
684   return (doit);
685 }
686 
687 /* This routine named in honor of the Bizarro world of the Superman
688    comics, where everything is backwards. */
689 gboolean
bizarro_update_hidden_vectors(gint i,gboolean changed,gboolean * hit_by_brush,GGobiData * d,ggobid * gg)690 bizarro_update_hidden_vectors (gint i, gboolean changed,
691                                gboolean * hit_by_brush, GGobiData * d,
692                                ggobid * gg)
693 {
694   cpaneld *cpanel = &gg->current_display->cpanel;
695   gboolean doit = true;
696 
697   /*
698    * First find out if this will result in a change; this in
699    * order to be able to return that information.
700    */
701   if (!changed) {
702     if (hit_by_brush[i])
703       /* This test covers the case where a transient brush has first
704        * covered all the points of interest, and then persistent
705        * brushing has been activated. -- dfs */
706       doit = d->hidden_now.els[i] == true ||
707         (cpanel->br.mode == BR_PERSISTENT && d->hidden.els[i] == true);
708     else
709       doit = (d->hidden_now.els[i] != d->hidden.els[i]);
710   }
711   /* */
712 
713 /*
714  * If doit is false, it's guaranteed that there will be no change.
715 */
716 
717   if (doit) {
718     if (hit_by_brush[i]) {
719       switch (cpanel->br.mode) {
720       case BR_PERSISTENT:
721         d->hidden.els[i] = d->hidden_now.els[i] = false;
722         break;
723       case BR_TRANSIENT:
724         d->hidden_now.els[i] = false;
725         break;
726       }
727     }
728     else {
729       switch (cpanel->br.mode) {
730       case BR_PERSISTENT:
731         d->hidden_now.els[i] = d->hidden.els[i];
732         break;
733       case BR_TRANSIENT:
734         d->hidden_now.els[i] = true;
735         break;
736       }
737     }
738   }
739 
740   return (doit);
741 }
742 
743 
744 /*----------------------------------------------------------------------*/
745 /*         Handle all symbols in one loop through a bin                 */
746 /*----------------------------------------------------------------------*/
747 
748 static gboolean
build_symbol_vectors(cpaneld * cpanel,GGobiData * d,ggobid * gg)749 build_symbol_vectors (cpaneld * cpanel, GGobiData * d, ggobid * gg)
750 {
751   gint ih, iv, m, j, k;
752   /*-- these look suspicious -- dfs --*/
753   static icoords obin0 = { BRUSH_NBINS / 2, BRUSH_NBINS / 2 };
754   static icoords obin1 = { BRUSH_NBINS / 2, BRUSH_NBINS / 2 };
755   icoords imin, imax;
756   gboolean changed = false;
757   gint nd = g_slist_length (gg->d);
758   gboolean (*f) (cpaneld *, GGobiData *, ggobid *) = NULL;
759 
760   /* These two are needed for the extended display.
761      Should the method be on the extended splot or the display (as it is now).
762      The choice is somewhat arbitrary since the method for the barchart doesn't
763      seem to do much with the display or plot. It is just used to check if we
764      have a barchart. */
765   splotd *sp = gg->current_splot;
766   displayd *display = (displayd *) sp->displayptr;
767 
768   if (GGOBI_IS_EXTENDED_DISPLAY (display)) {
769     f = GGOBI_EXTENDED_DISPLAY_GET_CLASS (display)->build_symbol_vectors;
770     if (f) {
771       changed = f (cpanel, d, gg);
772     }
773   }
774 
775 
776 /*
777  * None of the following code makes any sense for the barchart...
778 */
779   if (!f) {
780     brush_boundaries_set (cpanel, &obin0, &obin1, &imin, &imax, d, gg);
781 
782     for (ih = imin.x; ih <= imax.x; ih++) {
783       for (iv = imin.y; iv <= imax.y; iv++) {
784         for (m = 0; m < d->brush.binarray[ih][iv].nels; m++) {
785           /*
786            * j is the row number; k is the index of rows_in_plot.els[]
787            */
788           j = d->rows_in_plot.els[k = d->brush.binarray[ih][iv].els[m]];
789 
790           switch (cpanel->br.point_targets) {
791           case br_candg:  /*-- color and glyph --*/
792             changed = update_color_vectors (j, changed,
793                                             d->pts_under_brush.els, d, gg);
794             changed = update_glyph_vectors (j, changed,
795                                             d->pts_under_brush.els, d, gg);
796             break;
797           case br_color:
798             changed = update_color_vectors (j, changed,
799                                             d->pts_under_brush.els, d, gg);
800             break;
801           case br_glyph:  /*-- glyph type and size --*/
802             changed = update_glyph_vectors (j, changed,
803                                             d->pts_under_brush.els, d, gg);
804             break;
805           case br_shadow:  /*-- hidden --*/
806             changed = update_hidden_vectors (j, changed,
807                                              d->pts_under_brush.els, d, gg);
808             break;
809           case br_unshadow:
810             changed = bizarro_update_hidden_vectors (j, changed,
811                                                      d->pts_under_brush.els,
812                                                      d, gg);
813             break;
814           case br_off:
815             ;
816             break;
817           }
818 
819           /*-- link by id --*/
820           if (!gg->linkby_cv && nd > 1)
821             symbol_link_by_id (false, j, d, gg);
822           /*-- --*/
823         }
824       }
825     }
826   }
827 
828   obin0.x = d->brush.bin0.x;
829   obin0.y = d->brush.bin0.y;
830   obin1.x = d->brush.bin1.x;
831   obin1.y = d->brush.bin1.y;
832 
833   return (changed);
834 }
835 
836 /*----------------------------------------------------------------------*/
837 /*                   active_paint_points                                */
838 /*----------------------------------------------------------------------*/
839 
840 /*
841  * Set pts_under_brush[j] to 1 if point j is inside the rectangular brush.
842 */
843 gboolean
active_paint_points(splotd * sp,GGobiData * d,ggobid * gg)844 active_paint_points (splotd * sp, GGobiData * d, ggobid * gg)
845 {
846   gint ih, iv, j, pt;
847   gboolean changed;
848   displayd *display = (displayd *) sp->displayptr;
849   cpaneld *cpanel = &display->cpanel;
850   gint (*f) (splotd * sp, GGobiData *, ggobid *) = NULL;
851   BrushTargetType ttype;
852 
853   g_assert (d->pts_under_brush.nels == d->nrows);
854 
855   if (GGOBI_IS_EXTENDED_SPLOT (sp)) {
856     f = GGOBI_EXTENDED_SPLOT_GET_CLASS (sp)->active_paint_points;
857     if (f) {
858       d->npts_under_brush = f (sp, d, gg);
859     }
860   }
861 
862   if (!f) {
863 
864     /* Zero out pts_under_brush[] before looping */
865     d->npts_under_brush = 0;
866     for (j = 0; j < d->nrows_in_plot; j++)
867       d->pts_under_brush.els[d->rows_in_plot.els[j]] = 0;
868 
869     /*
870      * d->brush.binarray[][] only represents the
871      * cases in rows_in_plot.els[] so there's no need to test for that.
872      */
873 
874     ttype = cpanel->br.point_targets;
875     for (ih = d->brush.bin0.x; ih <= d->brush.bin1.x; ih++) {
876       for (iv = d->brush.bin0.y; iv <= d->brush.bin1.y; iv++) {
877         for (j = 0; j < d->brush.binarray[ih][iv].nels; j++) {
878           pt = d->rows_in_plot.els[d->brush.binarray[ih][iv].els[j]];
879 
880           /*
881            * Ignore hidden cases unless shadow or unshadow brushing.
882            */
883           if (d->hidden_now.els[pt] &&
884               (ttype != br_shadow && ttype != br_unshadow)) {
885             continue;
886           }
887           if (splot_plot_case (pt, d, sp, display, gg)) {
888             if (under_brush (pt, sp)) {
889               d->npts_under_brush++;
890               d->pts_under_brush.els[pt] = 1;
891             }
892           }
893         }
894       }
895     }
896   }
897 
898   changed = false;
899 
900   if (cpanel->br.brush_on_p) {
901     if (gg->linkby_cv) {
902       /*-- link by categorical variable --*/
903       changed = build_symbol_vectors_by_var (cpanel, d, gg);
904     }
905     else {
906       /*-- link by id --*/
907       changed = build_symbol_vectors (cpanel, d, gg);
908     }
909   }
910 
911   return (changed);
912 }
913 
914 /*----------------------------------------------------------------------*/
915 /*                      Edge brushing                                   */
916 /*----------------------------------------------------------------------*/
917 
918 
919 static gboolean
xed_by_brush(gint k,displayd * display,ggobid * gg)920 xed_by_brush (gint k, displayd * display, ggobid * gg)
921 /*
922  * Determine whether edge k intersects the brush
923 */
924 {
925   GGobiData *d = display->d;
926   GGobiData *e = display->e;
927   splotd *sp = gg->current_splot;
928   gboolean intersect;
929   glong x1 = sp->brush_pos.x1;
930   glong y1 = sp->brush_pos.y1;
931   glong x2 = sp->brush_pos.x2;
932   glong y2 = sp->brush_pos.y2;
933   gint a, b;
934 
935   endpointsd *endpoints;
936   endpoints = resolveEdgePoints (e, d);
937 
938   if (!endpoints || !edge_endpoints_get (k, &a, &b, d, endpoints, e))
939     return false;
940 
941   /*-- test for intersection with the vertical edge --*/
942   intersect = lines_intersect (x1 + (x2 - x1) / 2, y1, x1 + (x2 - x1) / 2, y2,
943                                sp->screen[a].x, sp->screen[a].y,
944                                sp->screen[b].x, sp->screen[b].y);
945 /*
946   intersect = isCrossed (x1 + (x2 - x1)/2, y1, x1 + (x2 - x1)/2, y2,
947     (gdouble) sp->screen[a].x, (gdouble) sp->screen[a].y,
948     (gdouble) sp->screen[b].x, (gdouble) sp->screen[b].y);
949 */
950 
951 /*-- I wonder if Lee's method is truly faster --- it requires
952      doubles, which forces me to do a lot of casting.  I should
953      figure out how to test it --*/
954   if (intersect != 1) {
955     intersect =
956       lines_intersect (x1, y1 + (y2 - y1) / 2, x2, y1 + (y2 - y1) / 2,
957                        sp->screen[a].x, sp->screen[a].y, sp->screen[b].x,
958                        sp->screen[b].y);
959 /*
960     intersect = isCrossed (x1, y1 + (y2 - y1)/2, x2, y1 + (y2 - y1)/2,
961       (gdouble) sp->screen[a].x, (gdouble) sp->screen[a].y,
962       (gdouble) sp->screen[b].x, (gdouble) sp->screen[b].y);
963 */
964   }
965 
966   return (intersect == 1);
967 }
968 
969 /*-- link by id --*/
970 static gboolean
build_edge_symbol_vectors(cpaneld * cpanel,GGobiData * e,ggobid * gg)971 build_edge_symbol_vectors (cpaneld * cpanel, GGobiData * e, ggobid * gg)
972 {
973   gint i;
974   gboolean changed = false;
975   gint nd = g_slist_length (gg->d);
976 
977 /*
978  * I'm not doing any checking here to verify that the edges
979  * are displayed.
980 */
981   for (i = 0; i < e->edge.n; i++) {
982 
983     switch (cpanel->br.edge_targets) {
984     case br_candg:  /*-- color and glyph --*/
985       changed = update_color_vectors (i, changed,
986                                       e->edge.xed_by_brush.els, e, gg);
987       changed = update_glyph_vectors (i, changed,
988                                       e->edge.xed_by_brush.els, e, gg);
989       break;
990     case br_color:  /*-- color --*/
991       changed = update_color_vectors (i, changed,
992                                       e->edge.xed_by_brush.els, e, gg);
993       break;
994     case br_glyph:  /*-- line width and line type --*/
995       changed = update_glyph_vectors (i, changed,
996                                       e->edge.xed_by_brush.els, e, gg);
997       break;
998     case br_shadow:  /*-- hidden --*/
999       changed = update_hidden_vectors (i, changed,
1000                                        e->edge.xed_by_brush.els, e, gg);
1001       break;
1002     case br_unshadow:
1003       changed = bizarro_update_hidden_vectors (i, changed,
1004                                                e->edge.xed_by_brush.els, e,
1005                                                gg);
1006       break;
1007     case br_off:
1008       ;
1009       break;
1010     }
1011 
1012     /*-- link by id --*/
1013     if (!gg->linkby_cv && nd > 1)
1014       symbol_link_by_id (false, i, e, gg);
1015     /*-- --*/
1016   }
1017 
1018   return (changed);
1019 }
1020 
1021 static gboolean
active_paint_edges(splotd * sp,GGobiData * e,ggobid * gg)1022 active_paint_edges (splotd * sp, GGobiData * e, ggobid * gg)
1023 {
1024   gint k;
1025   gboolean changed;
1026   displayd *display = sp->displayptr;
1027   cpaneld *cpanel = &display->cpanel;
1028 
1029   g_assert (e->edge.xed_by_brush.nels == e->edge.n);
1030 
1031   /* Zero out xed_by_brush[] before looping */
1032   e->edge.nxed_by_brush = 0;
1033   for (k = 0; k < e->edge.n; k++)
1034     e->edge.xed_by_brush.els[k] = false;
1035 
1036   for (k = 0; k < e->edge.n; k++) {
1037     if (xed_by_brush (k, display, gg)) {
1038       e->edge.nxed_by_brush++;
1039       e->edge.xed_by_brush.els[k] = true;
1040     }
1041   }
1042 
1043   changed = false;
1044   if (cpanel->br.brush_on_p) {
1045     if (gg->linkby_cv) {
1046       /*-- link by categorical variable: presently unavailable --*/
1047       /*changed = build_symbol_vectors_by_var (cpanel, e, gg);*/
1048       /* Simply brush without linking */
1049       changed = build_edge_symbol_vectors (cpanel, e, gg);
1050     }
1051     else {
1052       /*-- link by id  --*/
1053       changed = build_edge_symbol_vectors (cpanel, e, gg);
1054     }
1055   }
1056 
1057   return (changed);
1058 }
1059