Icons are changed
[gnuplot] / src / color.c
1 #ifndef lint
2 static char *RCSid() { return RCSid("$Id: color.c,v 1.70.2.9 2008/06/23 17:51:35 mikulik Exp $"); }
3 #endif
4
5 /* GNUPLOT - color.c */
6
7 /*[
8  *
9  * Petr Mikulik, since December 1998
10  * Copyright: open source as much as possible
11  *
12  * What is here:
13  *   - Global variables declared in .h are initialized here
14  *   - Palette routines
15  *   - Colour box drawing
16  *
17 ]*/
18
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "color.h"
25 #include "getcolor.h"
26
27 #include "axis.h"
28 #include "gadgets.h"
29 #include "graphics.h"
30 #include "plot.h"
31 #include "graph3d.h"
32 #include "pm3d.h"
33 #include "graphics.h"
34 #include "term_api.h"
35 #include "util3d.h"
36 #include "alloc.h"
37
38 /* COLOUR MODES - GLOBAL VARIABLES */
39
40 t_sm_palette sm_palette;  /* initialized in init_color() */
41
42 /* Copy of palette previously in use.
43  * Exported so that change_term() can invalidate contents
44  * FIXME: better naming 
45  */
46 static t_sm_palette prev_palette = {
47         -1, -1, -1, -1, -1, -1, -1, -1,
48         (rgb_color *) 0, -1
49     };
50
51
52 #ifdef EXTENDED_COLOR_SPECS
53 int supply_extended_color_specs = 0;
54 #endif
55
56 /* Corners of the colour box. */
57 static int cb_x_from, cb_x_to, cb_y_from, cb_y_to;
58
59 /* Internal prototype declarations: */
60
61 static void draw_inside_color_smooth_box_postscript __PROTO((FILE * out));
62 static void draw_inside_color_smooth_box_bitmap __PROTO((FILE * out));
63 void cbtick_callback __PROTO((AXIS_INDEX axis, double place, char *text, struct lp_style_type grid));
64
65
66
67 /* *******************************************************************
68   ROUTINES
69  */
70
71
72 void
73 init_color()
74 {
75   /* initialize global palette */
76   sm_palette.colorFormulae = 37;  /* const */
77   sm_palette.formulaR = 7;
78   sm_palette.formulaG = 5;
79   sm_palette.formulaB = 15;
80   sm_palette.positive = SMPAL_POSITIVE;
81   sm_palette.use_maxcolors = 0;
82   sm_palette.colors = 0;
83   sm_palette.color = NULL;
84   sm_palette.ps_allcF = 0;
85   sm_palette.gradient_num = 0;
86   sm_palette.gradient = NULL;
87   sm_palette.cmodel = C_MODEL_RGB;
88   sm_palette.Afunc.at = sm_palette.Bfunc.at = sm_palette.Cfunc.at = NULL;
89   sm_palette.gamma = 1.5;
90
91   /* initialisation of smooth color box */
92   color_box = default_color_box;
93 }
94
95
96 /*
97    Make the colour palette. Return 0 on success
98    Put number of allocated colours into sm_palette.colors
99  */
100 int
101 make_palette()
102 {
103     int i;
104     double gray;
105
106     if (!term->make_palette) {
107         fprintf(stderr, "Error: terminal \"%s\" does not support continuous colors.\n",term->name);
108         return 1;
109     }
110
111     /* ask for suitable number of colours in the palette */
112     i = term->make_palette(NULL);
113     if (i == 0) {
114         /* terminal with its own mapping (PostScript, for instance)
115            It will not change palette passed below, but non-NULL has to be
116            passed there to create the header or force its initialization
117          */
118         if (memcmp(&prev_palette, &sm_palette, sizeof(t_sm_palette))) {
119             term->make_palette(&sm_palette);
120             prev_palette = sm_palette;
121             FPRINTF((stderr,"make_palette: calling term->make_palette for term with ncolors == 0\n"));
122         } else
123             FPRINTF((stderr,"make_palette: skipping duplicate palette for term with ncolors == 0\n"));
124         return 0;
125     }
126
127     /* set the number of colours to be used (allocated) */
128     sm_palette.colors = i;
129     if (sm_palette.use_maxcolors > 0 && i > sm_palette.use_maxcolors)
130         sm_palette.colors = sm_palette.use_maxcolors;
131
132     if (prev_palette.colorFormulae < 0
133         || sm_palette.colorFormulae != prev_palette.colorFormulae
134         || sm_palette.colorMode != prev_palette.colorMode
135         || sm_palette.formulaR != prev_palette.formulaR
136         || sm_palette.formulaG != prev_palette.formulaG
137         || sm_palette.formulaB != prev_palette.formulaB
138         || sm_palette.positive != prev_palette.positive
139         || sm_palette.colors != prev_palette.colors) {
140         /* print the message only if colors have changed */
141         if (interactive)
142         fprintf(stderr, "smooth palette in %s: available %i color positions; using %i of them\n", term->name, i, sm_palette.colors);
143     }
144
145     prev_palette = sm_palette;
146
147     if (sm_palette.color != NULL) {
148         free(sm_palette.color);
149         sm_palette.color = NULL;
150     }
151     sm_palette.color = gp_alloc( sm_palette.colors * sizeof(rgb_color),
152                                  "pm3d palette color");
153
154     /*  fill sm_palette.color[]  */
155     for (i = 0; i < sm_palette.colors; i++) {
156         gray = (double) i / (sm_palette.colors - 1);    /* rescale to [0;1] */
157         rgb1_from_gray( gray, &(sm_palette.color[i]) );
158     }
159
160     /* let the terminal make the palette from the supplied RGB triplets */
161     term->make_palette(&sm_palette);
162
163     return 0;
164 }
165
166 /*
167  * Force a mismatch between the current palette and whatever is sent next,
168  * so that the new one will always be loaded 
169  */
170 void
171 invalidate_palette()
172 {
173     prev_palette.colors = -1;
174 }
175
176 /*
177    Set the colour on the terminal
178    Currently, each terminal takes care of remembering the current colour,
179    so there is not much to do here.
180  */
181 void
182 set_color(double gray)
183 {
184     t_colorspec color;
185     if (!(term->set_color))
186         return;
187     color.type = TC_FRAC;
188     color.value = gray;
189     term->set_color(&color);
190 }
191
192 void
193 set_rgbcolor(int rgblt)
194 {
195     t_colorspec color;
196     if (!(term->set_color))
197         return;
198     color.type = TC_RGB;
199     color.lt = rgblt;
200     term->set_color(&color);
201 }
202
203 void ifilled_quadrangle(gpiPoint* icorners)
204 {
205     icorners->style = FS_OPAQUE;
206     term->filled_polygon(4, icorners);
207
208     if (pm3d.hidden3d_tag) {
209
210         int i;
211
212         /* Colour has changed, so we must apply line properties again.
213          * FIXME: It would be cleaner to apply the general line properties
214          * outside this loop, and limit ourselves to apply_pm3dcolor().
215          */
216         static struct lp_style_type lp = DEFAULT_LP_STYLE_TYPE;
217         lp_use_properties(&lp, pm3d.hidden3d_tag, 0);
218         term_apply_lp_properties(&lp);
219
220         term->move(icorners[0].x, icorners[0].y);
221         for (i = 3; i >= 0; i--) {
222             term->vector(icorners[i].x, icorners[i].y);
223         }
224     }
225 }
226
227
228 /* The routine above for 4 points explicitly.
229  * This is the only routine which supportes extended
230  * color specs currently.
231  */
232 #ifdef EXTENDED_COLOR_SPECS
233 void
234 filled_quadrangle(gpdPoint * corners, gpiPoint * icorners)
235 #else
236 void
237 filled_quadrangle(gpdPoint * corners)
238 #endif
239 {
240     int i;
241     double x, y;
242 #ifndef EXTENDED_COLOR_SPECS
243     gpiPoint icorners[4];
244 #endif
245     for (i = 0; i < 4; i++) {
246         map3d_xy_double(corners[i].x, corners[i].y, corners[i].z, &x, &y);
247         icorners[i].x = x;
248         icorners[i].y = y;
249     }
250
251     ifilled_quadrangle(icorners);
252 }
253
254
255 /*
256    Makes mapping from real 3D coordinates, passed as coords array,
257    to 2D terminal coordinates, then draws filled polygon
258  */
259 void
260 filled_polygon_3dcoords(int points, struct coordinate GPHUGE * coords)
261 {
262     int i;
263     double x, y;
264     gpiPoint *icorners;
265     icorners = gp_alloc(points * sizeof(gpiPoint), "filled_polygon3d corners");
266     for (i = 0; i < points; i++) {
267         map3d_xy_double(coords[i].x, coords[i].y, coords[i].z, &x, &y);
268         icorners[i].x = x;
269         icorners[i].y = y;
270     }
271 #ifdef EXTENDED_COLOR_SPECS
272     if (supply_extended_color_specs) {
273         icorners[0].spec.gray = -1;     /* force solid color */
274     }
275 #endif
276     icorners->style = FS_OPAQUE;
277     term->filled_polygon(points, icorners);
278     free(icorners);
279 }
280
281
282 /*
283    Makes mapping from real 3D coordinates, passed as coords array, but at z coordinate
284    fixed (base_z, for instance) to 2D terminal coordinates, then draws filled polygon
285  */
286 void
287 filled_polygon_3dcoords_zfixed(int points, struct coordinate GPHUGE * coords, double z)
288 {
289     int i;
290     double x, y;
291     gpiPoint *icorners;
292     icorners = gp_alloc(points * sizeof(gpiPoint), "filled_polygon_zfix corners");
293     for (i = 0; i < points; i++) {
294         map3d_xy_double(coords[i].x, coords[i].y, z, &x, &y);
295         icorners[i].x = x;
296         icorners[i].y = y;
297     }
298 #ifdef EXTENDED_COLOR_SPECS
299     if (supply_extended_color_specs) {
300         icorners[0].spec.gray = -1;     /* force solid color */
301     }
302 #endif
303     icorners->style = FS_OPAQUE;
304     term->filled_polygon(points, icorners);
305     free(icorners);
306 }
307
308
309 /*
310    Draw colour smooth box
311
312    Firstly two helper routines for plotting inside of the box
313    for postscript and for other terminals, finally the main routine
314  */
315
316
317 /* plot the colour smooth box for from terminal's integer coordinates
318    [cb_x_from,cb_y_from] to [cb_x_to,cb_y_to].
319    This routine is for postscript files --- actually, it writes a small
320    PS routine.
321  */
322 static void
323 draw_inside_color_smooth_box_postscript(FILE * out)
324 {
325     int scale_x = (cb_x_to - cb_x_from), scale_y = (cb_y_to - cb_y_from);
326     fputs("stroke gsave\t%% draw gray scale smooth box\n"
327           "maxcolors 0 gt {/imax maxcolors def} {/imax 1024 def} ifelse\n", out);
328
329     /* nb. of discrete steps (counted in the loop) */
330     fprintf(out, "%i %i translate %i %i scale 0 setlinewidth\n", cb_x_from, cb_y_from, scale_x, scale_y);
331     /* define left bottom corner and scale of the box so that all coordinates
332        of the box are from [0,0] up to [1,1]. Further, this normalization
333        makes it possible to pass y from [0,1] as parameter to setgray */
334     fprintf(out, "/ystep 1 imax div def /y0 0 def /ii 0 def\n");
335     /* local variables; y-step, current y position and counter ii;  */
336     if (sm_palette.positive == SMPAL_NEGATIVE)  /* inverted gray for negative figure */
337         fputs("{ 0.99999 y0 sub g ", out); /* 1 > x > 1-1.0/1024 */
338     else
339         fputs("{ y0 g ", out);
340     if (color_box.rotation == 'v')
341         fputs("0 y0 N 1 0 V 0 ystep V -1 0 f\n", out);
342     else
343         fputs("y0 0 N 0 1 V ystep 0 V 0 -1 f\n", out);
344     fputs("/y0 y0 ystep add def /ii ii 1 add def\n"
345           "ii imax ge {exit} if } loop\n"
346           "grestore 0 setgray\n", out);
347 }
348
349
350
351 /* plot the colour smooth box for from terminal's integer coordinates
352    [x_from,y_from] to [x_to,y_to].
353    This routine is for non-postscript files, as it does explicitly the loop
354    over all thin rectangles
355  */
356 static void
357 draw_inside_color_smooth_box_bitmap(FILE * out)
358 {
359     int steps = 128; /* I think that nobody can distinguish more colours drawn in the palette */
360     int i, xy, xy2, xy_from, xy_to;
361     double xy_step, gray;
362     gpiPoint corners[4];
363
364     (void) out;                 /* to avoid "unused parameter" warning */
365     if (color_box.rotation == 'v') {
366         corners[0].x = corners[3].x = cb_x_from;
367         corners[1].x = corners[2].x = cb_x_to;
368         xy_from = cb_y_from;
369         xy_to = cb_y_to;
370     } else {
371         corners[0].y = corners[1].y = cb_y_from;
372         corners[2].y = corners[3].y = cb_y_to;
373         xy_from = cb_x_from;
374         xy_to = cb_x_to;
375     }
376     xy_step = (color_box.rotation == 'h' ? cb_x_to - cb_x_from : cb_y_to - cb_y_from) / (double) steps;
377
378     for (i = 0; i < steps; i++) {
379         gray = (double) i / steps;      /* colours equidistantly from [0,1] */
380         if (sm_palette.positive == SMPAL_NEGATIVE)
381             gray = 1 - gray;
382         /* Set the colour (also for terminals which support extended specs). */
383         set_color(gray);
384         xy = xy_from + (int) (xy_step * i);
385         xy2 = xy_from + (int) (xy_step * (i + 1));
386         if (color_box.rotation == 'v') {
387             corners[0].y = corners[1].y = xy;
388             corners[2].y = corners[3].y = (i == steps - 1) ? xy_to : xy2;
389         } else {
390             corners[0].x = corners[3].x = xy;
391             corners[1].x = corners[2].x = (i == steps - 1) ? xy_to : xy2;
392         }
393 #ifdef EXTENDED_COLOR_SPECS
394         if (supply_extended_color_specs) {
395             corners[0].spec.gray = -1;  /* force solid color */
396         }
397 #endif
398         /* print the rectangle with the given colour */
399         corners->style = FS_OPAQUE;
400         term->filled_polygon(4, corners);
401     }
402 }
403
404 /* Notice HBB 20010720: would be static, but HP-UX gcc bug forbids
405  * this, for now */
406 void
407 cbtick_callback(
408     AXIS_INDEX axis,
409     double place,
410     char *text,
411     struct lp_style_type grid) /* linetype or -2 for no grid */
412 {
413     int len = (text ? CB_AXIS.ticscale : CB_AXIS.miniticscale)
414         * (CB_AXIS.tic_in ? -1 : 1) * (term->h_tic);
415     double cb_place = (place - CB_AXIS.min) / (CB_AXIS.max - CB_AXIS.min);
416         /* relative z position along the colorbox axis */
417     unsigned int x1, y1, x2, y2;
418
419 #if 0
420     printf("cbtick_callback:  place=%g\ttext=\"%s\"\tgrid.l_type=%i\n",place,text,grid.l_type);
421 #endif
422     (void) axis;                /* to avoid 'unused' warning */
423
424     /* calculate tic position */
425     if (color_box.rotation == 'h') {
426         x1 = x2 = cb_x_from + cb_place * (cb_x_to - cb_x_from);
427         y1 = cb_y_from;
428         y2 = cb_y_from - len;
429     } else {
430         x1 = cb_x_to;
431         x2 = cb_x_to + len;
432         y1 = y2 = cb_y_from + cb_place * (cb_y_to - cb_y_from);
433     }
434
435     /* draw grid line */
436     if (grid.l_type > LT_NODRAW) {
437         term_apply_lp_properties(&grid);        /* grid linetype */
438         if (color_box.rotation == 'h') {
439             (*term->move) (x1, cb_y_from);
440             (*term->vector) (x1, cb_y_to);
441         } else {
442             (*term->move) (cb_x_from, y1);
443             (*term->vector) (cb_x_to, y1);
444         }
445         term_apply_lp_properties(&border_lp);   /* border linetype */
446     }
447
448     /* draw tic */
449     (*term->move) (x1, y1);
450     (*term->vector) (x2, y2);
451
452     /* draw label */
453     if (text) {
454         /* get offset */
455         int offsetx, offsety;
456         map3d_position_r(&(axis_array[axis].ticdef.offset),
457                          &offsetx, &offsety, "cbtics");
458         /* User-specified different color for the tics text */
459         if (axis_array[axis].ticdef.textcolor.type != TC_DEFAULT)
460             apply_pm3dcolor(&(axis_array[axis].ticdef.textcolor), term);
461         if (color_box.rotation == 'h') {
462             int y3 = cb_y_from - (term->v_char);
463             int hrotate = 0;
464
465             if (axis_array[axis].tic_rotate
466                 && (*term->text_angle)(axis_array[axis].tic_rotate))
467                     hrotate = axis_array[axis].tic_rotate;
468             if (len > 0) y3 -= len; /* add outer tics len */
469             if (y3<0) y3 = 0;
470             write_multiline(x2+offsetx, y3+offsety, text,
471                             (hrotate ? LEFT : CENTRE), CENTRE, hrotate,
472                             axis_array[axis].ticdef.font);
473             if (hrotate)
474                 (*term->text_angle)(0);
475         } else {
476             unsigned int x3 = cb_x_to + (term->h_char);
477             if (len > 0) x3 += len; /* add outer tics len */
478             write_multiline(x3+offsetx, y2+offsety, text,
479                             LEFT, CENTRE, 0.0,
480                             axis_array[axis].ticdef.font);
481         }
482         term_apply_lp_properties(&border_lp);   /* border linetype */
483     }
484
485     /* draw tic on the mirror side */
486     if (CB_AXIS.ticmode & TICS_MIRROR) {
487         if (color_box.rotation == 'h') {
488             y1 = cb_y_to;
489             y2 = cb_y_to + len;
490         } else {
491             x1 = cb_x_from;
492             x2 = cb_x_from - len;
493         }
494         (*term->move) (x1, y1);
495         (*term->vector) (x2, y2);
496     }
497 }
498
499 /*
500    Finally the main colour smooth box drawing routine
501  */
502 void
503 draw_color_smooth_box(int plot_mode)
504 {
505     double tmp;
506     FILE *out = gppsfile;       /* either gpoutfile or PSLATEX_auxfile */
507
508     if (color_box.where == SMCOLOR_BOX_NO)
509         return;
510     if (!term->filled_polygon)
511         return;
512
513     /*
514        firstly, choose some good position of the color box
515
516        user's position like that (?):
517        else {
518        x_from = color_box.xlow;
519        x_to   = color_box.xhigh;
520        }
521      */
522     if (color_box.where == SMCOLOR_BOX_USER) {
523         if (!is_3d_plot) {
524             double xtemp, ytemp;
525             map_position(&color_box.origin, &cb_x_from, &cb_y_from, "cbox");
526             map_position_r(&color_box.size, &xtemp, &ytemp, "cbox");
527             cb_x_to = xtemp;
528             cb_y_to = ytemp;
529         } else if (splot_map && is_3d_plot) {
530             /* In map view mode we allow any coordinate system for placement */
531             double xtemp, ytemp;
532             map3d_position_double(&color_box.origin, &xtemp, &ytemp, "cbox");
533             cb_x_from = xtemp;
534             cb_y_from = ytemp;
535             map3d_position_r(&color_box.size, &cb_x_to, &cb_y_to, "cbox");
536         } else {
537             /* But in full 3D mode we only allow screen coordinates */
538             cb_x_from = color_box.origin.x * (term->xmax) + 0.5;
539             cb_y_from = color_box.origin.y * (term->ymax) + 0.5;
540             cb_x_to = color_box.size.x * (term->xmax) + 0.5;
541             cb_y_to = color_box.size.y * (term->ymax) + 0.5;
542         }
543         cb_x_to += cb_x_from;
544         cb_y_to += cb_y_from;
545
546     } else { /* color_box.where == SMCOLOR_BOX_DEFAULT */
547         if (plot_mode == MODE_SPLOT && !splot_map) {
548             /* HBB 20031215: new code.  Constants fixed to what the result
549              * of the old code in default view (set view 60,30,1,1)
550              * happened to be. Somebody fix them if they're not right! */
551             cb_x_from = xmiddle + 0.709 * xscaler;
552             cb_x_to   = xmiddle + 0.778 * xscaler;
553             cb_y_from = ymiddle - 0.147 * yscaler;
554             cb_y_to   = ymiddle + 0.497 * yscaler;
555
556         } else if (is_3d_plot) {
557             /* MWS 09-Dec-05, make color box full size for splot maps. */
558             double dx = (X_AXIS.max - X_AXIS.min);
559             map3d_xy(X_AXIS.max + dx * 0.025, Y_AXIS.min, base_z, &cb_x_from, &cb_y_from);
560             map3d_xy(X_AXIS.max + dx * 0.075, Y_AXIS.max, ceiling_z, &cb_x_to, &cb_y_to);
561         } else { /* 2D plot */
562             struct position default_origin = {graph,graph,graph, 1.025, 0, 0};
563             struct position default_size = {graph,graph,graph, 0.05, 1.0, 0};
564             double xtemp, ytemp;
565             map_position(&default_origin, &cb_x_from, &cb_y_from, "cbox");
566             map_position_r(&default_size, &xtemp, &ytemp, "cbox");
567             cb_x_to = xtemp + cb_x_from;
568             cb_y_to = ytemp + cb_y_from;
569         }
570
571         /* now corrections for outer tics */
572         if (color_box.rotation == 'v') {
573             int cblen = (CB_AXIS.tic_in ? -1 : 1) * CB_AXIS.ticscale * 
574                 (term->h_tic); /* positive for outer tics */
575             int ylen = (Y_AXIS.tic_in ? -1 : 1) * Y_AXIS.ticscale * 
576                 (term->h_tic); /* positive for outer tics */
577             if ((cblen > 0) && (CB_AXIS.ticmode & TICS_MIRROR)) {
578                 cb_x_from += cblen;
579                 cb_x_to += cblen;
580             }
581             if ((ylen > 0) && 
582                 (axis_array[FIRST_Y_AXIS].ticmode & TICS_MIRROR)) {
583                 cb_x_from += ylen;
584                 cb_x_to += ylen;
585             }
586         }
587     }
588
589     if (cb_y_from > cb_y_to) { /* switch them */
590         tmp = cb_y_to;
591         cb_y_to = cb_y_from;
592         cb_y_from = tmp;
593     }
594
595     /* Optimized version of the smooth colour box in postscript. Advantage:
596        only few lines of code is written into the output file.
597      */
598     if (gppsfile)
599         draw_inside_color_smooth_box_postscript(out);
600     else
601         draw_inside_color_smooth_box_bitmap(out);
602
603     if (color_box.border) {
604         /* now make boundary around the colour box */
605         if (color_box.border_lt_tag >= 0) {
606             /* user specified line type */
607             struct lp_style_type lp = border_lp;
608             lp_use_properties(&lp, color_box.border_lt_tag, 1);
609             term_apply_lp_properties(&lp);
610         } else {
611             /* black solid colour should be chosen, so it's border linetype */
612             term_apply_lp_properties(&border_lp);
613         }
614         newpath();
615         (term->move) (cb_x_from, cb_y_from);
616         (term->vector) (cb_x_to, cb_y_from);
617         (term->vector) (cb_x_to, cb_y_to);
618         (term->vector) (cb_x_from, cb_y_to);
619         (term->vector) (cb_x_from, cb_y_from);
620         closepath();
621
622         /* Set line properties to some value, this also draws lines in postscript terminals. */
623             term_apply_lp_properties(&border_lp);
624         }
625
626     /* draw tics */
627     if (axis_array[COLOR_AXIS].ticmode) {
628         term_apply_lp_properties(&border_lp); /* border linetype */
629         gen_tics(COLOR_AXIS, cbtick_callback );
630     }
631
632     /* write the colour box label */
633     if (CB_AXIS.label.text) {
634         int x, y;
635         apply_pm3dcolor(&(CB_AXIS.label.textcolor),term);
636         if (color_box.rotation == 'h') {
637             int len = CB_AXIS.ticscale * (CB_AXIS.tic_in ? 1 : -1) * 
638                 (term->v_tic);
639
640             map3d_position_r(&(CB_AXIS.label.offset), &x, &y, "smooth_box");
641             x += (cb_x_from + cb_x_to) / 2;
642
643 #define DEFAULT_Y_DISTANCE 1.0
644             y += cb_y_from + (- DEFAULT_Y_DISTANCE - 1.7) * term->v_char;
645 #undef DEFAULT_Y_DISTANCE
646             if (len < 0) y += len;
647             if (x<0) x = 0;
648             if (y<0) y = 0;
649             write_multiline(x, y, CB_AXIS.label.text, CENTRE, JUST_CENTRE, 0,
650                             CB_AXIS.label.font);
651         } else {
652             int len = CB_AXIS.ticscale * (CB_AXIS.tic_in ? -1 : 1) *
653                 (term->h_tic);
654             /* calculate max length of cb-tics labels */
655             widest_tic_strlen = 0;
656             if (CB_AXIS.ticmode & TICS_ON_BORDER) {
657                 widest_tic_strlen = 0; /* reset the global variable */
658                 gen_tics(COLOR_AXIS, /* 0, */ widest_tic_callback);
659             }
660             map3d_position_r(&(CB_AXIS.label.offset), &x, &y, "smooth_box");
661 #define DEFAULT_X_DISTANCE 1.0
662             x += cb_x_to + (widest_tic_strlen + DEFAULT_X_DISTANCE + 1.5) * term->h_char;
663 #undef DEFAULT_X_DISTANCE
664             if (len > 0) x += len;
665             y += (cb_y_from + cb_y_to) / 2;
666             if (x<0) x = 0;
667             if (y<0) y = 0;
668             if ((*term->text_angle)(CB_AXIS.label.rotate)) {
669                 write_multiline(x, y, CB_AXIS.label.text, CENTRE, JUST_TOP,
670                                 CB_AXIS.label.rotate, CB_AXIS.label.font);
671                 (*term->text_angle)(0);
672             } else {
673                 write_multiline(x, y, CB_AXIS.label.text, LEFT, JUST_TOP, 0, CB_AXIS.label.font);
674             }
675         }
676         reset_textcolor(&(CB_AXIS.label.textcolor),term);
677     }
678
679 }
680