Skip to content

Commit 3fe48c0

Browse files
author
sfeam
committed
for autoscaling, merge data ranges plotted to both ends of a linked axis pair
1 parent 347d8bd commit 3fe48c0

File tree

5 files changed

+179
-39
lines changed

5 files changed

+179
-39
lines changed

ChangeLog

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
2017-10-08 Ethan A Merritt <[email protected]>
2+
3+
* src/axis.c (reconcile_linked_axes) src/axis.h src/plot2d.c:
4+
gnuplot version 5.0 always tracked the autoscale range on the primary
5+
axis (x1 or y1) of a linked pair, even if the data was actually plotted
6+
on x2 or y2. In verison 5.2 we track the data range on x1 x2 y1 y2
7+
separately. This caused breakage wherever the program assumed the
8+
autoscale range on x1 (or y1) was always current. Worse, it would
9+
propagate that range onto the secondary axis, possibly overwriting the
10+
correct range. Now we introduce a new routine reconcile_linked_axes()
11+
that merges the min/max values from e.g. x1 and x2 so that the
12+
autoscaled range covers input data plotted on either axis.
13+
Bug #1973
14+
15+
* demo/linked_autoscale.dem: Exercise the new code (fails on previous
16+
gnuplot versions).
17+
118
2017-10-06 Hans-Bernhard Broeker <[email protected]>
219

320
* src/command.c: Move WEXITSTATUS fall-back definition away from here.

demo/linked_autoscale.dem

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#
2+
# Exercise combinations of linked axes and autoscaled data
3+
# Some of these produced errors or incorrect plots in previous
4+
# gnuplot versions (5.0 - 5.2) E.g. But #1973
5+
#
6+
set samples 5
7+
set x2tics 1
8+
set tics nomirror
9+
set auto noextend
10+
set linetype 1 ps 2
11+
set key Left left samplen .01
12+
unset link x
13+
14+
set multiplot layout 3,1
15+
set title 'axes x1y1'
16+
plot sample [i=1:5] '+' using (i):(i) axes x1y1 with lp title 'no link'
17+
18+
set link x2
19+
plot sample [i=1:5] '+' using (i):(i) axes x1y1 with lp title 'set link x2'
20+
21+
set link x2 via x*2 inv x/2
22+
plot sample [i=1:5] '+' using (i):(i) axes x1y1 with lp title 'set link x2 via x*2 inv x/2'
23+
unset multiplot
24+
pause -1 "Hit return to continue"
25+
26+
set multiplot layout 3,1
27+
unset link x2
28+
set title 'axes x2y1'
29+
plot sample [i=1:5] '+' using (i):(i) axes x2y1 with lp title 'no link'
30+
31+
set link x2
32+
plot sample [i=1:5] '+' using (i):(i) axes x2y1 with lp title 'set link x2'
33+
34+
set link x2 via x*2 inv x/2
35+
plot sample [i=1:5] '+' using (i):(i) axes x2y1 with lp title 'set link x2 via x*2 inv x/2'
36+
unset multiplot
37+
pause -1 "Hit return to continue"
38+
39+
#
40+
# More problem cases
41+
#
42+
reset
43+
set x2tics
44+
set link x2
45+
set key Left left samplen .01
46+
set multiplot layout 1,2
47+
plot 'silver.dat' using 2:1 axes x1y1 with lp, '' using 3:1 with lp axes x2y1
48+
plot 'silver.dat' using 2:1 axes x2y1 with lp, '' using 3:1 with lp axes x1y1
49+
unset multiplot
50+
pause -1 "Hit return to continue"
51+
52+
reset
53+
set link y
54+
set y2tics
55+
set key Left left samplen .01
56+
plot 'silver.dat' using 1:2 axes x1y1, '' using 1:3 axes x1y2
57+
pause -1 "Hit return to continue"
58+
59+
#
60+
# This is a sampling bug rather than an autoscale bug
61+
#
62+
set title "Should be 5 samples but may only get 3"
63+
set key title 'plot sample [i=1:5:1] "+" using 1:1 axes x2y1'
64+
set link x2 via x*2 inv x/2
65+
set x2tics
66+
plot sample [i=1:5:1] '+' using 1:1 axes x2y1 with lp title 'set link x2 via x*2 inv x/2'
67+
pause -1 "Hit return to continue"
68+
reset

src/axis.c

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#ifndef lint
2-
static char *RCSid() { return RCSid("$Id: axis.c,v 1.247 2017-09-29 19:02:23 sfeam Exp $"); }
2+
static char *RCSid() { return RCSid("$Id: axis.c,v 1.248 2017-10-09 00:49:03 sfeam Exp $"); }
33
#endif
44

55
/* GNUPLOT - axis.c */
@@ -2487,22 +2487,68 @@ extend_primary_ticrange(AXIS *axis)
24872487

24882488
/*
24892489
* As data is read in or functions evaluated, the min/max values are tracked
2490-
* for the visible axes but not for the primary (linear) axes.
2491-
* This routine fills in the primary min/max from the linked secondary axis.
2490+
* for the secondary (visible) axes but not for the linked primary (linear) axis.
2491+
* This routine fills in the primary min/max from the secondary axis.
24922492
*/
24932493
void
24942494
update_primary_axis_range(struct axis *secondary)
24952495
{
24962496
struct axis *primary = secondary->linked_to_primary;
24972497

2498-
if (nonlinear(secondary)) {
2498+
if (primary != NULL) {
2499+
/* nonlinear axis (secondary is visible; primary is hidden) */
24992500
primary->min = eval_link_function(primary, secondary->min);
25002501
primary->max = eval_link_function(primary, secondary->max);
25012502
primary->data_min = eval_link_function(primary, secondary->data_min);
25022503
primary->data_max = eval_link_function(primary, secondary->data_max);
25032504
}
25042505
}
25052506

2507+
/*
2508+
* Same thing but in the opposite direction. We read in data on the primary axis
2509+
* and want the autoscaling on a linked secondary axis to match.
2510+
*/
2511+
void
2512+
update_secondary_axis_range(struct axis *primary)
2513+
{
2514+
struct axis *secondary = primary->linked_to_secondary;
2515+
2516+
if (secondary != NULL) {
2517+
secondary->min = eval_link_function(secondary, primary->min);
2518+
secondary->max = eval_link_function(secondary, primary->max);
2519+
secondary->data_min = eval_link_function(secondary, primary->data_min);
2520+
secondary->data_max = eval_link_function(secondary, primary->data_max);
2521+
}
2522+
}
2523+
2524+
/*
2525+
* gnuplot version 5.0 always maintained autoscaled range on x1
2526+
* specifically, transforming from x2 coordinates if necessary.
2527+
* In version 5.2 we track the x1 and x2 axis data limits separately.
2528+
* However if x1 and x2 are linked to each other we must reconcile
2529+
* their data limits before plotting.
2530+
*/
2531+
void
2532+
reconcile_linked_axes(AXIS *primary, AXIS *secondary)
2533+
{
2534+
double dummy;
2535+
int inrange = INRANGE;
2536+
if ((primary->autoscale & AUTOSCALE_BOTH) != AUTOSCALE_NONE
2537+
&& primary->linked_to_secondary) {
2538+
double min_2_into_1 = eval_link_function(primary, secondary->data_min);
2539+
double max_2_into_1 = eval_link_function(primary, secondary->data_max);
2540+
2541+
/* Merge secondary min/max into primary data range */
2542+
ACTUAL_STORE_AND_UPDATE_RANGE(dummy, min_2_into_1, inrange, primary, FALSE, NOOP);
2543+
ACTUAL_STORE_AND_UPDATE_RANGE(dummy, max_2_into_1, inrange, primary, FALSE, NOOP);
2544+
(void)dummy; /* Otherwise the compiler complains about an unused variable */
2545+
2546+
/* Take the result back the other way to update secondary */
2547+
secondary->min = eval_link_function(secondary, primary->min);
2548+
secondary->max = eval_link_function(secondary, primary->max);
2549+
}
2550+
}
2551+
25062552

25072553
/*
25082554
* Check for linked-axis coordinate transformation given by command

src/axis.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* $Id: axis.h,v 1.179 2017-09-29 19:02:23 sfeam Exp $
2+
* $Id: axis.h,v 1.180 2017-10-09 00:49:03 sfeam Exp $
33
*
44
*/
55

@@ -595,6 +595,8 @@ void clone_linked_axes __PROTO((AXIS *axis1, AXIS *axis2));
595595
AXIS *get_shadow_axis __PROTO((AXIS *axis));
596596
void extend_primary_ticrange __PROTO((AXIS *axis));
597597
void update_primary_axis_range __PROTO((struct axis *secondary));
598+
void update_secondary_axis_range __PROTO((struct axis *primary));
599+
void reconcile_linked_axes __PROTO((AXIS *primary, AXIS *secondary));
598600

599601
int map_x __PROTO((double value));
600602
int map_y __PROTO((double value));

src/plot2d.c

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#ifndef lint
2-
static char *RCSid() { return RCSid("$Id: plot2d.c,v 1.459 2017-09-29 19:23:37 sfeam Exp $"); }
2+
static char *RCSid() { return RCSid("$Id: plot2d.c,v 1.460 2017-10-09 00:49:03 sfeam Exp $"); }
33
#endif
44

55
/* GNUPLOT - plot2d.c */
@@ -2718,11 +2718,7 @@ eval_plots()
27182718

27192719
/* Reset flags to auto-scale X axis to contents of data set */
27202720
if (!(uses_axis[x_axis] & USES_AXIS_FOR_DATA) && X_AXIS.autoscale) {
2721-
struct axis *scaling_axis;
2722-
if (x_axis == SECOND_X_AXIS && !X_AXIS.linked_to_primary)
2723-
scaling_axis = &axis_array[SECOND_X_AXIS];
2724-
else
2725-
scaling_axis = &axis_array[FIRST_X_AXIS];
2721+
struct axis *scaling_axis = &axis_array[this_plot->x_axis];
27262722
if (scaling_axis->autoscale & AUTOSCALE_MIN)
27272723
scaling_axis->min = VERYLARGE;
27282724
if (scaling_axis->autoscale & AUTOSCALE_MAX)
@@ -2962,18 +2958,25 @@ eval_plots()
29622958
axis_array[FIRST_X_AXIS].max = 10;
29632959
}
29642960

2965-
/* check that xmin -> xmax is not too small */
2966-
axis_checked_extend_empty_range(FIRST_X_AXIS, "x range is invalid");
2961+
if (uses_axis[FIRST_X_AXIS] & USES_AXIS_FOR_DATA) {
2962+
/* check that x1min -> x1max is not too small */
2963+
axis_checked_extend_empty_range(FIRST_X_AXIS, "x range is invalid");
2964+
}
29672965

29682966
if (uses_axis[SECOND_X_AXIS] & USES_AXIS_FOR_DATA) {
29692967
/* check that x2min -> x2max is not too small */
29702968
axis_checked_extend_empty_range(SECOND_X_AXIS, "x2 range is invalid");
29712969
} else if (axis_array[SECOND_X_AXIS].autoscale) {
29722970
/* copy x1's range */
2973-
if (axis_array[SECOND_X_AXIS].autoscale & AUTOSCALE_MIN)
2974-
axis_array[SECOND_X_AXIS].min = axis_array[FIRST_X_AXIS].min;
2975-
if (axis_array[SECOND_X_AXIS].autoscale & AUTOSCALE_MAX)
2976-
axis_array[SECOND_X_AXIS].max = axis_array[FIRST_X_AXIS].max;
2971+
/* FIXME: merge both cases into update_secondary_axis_range */
2972+
if (axis_array[SECOND_X_AXIS].linked_to_primary) {
2973+
update_secondary_axis_range(&axis_array[FIRST_X_AXIS]);
2974+
} else {
2975+
if (axis_array[SECOND_X_AXIS].autoscale & AUTOSCALE_MIN)
2976+
axis_array[SECOND_X_AXIS].min = axis_array[FIRST_X_AXIS].min;
2977+
if (axis_array[SECOND_X_AXIS].autoscale & AUTOSCALE_MAX)
2978+
axis_array[SECOND_X_AXIS].max = axis_array[FIRST_X_AXIS].max;
2979+
}
29772980
}
29782981
}
29792982
if (some_functions) {
@@ -3297,40 +3300,45 @@ eval_plots()
32973300
if (first_plot->plot_type == NODATA)
32983301
int_error(NO_CARET,"No data in plot");
32993302

3303+
/* gnuplot version 5.0 always used x1 to track autoscaled range
3304+
* regardless of whether x1 or x2 was used to plot the data.
3305+
* In version 5.2 we track the x1/x2 axis data limits separately.
3306+
* However if x1 and x2 are linked to each other we must now
3307+
* reconcile their data limits before plotting.
3308+
*/
3309+
if (uses_axis[FIRST_X_AXIS] && uses_axis[SECOND_X_AXIS]) {
3310+
AXIS *primary = &axis_array[FIRST_X_AXIS];
3311+
AXIS *secondary = &axis_array[SECOND_X_AXIS];
3312+
reconcile_linked_axes(primary, secondary);
3313+
}
3314+
if (uses_axis[FIRST_Y_AXIS] && uses_axis[SECOND_Y_AXIS]) {
3315+
AXIS *primary = &axis_array[FIRST_Y_AXIS];
3316+
AXIS *secondary = &axis_array[SECOND_Y_AXIS];
3317+
reconcile_linked_axes(primary, secondary);
3318+
}
3319+
33003320
if (uses_axis[FIRST_X_AXIS]) {
33013321
if (axis_array[FIRST_X_AXIS].max == -VERYLARGE ||
33023322
axis_array[FIRST_X_AXIS].min == VERYLARGE)
33033323
int_error(NO_CARET, "all points undefined!");
3304-
axis_check_range(FIRST_X_AXIS);
3324+
} else {
3325+
assert(uses_axis[SECOND_X_AXIS]);
33053326
}
33063327
if (uses_axis[SECOND_X_AXIS]) {
33073328
if (axis_array[SECOND_X_AXIS].max == -VERYLARGE ||
33083329
axis_array[SECOND_X_AXIS].min == VERYLARGE)
33093330
int_error(NO_CARET, "all points undefined!");
3310-
axis_check_range(SECOND_X_AXIS);
33113331
} else {
3312-
/* FIXME: If this triggers, doesn't it clobber linked axes? */
33133332
assert(uses_axis[FIRST_X_AXIS]);
3314-
if (axis_array[SECOND_X_AXIS].autoscale & AUTOSCALE_MIN)
3315-
axis_array[SECOND_X_AXIS].min = axis_array[FIRST_X_AXIS].min;
3316-
if (axis_array[SECOND_X_AXIS].autoscale & AUTOSCALE_MAX)
3317-
axis_array[SECOND_X_AXIS].max = axis_array[FIRST_X_AXIS].max;
3318-
if (! axis_array[SECOND_X_AXIS].autoscale)
3319-
axis_check_range(SECOND_X_AXIS);
3320-
}
3321-
if (! uses_axis[FIRST_X_AXIS]) {
3322-
assert(uses_axis[SECOND_X_AXIS]);
3323-
if (axis_array[FIRST_X_AXIS].autoscale & AUTOSCALE_MIN)
3324-
axis_array[FIRST_X_AXIS].min = axis_array[SECOND_X_AXIS].min;
3325-
if (axis_array[FIRST_X_AXIS].autoscale & AUTOSCALE_MAX)
3326-
axis_array[FIRST_X_AXIS].max = axis_array[SECOND_X_AXIS].max;
33273333
}
3334+
axis_check_range(FIRST_X_AXIS);
3335+
axis_check_range(SECOND_X_AXIS);
33283336

3337+
/* For nonlinear axes, but must also be compatible with "set link x". */
33293338
/* min/max values were tracked during input for the visible axes. */
33303339
/* Now we use them to update the corresponding shadow (nonlinear) ones. */
3331-
update_primary_axis_range(&axis_array[FIRST_X_AXIS]);
3332-
update_primary_axis_range(&axis_array[SECOND_X_AXIS]);
3333-
3340+
update_primary_axis_range(&axis_array[FIRST_X_AXIS]);
3341+
update_primary_axis_range(&axis_array[SECOND_X_AXIS]);
33343342

33353343
if (this_plot && this_plot->plot_style == TABLESTYLE) {
33363344
/* the y axis range has no meaning in this case */
@@ -3347,18 +3355,16 @@ eval_plots()
33473355
axis_checked_extend_empty_range(SECOND_Y_AXIS, "all points y2 value undefined!");
33483356
axis_check_range(SECOND_Y_AXIS);
33493357
} else {
3350-
/* else we want to copy y2 range */
33513358
assert(uses_axis[FIRST_Y_AXIS]);
33523359
if (axis_array[SECOND_Y_AXIS].autoscale & AUTOSCALE_MIN)
33533360
axis_array[SECOND_Y_AXIS].min = axis_array[FIRST_Y_AXIS].min;
33543361
if (axis_array[SECOND_Y_AXIS].autoscale & AUTOSCALE_MAX)
33553362
axis_array[SECOND_Y_AXIS].max = axis_array[FIRST_Y_AXIS].max;
3356-
/* Log() fixup is only necessary if the range was *not* copied from
3357-
* the (already logarithmized) yrange */
33583363
if (! axis_array[SECOND_Y_AXIS].autoscale)
33593364
axis_check_range(SECOND_Y_AXIS);
33603365
}
33613366
if (! uses_axis[FIRST_Y_AXIS]) {
3367+
/* FIXME: probably unneeded or wrong since adding reconcile_linked_axes */
33623368
assert(uses_axis[SECOND_Y_AXIS]);
33633369
if (axis_array[FIRST_Y_AXIS].autoscale & AUTOSCALE_MIN)
33643370
axis_array[FIRST_Y_AXIS].min = axis_array[SECOND_Y_AXIS].min;
@@ -3640,3 +3646,4 @@ reevaluate_plot_title(struct curve_points *this_plot)
36403646
}
36413647
}
36423648
}
3649+

0 commit comments

Comments
 (0)