Skip to content

Commit

Permalink
Accept a full sampling range specification in plot with out the "samp…
Browse files Browse the repository at this point in the history
…le" keyword

Background:
The plot command accepts axis ranges as the first thing after "plot".
If present, they update the primary axis ranges x y x2 y2.
Since a sampling range of the form [t=min:max] looks just like an axis
range with a renamed axis variable name this introduced a window for
ambiguity.  To remove the ambiguity, version 5 had a keyword "sample"
that interposed between axis ranges (if any) and the first sampling range.

This patch:
If a second colon is found in the range, as in [t=min:max:increment], then
it is unambiguously a sampling range rather than a primary axis range.
In this case there is no ambiguity and the plot proceeds as if there had
been a preceeding "sample" keyword.  To make this more general, a empty
field following that second colon defaults to whatever would have happened
without the colon.  Usually this is increment=(max-min)/samples or
increment=1 depending on context.  So it is always possible to avoid using
the "sample" keyword by consistently including a full sampling range
including the second color.

v5:
  plot [x=min:max][y=min:max] sample [t=min:man:increment]   # OK
  plot [x=min:max][y=min:max][t=min:man:increment]   # not accepted
  plot [x=min:max][y=min:max][t=min:man]             # misinterpreted as x2 range
v6 after this patch:
  plot [x=min:max][y=min:max] sample [t=min:man:increment]   # OK
  plot [x=min:max][y=min:max][t=min:man:increment]   # OK, increment makes it clear
  plot [x=min:max][y=min:max][t=min:man]             # still misinterpreted as x2 range
  plot [x=min:max][y=min:max][t=min:man:]            # empty increment OK, uses default

Note:
  The recognition of full sampling ranges [min:max:incr] also applies to
  splot commands, but if you mix axis ranges and sampling ranges it can still
  get confused.  To be safe, always use a full sampling range with a second colon.

Bug #2666
  • Loading branch information
Ethan A Merritt committed Nov 4, 2023
1 parent 5044393 commit e00e588
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 25 deletions.
36 changes: 24 additions & 12 deletions docs/gnuplot.doc
Original file line number Diff line number Diff line change
Expand Up @@ -7871,7 +7871,7 @@ Ffigure_zerror
`splot` draws 2D projections of 3D surfaces and data.

Syntax:
plot {<ranges>} <plot-element> {, <plot-element>, <plot-element>}
plot {<axis-ranges>} <plot-element> {, <plot-element>, <plot-element>}

Each plot element consists of a definition, a function, or a data source
together with optional properties or modifiers:
Expand Down Expand Up @@ -9118,16 +9118,20 @@ Ffigure_smooth_path

An independent sampling range can be provided immediately before the '+'. As
in normal function plots, a name can be assigned to the independent variable.
If given for the first plot element, the sampling range specifier has to be
preceded by the `sample` keyword (see also `plot sampling`).

plot sample [beta=0:2*pi] '+' using (sin(beta)):(cos(beta)) with lines

Here is an example where the sampling interval (1.5) is given as part of the
sampling range. Samples will be generated at -3, -1.5, 0, 1.5, ..., 24.

plot $MYDATA, [t=-3:25:1.5] '+' using (t):(f(t))

A sampling range that immediately follows the `plot` or `splot` command might
be mistakenly parsed as an x-axis range. To prevent this ambiguity you can
either precede it with the `sample` keyword or provide a sampling interval,
possibly empty to use the default, as a third field of the sampling range.
See the example of such ambiguity in `plot sampling`.

plot sample [beta=0:2*pi] '+' using (sin(beta)):(cos(beta)) with lines
plot [beta=0:2*pi:] '+' using (sin(beta)):(cos(beta)) with lines

The pseudo-file '++' returns 2 columns of data forming a regular grid of [u,v]
coordinates with the number of points along u controlled by `set samples` and
the number of points along v controlled by `set isosamples`. You must set
Expand Down Expand Up @@ -9591,14 +9595,22 @@ Ffigure_smooth_path
set xrange [0:50]
plot [0:10] f(x), [10:20] g(x), [20:30] h(x)

To remove the ambiguity in the previous example, insert the keyword `sample`
to indicate that [0:10] is a sampling range applied to a single plot component
rather than a global x-axis range that would apply to the entire plot.
plot sample [0:10] f(x), [10:20] g(x), [20:30] h(x)
To remove the ambiguity in the previous example, either insert the keyword
`sample` to indicate that [0:10] is a sampling range rather than an axis range
or add a sampling increment field (following a second colon) in the range
specifier. This works because a full range specifier [min:max:increment]
cannot be mis-parsed as an axis range. If the increment field is empty
then the increment defaults to (min-max/samples), so all three variants
below produce the same result.
set samples 100
plot sample [0:10] f(x), [10:20] g(x), [20:30] h(x)
plot [0:10:0.1] f(x), [10:20] g(x), [20:30] h(x)
plot [0:10:] f(x), [10:20] g(x), [20:30] h(x)

This example shows one way of tracing out a helix in a 3D plot
set xrange [-2:2]; set yrange [-2:2]
splot sample [h=1:10] '+' using (cos(h)):(sin(h)):(h)
set angle degrees
splot [phi=1:720:2] '+' using (cos(phi)):(sin(phi)):(phi)

4 2D sampling (u and v axes)
?sampling 2D
Expand All @@ -9624,7 +9636,7 @@ Ffigure_smooth_path

The range specifiers for sampling on u and v can include an explicit sampling
interval to control the number and spacing of samples:
splot sample [u=30:70:1][v=0:50:5] '++' using 1:2:(func($1,$2))
splot [u=30:70:1][v=0:50:5] '++' using 1:2:(func($1,$2))

3 for loops in plot command
?commands plot for
Expand Down
21 changes: 19 additions & 2 deletions src/axis.c
Original file line number Diff line number Diff line change
Expand Up @@ -2368,15 +2368,18 @@ char *c, *cfmt;
/* Accepts a range of the form [MIN:MAX] or [var=MIN:MAX]
* Loads new limiting values into axis->min axis->max
* Returns
* >0 = token indexing the leading variable name [foo=min:max]
* 0 = no range spec present
* -1 = range spec with no attached variable name
* >0 = token indexing the attached variable name
* -1 = range spec with no leading variable name
* -2 = found a second colon, so it must be a sampling range
*/
int
parse_range(AXIS_INDEX axis)
{
struct axis *this_axis = &axis_array[axis];
int dummy_token = -1;
int starting_token = c_token;
struct axis saved_axis;

/* No range present */
if (!equals(c_token, "["))
Expand All @@ -2395,9 +2398,23 @@ parse_range(AXIS_INDEX axis)
c_token += 2;
}

/* Save entire axis state before loading range, so we can back out if necessary */
memcpy(&saved_axis, this_axis, sizeof(struct axis));

this_axis->autoscale = load_range(this_axis, &this_axis->min, &this_axis->max,
this_axis->autoscale);

/* A second colon is permitted only in a sampling range.
* If we see one now, back out from trying to interpret this as a primary axis.
*/
if (equals(c_token, ":")
&& !(axis == SAMPLE_AXIS || axis == T_AXIS || axis == U_AXIS || axis == V_AXIS)) {
c_token = starting_token;
/* restore original axis range */
memcpy(this_axis, &saved_axis, sizeof(struct axis));
return -2;
}

/* Nonlinear axis - find the linear range equivalent */
if (this_axis->linked_to_primary) {
AXIS *primary = this_axis->linked_to_primary;
Expand Down
29 changes: 18 additions & 11 deletions src/plot2d.c
Original file line number Diff line number Diff line change
Expand Up @@ -268,18 +268,25 @@ plotrequest()
parse_skip_range();
}

/* Range limits for the entire plot are optional but must be given */
/* in a fixed order. The keyword 'sample' terminates range parsing. */
if (parametric || polar) {
/* Axis range limits for the entire plot are optional but must be given
* in the fixed order x y x2 y2.
* A sampling range [var=start:end:increment] terminates parsing of axis
* range limits. The keyword 'sample' also terminates range parsing.
*/
if (parametric || polar)
dummy_token = parse_range(T_AXIS);
parse_range(FIRST_X_AXIS);
} else {
else
dummy_token = parse_range(FIRST_X_AXIS);
}
parse_range(FIRST_Y_AXIS);
parse_range(SECOND_X_AXIS);
parse_range(SECOND_Y_AXIS);
if (equals(c_token,"sample") && equals(c_token+1,"["))
#define SAMPLING_RANGE -2
if (dummy_token == SAMPLING_RANGE)
dummy_token = 0;
else if ((parse_range(FIRST_Y_AXIS) != SAMPLING_RANGE)
&& (parse_range(SECOND_X_AXIS) != SAMPLING_RANGE)
&& (parse_range(SECOND_Y_AXIS) != SAMPLING_RANGE))
; /* Nothing to do */
#undef SAMPLING_RANGE
/* FIXME: would like to deprecate the "sample" keyword altogether */
if (equals(c_token,"sample"))
c_token++;

/* Clear out any tick labels read from data files in previous plot */
Expand Down Expand Up @@ -3621,7 +3628,7 @@ eval_plots()
if (!parametric && !polar)
init_sample_range(axis_array + x_axis, FUNC);
sample_range_token = parse_range(SAMPLE_AXIS);
if (equals(sample_range_token, "u"))
if (sample_range_token > 0 && equals(sample_range_token, "u"))
parse_range(V_AXIS);
dummy_func = &(this_plot->plot_function);

Expand Down

0 comments on commit e00e588

Please sign in to comment.