Reimplemented CreateBitmapOptionChoice
[grace.git] / src / graph_utils.c
blob1781f5d37fb79453912443ae322a33c787f69be8
1 /*
2 * Grace - GRaphing, Advanced Computation and Exploration of data
3 *
4 * Home page: http://plasma-gate.weizmann.ac.il/Grace/
5 *
6 * Copyright (c) 1991-1995 Paul J Turner, Portland, OR
7 * Copyright (c) 1996-2003 Grace Development Team
8 *
9 * Maintained by Evgeny Stambulchik
12 * All Rights Reserved
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 /*
31 * utilities for graphs
35 #include <config.h>
37 #include <string.h>
39 #include "graceapp.h"
40 #include "core_utils.h"
42 static int graph_count_hook(Quark *q, void *udata, QTraverseClosure *closure)
44 int *ngraphs = (int *) udata;
46 if (quark_fid_get(q) == QFlavorGraph) {
47 (*ngraphs)++;
50 return TRUE;
53 int number_of_graphs(Quark *project)
55 int ngraphs = 0;
57 quark_traverse(project, graph_count_hook, &ngraphs);
59 return ngraphs;
62 static int frame_count_hook(Quark *q, void *udata, QTraverseClosure *closure)
64 int *nframes = (int *) udata;
66 if (quark_fid_get(q) == QFlavorFrame) {
67 (*nframes)++;
70 return TRUE;
73 int number_of_frames(Quark *project)
75 int nframes = 0;
77 quark_traverse(project, frame_count_hook, &nframes);
79 return nframes;
82 Quark *graph_get_current(const Quark *project)
84 if (project) {
85 Project *pr = project_get_data(project);
86 return pr->cg;
87 } else {
88 return NULL;
92 Quark *graph_next(Quark *project)
94 Quark *f, *g;
96 if (!project) {
97 return NULL;
100 f = frame_new(project);
101 g = graph_new(f);
102 if (g && number_of_graphs(project) == 1) {
103 Project *pr = project_get_data(project);
104 pr->cg = g;
106 return g;
109 int select_graph(Quark *gr)
111 if (quark_is_active(gr)) {
112 Project *pr = project_get_data(get_parent_project(gr));
113 if (pr) {
114 pr->cg = gr;
116 return RETURN_SUCCESS;
117 } else {
118 return RETURN_FAILURE;
120 } else {
121 return RETURN_FAILURE;
126 int is_log_axis(const Quark *q)
128 Quark *gr = get_parent_graph(q);
129 if ((axisgrid_is_x(q) && islogx(gr)) ||
130 (axisgrid_is_y(q) && islogy(gr))) {
131 return TRUE;
132 } else {
133 return FALSE;
137 int is_logit_axis(const Quark *q)
139 Quark *gr = get_parent_graph(q);
140 if ((axisgrid_is_x(q) && islogitx(gr)) ||
141 (axisgrid_is_y(q) && islogity(gr))) {
142 return TRUE;
143 } else {
144 return FALSE;
148 int islogx(Quark *gr)
150 if (graph_get_xscale(gr) == SCALE_LOG) {
151 return TRUE;
152 } else {
153 return FALSE;
157 int islogy(Quark *gr)
159 if (graph_get_yscale(gr) == SCALE_LOG) {
160 return TRUE;
161 } else {
162 return FALSE;
166 int islogitx(Quark *gr)
168 if (graph_get_xscale(gr) == SCALE_LOGIT) {
169 return TRUE;
170 } else {
171 return FALSE;
175 int islogity(Quark *gr)
177 if (graph_get_yscale(gr) == SCALE_LOGIT) {
178 return TRUE;
179 } else {
180 return FALSE;
185 /* The following routines determine default axis range and tickmarks */
187 static void autorange_bysets(Quark **sets, int nsets, int autos_type);
189 static int autotick_hook(Quark *q, void *udata, QTraverseClosure *closure)
191 int *amask = (int *) udata;
193 switch (quark_fid_get(q)) {
194 case QFlavorAGrid:
195 closure->descend = FALSE;
196 if (((*amask & AXIS_MASK_X) && axisgrid_is_x(q)) ||
197 ((*amask & AXIS_MASK_Y) && axisgrid_is_y(q))) {
198 axisgrid_autotick(q);
200 break;
203 return TRUE;
206 void autotick_graph_axes(Quark *q, int amask)
208 quark_traverse(q, autotick_hook, &amask);
211 void autoscale_bysets(Quark **sets, int nsets, int autos_type)
213 Quark *gr;
215 if (nsets <= 0) {
216 return;
219 gr = get_parent_graph(sets[0]);
221 autorange_bysets(sets, nsets, autos_type);
222 autotick_graph_axes(gr, autos_type);
225 int autoscale_graph(Quark *gr, int autos_type)
227 int nsets;
228 Quark **sets;
229 nsets = quark_get_descendant_sets(gr, &sets);
230 if (nsets) {
231 autoscale_bysets(sets, nsets, autos_type);
232 xfree(sets);
233 return RETURN_SUCCESS;
234 } else {
235 return RETURN_FAILURE;
239 static void round_axis_limits(double *amin, double *amax, int scale)
241 double smin, smax;
242 int nrange;
244 if (*amin == *amax) {
245 switch (sign(*amin)) {
246 case 0:
247 *amin = -1.0;
248 *amax = +1.0;
249 break;
250 case 1:
251 *amin /= 2.0;
252 *amax *= 2.0;
253 break;
254 case -1:
255 *amin *= 2.0;
256 *amax /= 2.0;
257 break;
261 if (scale == SCALE_LOG) {
262 if (*amax <= 0.0) {
263 errmsg("Can't autoscale a log axis by non-positive data");
264 *amax = 10.0;
265 *amin = 1.0;
266 return;
267 } else if (*amin <= 0.0) {
268 errmsg("Data have non-positive values");
269 *amin = *amax/1.0e3;
271 smin = log10(*amin);
272 smax = log10(*amax);
273 } else if (scale == SCALE_LOGIT) {
274 if (*amax <= 0.0) {
275 errmsg("Can't autoscale a logit axis by non-positive data");
276 *amax = 0.9;
277 *amin = 0.1;
278 return;
279 } else if (*amin <= 0.0) {
280 errmsg("Data have non-positive values");
281 *amin = 0.1;
283 smin = log(*amin/(1-*amin));
284 smax = log(*amax/(1-*amax));
285 } else {
286 smin = *amin;
287 smax = *amax;
290 if (sign(smin) == sign(smax)) {
291 nrange = -rint(log10(fabs(2*(smax - smin)/(smax + smin))));
292 nrange = MAX2(0, nrange);
293 } else {
294 nrange = 0;
296 smin = nicenum(smin, nrange, NICE_FLOOR);
297 smax = nicenum(smax, nrange, NICE_CEIL);
298 if (sign(smin) == sign(smax)) {
299 if (smax/smin > 5.0) {
300 smin = 0.0;
301 } else if (smin/smax > 5.0) {
302 smax = 0.0;
306 if (scale == SCALE_LOG) {
307 *amin = pow(10.0, smin);
308 *amax = pow(10.0, smax);
309 } else if (scale == SCALE_LOGIT) {
310 *amin = exp(smin)/(1.0 + exp(smin));
311 *amax = exp(smax)/(1.0 + exp(smax));
312 } else {
313 *amin = smin;
314 *amax = smax;
318 static void autorange_bysets(Quark **sets, int nsets, int autos_type)
320 Quark *gr;
321 world w;
322 double xmax, xmin, ymax, ymin;
323 int scale;
325 if (autos_type == AUTOSCALE_NONE || nsets <= 0) {
326 return;
329 gr = get_parent_graph(sets[0]);
331 graph_get_world(gr, &w);
333 if (graph_get_type(gr) == GRAPH_SMITH) {
334 if (autos_type == AUTOSCALE_X || autos_type == AUTOSCALE_XY) {
335 w.xg1 = -1.0;
336 w.yg1 = -1.0;
338 if (autos_type == AUTOSCALE_Y || autos_type == AUTOSCALE_XY) {
339 w.xg2 = 1.0;
340 w.yg2 = 1.0;
342 graph_set_world(gr, &w);
343 return;
346 xmin = w.xg1;
347 xmax = w.xg2;
348 ymin = w.yg1;
349 ymax = w.yg2;
350 if (autos_type == AUTOSCALE_XY) {
351 getsetminmax(sets, nsets, &xmin, &xmax, &ymin, &ymax);
352 } else if (autos_type == AUTOSCALE_X) {
353 getsetminmax_c(sets, nsets, &xmin, &xmax, &ymin, &ymax, 2);
354 } else if (autos_type == AUTOSCALE_Y) {
355 getsetminmax_c(sets, nsets, &xmin, &xmax, &ymin, &ymax, 1);
358 if (autos_type == AUTOSCALE_X || autos_type == AUTOSCALE_XY) {
359 scale = graph_get_xscale(gr);
360 round_axis_limits(&xmin, &xmax, scale);
361 w.xg1 = xmin;
362 w.xg2 = xmax;
365 if (autos_type == AUTOSCALE_Y || autos_type == AUTOSCALE_XY) {
366 scale = graph_get_yscale(gr);
367 round_axis_limits(&ymin, &ymax, scale);
368 w.yg1 = ymin;
369 w.yg2 = ymax;
372 graph_set_world(gr, &w);
376 * pan through world coordinates
378 int graph_scroll(Quark *gr, int type)
380 RunTime *rt = rt_from_quark(gr);
381 world w;
382 double xmax, xmin, ymax, ymin;
383 double dwc;
385 if (graph_get_world(gr, &w) == RETURN_SUCCESS) {
386 if (islogx(gr) == TRUE) {
387 xmin = log10(w.xg1);
388 xmax = log10(w.xg2);
389 } else {
390 xmin = w.xg1;
391 xmax = w.xg2;
394 if (islogy(gr) == TRUE) {
395 ymin = log10(w.yg1);
396 ymax = log10(w.yg2);
397 } else {
398 ymin = w.yg1;
399 ymax = w.yg2;
402 dwc = 1.0;
403 switch (type) {
404 case GSCROLL_LEFT:
405 dwc = -1.0;
406 case GSCROLL_RIGHT:
407 dwc *= rt->scrollper * (xmax - xmin);
408 xmin += dwc;
409 xmax += dwc;
410 break;
411 case GSCROLL_DOWN:
412 dwc = -1.0;
413 case GSCROLL_UP:
414 dwc *= rt->scrollper * (ymax - ymin);
415 ymin += dwc;
416 ymax += dwc;
417 break;
420 if (islogx(gr) == TRUE) {
421 w.xg1 = pow(10.0, xmin);
422 w.xg2 = pow(10.0, xmax);
423 } else {
424 w.xg1 = xmin;
425 w.xg2 = xmax;
428 if (islogy(gr) == TRUE) {
429 w.yg1 = pow(10.0, ymin);
430 w.yg2 = pow(10.0, ymax);
431 } else {
432 w.yg1 = ymin;
433 w.yg2 = ymax;
436 graph_set_world(gr, &w);
438 return RETURN_SUCCESS;
439 } else {
440 return RETURN_FAILURE;
444 int graph_zoom(Quark *gr, int type)
446 RunTime *rt = rt_from_quark(gr);
447 double dx, dy;
448 double xmax, xmin, ymax, ymin;
449 world w;
451 if (graph_get_world(gr, &w) == RETURN_SUCCESS) {
453 if (islogx(gr) == TRUE) {
454 xmin = log10(w.xg1);
455 xmax = log10(w.xg2);
456 } else {
457 xmin = w.xg1;
458 xmax = w.xg2;
461 if (islogy(gr) == TRUE) {
462 ymin = log10(w.yg1);
463 ymax = log10(w.yg2);
464 } else {
465 ymin = w.yg1;
466 ymax = w.yg2;
469 dx = rt->shexper * (xmax - xmin);
470 dy = rt->shexper * (ymax - ymin);
471 if (type == GZOOM_SHRINK) {
472 dx *= -1;
473 dy *= -1;
476 xmin -= dx;
477 xmax += dx;
478 ymin -= dy;
479 ymax += dy;
481 if (islogx(gr) == TRUE) {
482 w.xg1 = pow(10.0, xmin);
483 w.xg2 = pow(10.0, xmax);
484 } else {
485 w.xg1 = xmin;
486 w.xg2 = xmax;
489 if (islogy(gr) == TRUE) {
490 w.yg1 = pow(10.0, ymin);
491 w.yg2 = pow(10.0, ymax);
492 } else {
493 w.yg1 = ymin;
494 w.yg2 = ymax;
497 graph_set_world(gr, &w);
499 return RETURN_SUCCESS;
500 } else {
501 return RETURN_FAILURE;
506 * Arrange frames
508 int arrange_frames(Quark **frames, int nframes,
509 int nrows, int ncols, int order, int snake,
510 double loff, double roff, double toff, double boff,
511 double vgap, double hgap,
512 int hpack, int vpack)
514 int i, imax, j, jmax, iw, ih, nf;
515 double pw, ph, w, h;
516 view v;
517 Quark *f, *pr;
519 if (!frames) {
520 return RETURN_FAILURE;
522 f = frames[0];
524 pr = get_parent_project(f);
525 if (!pr) {
526 return RETURN_FAILURE;
529 if (hpack) {
530 hgap = 0.0;
532 if (vpack) {
533 vgap = 0.0;
535 if (ncols < 1 || nrows < 1) {
536 errmsg("# of rows and columns must be > 0");
537 return RETURN_FAILURE;
539 if (hgap < 0.0 || vgap < 0.0) {
540 errmsg("hgap and vgap must be >= 0");
541 return RETURN_FAILURE;
544 project_get_viewport(pr, &pw, &ph);
545 w = (pw - loff - roff)/(ncols + (ncols - 1)*hgap);
546 h = (ph - toff - boff)/(nrows + (nrows - 1)*vgap);
547 if (h <= 0.0 || w <= 0.0) {
548 errmsg("Page offsets are too large");
549 return RETURN_FAILURE;
552 nf = 0;
553 if (order & GA_ORDER_HV_INV) {
554 imax = ncols;
555 jmax = nrows;
556 } else {
557 imax = nrows;
558 jmax = ncols;
560 for (i = 0; i < imax && nf < nframes; i++) {
561 for (j = 0; j < jmax && nf < nframes; j++) {
562 f = frames[nf];
564 if (order & GA_ORDER_HV_INV) {
565 iw = i;
566 ih = j;
567 if (snake && (iw % 2)) {
568 ih = nrows - ih - 1;
570 } else {
571 iw = j;
572 ih = i;
573 if (snake && (ih % 2)) {
574 iw = ncols - iw - 1;
577 if (order & GA_ORDER_H_INV) {
578 iw = ncols - iw - 1;
580 /* viewport y coord goes bottom -> top ! */
581 if (!(order & GA_ORDER_V_INV)) {
582 ih = nrows - ih - 1;
585 v.xv1 = loff + iw*w*(1.0 + hgap);
586 v.xv2 = v.xv1 + w;
587 v.yv1 = boff + ih*h*(1.0 + vgap);
588 v.yv2 = v.yv1 + h;
589 frame_set_view(f, &v);
591 nf++;
594 return RETURN_SUCCESS;
597 void move_legend(Quark *fr, const VVector *shift)
599 legend *l = frame_get_legend(fr);
600 if (l) {
601 l->offset.x += shift->x;
602 l->offset.y += shift->y;
604 quark_dirtystate_set(fr, TRUE);
608 typedef struct {
609 double x, y;
610 } ext_xy_t;
612 static int hook(Quark *q, void *udata, QTraverseClosure *closure)
614 frame *f;
615 view v;
616 DObject *o;
617 AText *at;
618 ext_xy_t *ext_xy = (ext_xy_t *) udata;
620 switch (quark_fid_get(q)) {
621 case QFlavorFrame:
622 f = frame_get_data(q);
623 frame_get_view(q, &v);
624 v.xv1 *= ext_xy->x;
625 v.xv2 *= ext_xy->x;
626 v.yv1 *= ext_xy->y;
627 v.yv2 *= ext_xy->y;
628 frame_set_view(q, &v);
630 f->l.offset.x *= ext_xy->x;
631 f->l.offset.y *= ext_xy->y;
633 /* TODO: tickmark offsets */
634 quark_dirtystate_set(q, TRUE);
635 break;
636 case QFlavorDObject:
637 o = object_get_data(q);
638 if (object_get_loctype(q) == COORD_VIEW) {
639 o->ap.x *= ext_xy->x;
640 o->ap.y *= ext_xy->y;
641 o->offset.x *= ext_xy->x;
642 o->offset.y *= ext_xy->y;
644 quark_dirtystate_set(q, TRUE);
646 break;
647 case QFlavorAText:
648 at = atext_get_data(q);
649 if (object_get_loctype(q) == COORD_VIEW) {
650 at->ap.x *= ext_xy->x;
651 at->ap.y *= ext_xy->y;
652 at->offset.x *= ext_xy->x;
653 at->offset.y *= ext_xy->y;
655 quark_dirtystate_set(q, TRUE);
657 break;
660 return TRUE;
663 void rescale_viewport(Quark *project, double ext_x, double ext_y)
665 ext_xy_t ext_xy;
666 ext_xy.x = ext_x;
667 ext_xy.y = ext_y;
669 quark_traverse(project, hook, &ext_xy);