Skip to content

Commit

Permalink
for autoscaling, merge data ranges plotted to both ends of a linked a…
Browse files Browse the repository at this point in the history
…xis pair
  • Loading branch information
sfeam committed Oct 9, 2017
1 parent 347d8bd commit 3fe48c0
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 39 deletions.
17 changes: 17 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
2017-10-08 Ethan A Merritt <[email protected]>

* src/axis.c (reconcile_linked_axes) src/axis.h src/plot2d.c:
gnuplot version 5.0 always tracked the autoscale range on the primary
axis (x1 or y1) of a linked pair, even if the data was actually plotted
on x2 or y2. In verison 5.2 we track the data range on x1 x2 y1 y2
separately. This caused breakage wherever the program assumed the
autoscale range on x1 (or y1) was always current. Worse, it would
propagate that range onto the secondary axis, possibly overwriting the
correct range. Now we introduce a new routine reconcile_linked_axes()
that merges the min/max values from e.g. x1 and x2 so that the
autoscaled range covers input data plotted on either axis.
Bug #1973

* demo/linked_autoscale.dem: Exercise the new code (fails on previous
gnuplot versions).

2017-10-06 Hans-Bernhard Broeker <[email protected]>

* src/command.c: Move WEXITSTATUS fall-back definition away from here.
Expand Down
68 changes: 68 additions & 0 deletions demo/linked_autoscale.dem
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#
# Exercise combinations of linked axes and autoscaled data
# Some of these produced errors or incorrect plots in previous
# gnuplot versions (5.0 - 5.2) E.g. But #1973
#
set samples 5
set x2tics 1
set tics nomirror
set auto noextend
set linetype 1 ps 2
set key Left left samplen .01
unset link x

set multiplot layout 3,1
set title 'axes x1y1'
plot sample [i=1:5] '+' using (i):(i) axes x1y1 with lp title 'no link'

set link x2
plot sample [i=1:5] '+' using (i):(i) axes x1y1 with lp title 'set link x2'

set link x2 via x*2 inv x/2
plot sample [i=1:5] '+' using (i):(i) axes x1y1 with lp title 'set link x2 via x*2 inv x/2'
unset multiplot
pause -1 "Hit return to continue"

set multiplot layout 3,1
unset link x2
set title 'axes x2y1'
plot sample [i=1:5] '+' using (i):(i) axes x2y1 with lp title 'no link'

set link x2
plot sample [i=1:5] '+' using (i):(i) axes x2y1 with lp title 'set link x2'

set link x2 via x*2 inv x/2
plot sample [i=1:5] '+' using (i):(i) axes x2y1 with lp title 'set link x2 via x*2 inv x/2'
unset multiplot
pause -1 "Hit return to continue"

#
# More problem cases
#
reset
set x2tics
set link x2
set key Left left samplen .01
set multiplot layout 1,2
plot 'silver.dat' using 2:1 axes x1y1 with lp, '' using 3:1 with lp axes x2y1
plot 'silver.dat' using 2:1 axes x2y1 with lp, '' using 3:1 with lp axes x1y1
unset multiplot
pause -1 "Hit return to continue"

reset
set link y
set y2tics
set key Left left samplen .01
plot 'silver.dat' using 1:2 axes x1y1, '' using 1:3 axes x1y2
pause -1 "Hit return to continue"

#
# This is a sampling bug rather than an autoscale bug
#
set title "Should be 5 samples but may only get 3"
set key title 'plot sample [i=1:5:1] "+" using 1:1 axes x2y1'
set link x2 via x*2 inv x/2
set x2tics
plot sample [i=1:5:1] '+' using 1:1 axes x2y1 with lp title 'set link x2 via x*2 inv x/2'
pause -1 "Hit return to continue"
reset
54 changes: 50 additions & 4 deletions src/axis.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#ifndef lint
static char *RCSid() { return RCSid("$Id: axis.c,v 1.247 2017-09-29 19:02:23 sfeam Exp $"); }
static char *RCSid() { return RCSid("$Id: axis.c,v 1.248 2017-10-09 00:49:03 sfeam Exp $"); }
#endif

/* GNUPLOT - axis.c */
Expand Down Expand Up @@ -2487,22 +2487,68 @@ extend_primary_ticrange(AXIS *axis)

/*
* As data is read in or functions evaluated, the min/max values are tracked
* for the visible axes but not for the primary (linear) axes.
* This routine fills in the primary min/max from the linked secondary axis.
* for the secondary (visible) axes but not for the linked primary (linear) axis.
* This routine fills in the primary min/max from the secondary axis.
*/
void
update_primary_axis_range(struct axis *secondary)
{
struct axis *primary = secondary->linked_to_primary;

if (nonlinear(secondary)) {
if (primary != NULL) {
/* nonlinear axis (secondary is visible; primary is hidden) */
primary->min = eval_link_function(primary, secondary->min);
primary->max = eval_link_function(primary, secondary->max);
primary->data_min = eval_link_function(primary, secondary->data_min);
primary->data_max = eval_link_function(primary, secondary->data_max);
}
}

/*
* Same thing but in the opposite direction. We read in data on the primary axis
* and want the autoscaling on a linked secondary axis to match.
*/
void
update_secondary_axis_range(struct axis *primary)
{
struct axis *secondary = primary->linked_to_secondary;

if (secondary != NULL) {
secondary->min = eval_link_function(secondary, primary->min);
secondary->max = eval_link_function(secondary, primary->max);
secondary->data_min = eval_link_function(secondary, primary->data_min);
secondary->data_max = eval_link_function(secondary, primary->data_max);
}
}

/*
* gnuplot version 5.0 always maintained autoscaled range on x1
* specifically, transforming from x2 coordinates if necessary.
* In version 5.2 we track the x1 and x2 axis data limits separately.
* However if x1 and x2 are linked to each other we must reconcile
* their data limits before plotting.
*/
void
reconcile_linked_axes(AXIS *primary, AXIS *secondary)
{
double dummy;
int inrange = INRANGE;
if ((primary->autoscale & AUTOSCALE_BOTH) != AUTOSCALE_NONE
&& primary->linked_to_secondary) {
double min_2_into_1 = eval_link_function(primary, secondary->data_min);
double max_2_into_1 = eval_link_function(primary, secondary->data_max);

/* Merge secondary min/max into primary data range */
ACTUAL_STORE_AND_UPDATE_RANGE(dummy, min_2_into_1, inrange, primary, FALSE, NOOP);
ACTUAL_STORE_AND_UPDATE_RANGE(dummy, max_2_into_1, inrange, primary, FALSE, NOOP);
(void)dummy; /* Otherwise the compiler complains about an unused variable */

/* Take the result back the other way to update secondary */
secondary->min = eval_link_function(secondary, primary->min);
secondary->max = eval_link_function(secondary, primary->max);
}
}


/*
* Check for linked-axis coordinate transformation given by command
Expand Down
4 changes: 3 additions & 1 deletion src/axis.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* $Id: axis.h,v 1.179 2017-09-29 19:02:23 sfeam Exp $
* $Id: axis.h,v 1.180 2017-10-09 00:49:03 sfeam Exp $
*
*/

Expand Down Expand Up @@ -595,6 +595,8 @@ void clone_linked_axes __PROTO((AXIS *axis1, AXIS *axis2));
AXIS *get_shadow_axis __PROTO((AXIS *axis));
void extend_primary_ticrange __PROTO((AXIS *axis));
void update_primary_axis_range __PROTO((struct axis *secondary));
void update_secondary_axis_range __PROTO((struct axis *primary));
void reconcile_linked_axes __PROTO((AXIS *primary, AXIS *secondary));

int map_x __PROTO((double value));
int map_y __PROTO((double value));
Expand Down
75 changes: 41 additions & 34 deletions src/plot2d.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#ifndef lint
static char *RCSid() { return RCSid("$Id: plot2d.c,v 1.459 2017-09-29 19:23:37 sfeam Exp $"); }
static char *RCSid() { return RCSid("$Id: plot2d.c,v 1.460 2017-10-09 00:49:03 sfeam Exp $"); }
#endif

/* GNUPLOT - plot2d.c */
Expand Down Expand Up @@ -2718,11 +2718,7 @@ eval_plots()

/* Reset flags to auto-scale X axis to contents of data set */
if (!(uses_axis[x_axis] & USES_AXIS_FOR_DATA) && X_AXIS.autoscale) {
struct axis *scaling_axis;
if (x_axis == SECOND_X_AXIS && !X_AXIS.linked_to_primary)
scaling_axis = &axis_array[SECOND_X_AXIS];
else
scaling_axis = &axis_array[FIRST_X_AXIS];
struct axis *scaling_axis = &axis_array[this_plot->x_axis];
if (scaling_axis->autoscale & AUTOSCALE_MIN)
scaling_axis->min = VERYLARGE;
if (scaling_axis->autoscale & AUTOSCALE_MAX)
Expand Down Expand Up @@ -2962,18 +2958,25 @@ eval_plots()
axis_array[FIRST_X_AXIS].max = 10;
}

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

if (uses_axis[SECOND_X_AXIS] & USES_AXIS_FOR_DATA) {
/* check that x2min -> x2max is not too small */
axis_checked_extend_empty_range(SECOND_X_AXIS, "x2 range is invalid");
} else if (axis_array[SECOND_X_AXIS].autoscale) {
/* copy x1's range */
if (axis_array[SECOND_X_AXIS].autoscale & AUTOSCALE_MIN)
axis_array[SECOND_X_AXIS].min = axis_array[FIRST_X_AXIS].min;
if (axis_array[SECOND_X_AXIS].autoscale & AUTOSCALE_MAX)
axis_array[SECOND_X_AXIS].max = axis_array[FIRST_X_AXIS].max;
/* FIXME: merge both cases into update_secondary_axis_range */
if (axis_array[SECOND_X_AXIS].linked_to_primary) {
update_secondary_axis_range(&axis_array[FIRST_X_AXIS]);
} else {
if (axis_array[SECOND_X_AXIS].autoscale & AUTOSCALE_MIN)
axis_array[SECOND_X_AXIS].min = axis_array[FIRST_X_AXIS].min;
if (axis_array[SECOND_X_AXIS].autoscale & AUTOSCALE_MAX)
axis_array[SECOND_X_AXIS].max = axis_array[FIRST_X_AXIS].max;
}
}
}
if (some_functions) {
Expand Down Expand Up @@ -3297,40 +3300,45 @@ eval_plots()
if (first_plot->plot_type == NODATA)
int_error(NO_CARET,"No data in plot");

/* gnuplot version 5.0 always used x1 to track autoscaled range
* regardless of whether x1 or x2 was used to plot the data.
* In version 5.2 we track the x1/x2 axis data limits separately.
* However if x1 and x2 are linked to each other we must now
* reconcile their data limits before plotting.
*/
if (uses_axis[FIRST_X_AXIS] && uses_axis[SECOND_X_AXIS]) {
AXIS *primary = &axis_array[FIRST_X_AXIS];
AXIS *secondary = &axis_array[SECOND_X_AXIS];
reconcile_linked_axes(primary, secondary);
}
if (uses_axis[FIRST_Y_AXIS] && uses_axis[SECOND_Y_AXIS]) {
AXIS *primary = &axis_array[FIRST_Y_AXIS];
AXIS *secondary = &axis_array[SECOND_Y_AXIS];
reconcile_linked_axes(primary, secondary);
}

if (uses_axis[FIRST_X_AXIS]) {
if (axis_array[FIRST_X_AXIS].max == -VERYLARGE ||
axis_array[FIRST_X_AXIS].min == VERYLARGE)
int_error(NO_CARET, "all points undefined!");
axis_check_range(FIRST_X_AXIS);
} else {
assert(uses_axis[SECOND_X_AXIS]);
}
if (uses_axis[SECOND_X_AXIS]) {
if (axis_array[SECOND_X_AXIS].max == -VERYLARGE ||
axis_array[SECOND_X_AXIS].min == VERYLARGE)
int_error(NO_CARET, "all points undefined!");
axis_check_range(SECOND_X_AXIS);
} else {
/* FIXME: If this triggers, doesn't it clobber linked axes? */
assert(uses_axis[FIRST_X_AXIS]);
if (axis_array[SECOND_X_AXIS].autoscale & AUTOSCALE_MIN)
axis_array[SECOND_X_AXIS].min = axis_array[FIRST_X_AXIS].min;
if (axis_array[SECOND_X_AXIS].autoscale & AUTOSCALE_MAX)
axis_array[SECOND_X_AXIS].max = axis_array[FIRST_X_AXIS].max;
if (! axis_array[SECOND_X_AXIS].autoscale)
axis_check_range(SECOND_X_AXIS);
}
if (! uses_axis[FIRST_X_AXIS]) {
assert(uses_axis[SECOND_X_AXIS]);
if (axis_array[FIRST_X_AXIS].autoscale & AUTOSCALE_MIN)
axis_array[FIRST_X_AXIS].min = axis_array[SECOND_X_AXIS].min;
if (axis_array[FIRST_X_AXIS].autoscale & AUTOSCALE_MAX)
axis_array[FIRST_X_AXIS].max = axis_array[SECOND_X_AXIS].max;
}
axis_check_range(FIRST_X_AXIS);
axis_check_range(SECOND_X_AXIS);

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

update_primary_axis_range(&axis_array[FIRST_X_AXIS]);
update_primary_axis_range(&axis_array[SECOND_X_AXIS]);

if (this_plot && this_plot->plot_style == TABLESTYLE) {
/* the y axis range has no meaning in this case */
Expand All @@ -3347,18 +3355,16 @@ eval_plots()
axis_checked_extend_empty_range(SECOND_Y_AXIS, "all points y2 value undefined!");
axis_check_range(SECOND_Y_AXIS);
} else {
/* else we want to copy y2 range */
assert(uses_axis[FIRST_Y_AXIS]);
if (axis_array[SECOND_Y_AXIS].autoscale & AUTOSCALE_MIN)
axis_array[SECOND_Y_AXIS].min = axis_array[FIRST_Y_AXIS].min;
if (axis_array[SECOND_Y_AXIS].autoscale & AUTOSCALE_MAX)
axis_array[SECOND_Y_AXIS].max = axis_array[FIRST_Y_AXIS].max;
/* Log() fixup is only necessary if the range was *not* copied from
* the (already logarithmized) yrange */
if (! axis_array[SECOND_Y_AXIS].autoscale)
axis_check_range(SECOND_Y_AXIS);
}
if (! uses_axis[FIRST_Y_AXIS]) {
/* FIXME: probably unneeded or wrong since adding reconcile_linked_axes */
assert(uses_axis[SECOND_Y_AXIS]);
if (axis_array[FIRST_Y_AXIS].autoscale & AUTOSCALE_MIN)
axis_array[FIRST_Y_AXIS].min = axis_array[SECOND_Y_AXIS].min;
Expand Down Expand Up @@ -3640,3 +3646,4 @@ reevaluate_plot_title(struct curve_points *this_plot)
}
}
}

0 comments on commit 3fe48c0

Please sign in to comment.