Skip to content

Commit d536a47

Browse files
committed
Make it possible to set custom tick positions and labels with the plots.pl macro.
To set custom tick positions use the `tick_positions` axis option. Set that to a reference to an array containing the positions on the axis that ticks are desired. For example, `tick_positions => [ 2, 5, 9 ]` will place ticks at positions 2, 5, and 9 on the axis. Note that when this option is used the `tick_delta`, `tick_scale`, and `tick_distance` options are not used. So only the given tick positions will appear in the graph. To set custom tick labels use the `tick_labels` option. Note that this is not a new option, but now it accepts a new type of value. Previously this was purely boolean (0 or 1), and it only determined if tick labels would be shown or not. Now it can take a value that is a reference to a hash. The keys of the hash are tick positions, and the values are the labels to be placed at those positons. Note that formatting of the label must be done by the auther, and the `tick_label_format` option is ignored for any label provided in this hash. If a major tick is not listed in the hash, then the position will be used for the label and it will be formatted according to the `tick_label_format` option. This is intended to replace what is done in #1374 and is a more flexible approach than what is done there. In that pull request the capability for custom tick labels only is added, and it is extremely restrictive in what it can do. Only positive tick labels can be customized, and it requires that the problem author label all major ticks (there is no fallback and a tick is labeled "undefined" if one is missing).
1 parent 9766e9f commit d536a47

4 files changed

Lines changed: 116 additions & 31 deletions

File tree

htdocs/js/Plots/plots.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,18 @@ const PGplots = {
8686
}
8787
},
8888

89-
// Override the default axis generateLabelText method so that 0 is displayed
90-
// using MathJax if the axis is configured to show tick labels using MathJax.
89+
// Override the default axis generateLabelText method to show custom tick labels if they are set, and so
90+
// that 0 is displayed using MathJax if the axis is configured to show tick labels using MathJax.
9191
generateLabelText(tick, zero, value) {
92+
for (const axis of ['xAxis', 'yAxis']) {
93+
if (
94+
this === plot[axis]?.defaultTicks &&
95+
typeof options[axis]?.ticks?.labels === 'object' &&
96+
tick.usrCoords[1] in options[axis].ticks.labels
97+
) {
98+
return options[axis].ticks.labels[tick.usrCoords[1]];
99+
}
100+
}
92101
if (JXG.exists(value)) return this.formatLabelText(value);
93102
const distance = this.getDistanceFromZero(zero, tick);
94103
return this.formatLabelText(Math.abs(distance) < JXG.Math.eps ? 0 : distance / this.visProp.scale);
@@ -216,7 +225,7 @@ const PGplots = {
216225
strokeColor: options.grid.color ?? '#808080',
217226
strokeOpacity: options.grid.opacity ?? 0.2,
218227
insertTicks: false,
219-
ticksDistance: options.xAxis?.ticks?.distance ?? 2,
228+
ticksDistance: options.xAxis.ticks?.positions ?? options.xAxis.ticks?.distance ?? 2,
220229
scale: options.xAxis?.ticks?.scale ?? 1,
221230
minorTicks: options.grid.x.minorGrids ? (options.xAxis?.ticks?.minorTicks ?? 3) : 0,
222231
ignoreInfiniteTickEndings: false,
@@ -280,7 +289,7 @@ const PGplots = {
280289
strokeColor: options.grid.color ?? '#808080',
281290
strokeOpacity: options.grid.opacity ?? 0.2,
282291
insertTicks: false,
283-
ticksDistance: options.yAxis?.ticks?.distance ?? 2,
292+
ticksDistance: options.yAxis.ticks?.positions ?? options.yAxis.ticks?.distance ?? 2,
284293
scale: options.yAxis?.ticks?.scale ?? 1,
285294
minorTicks: options.grid.y.minorGrids ? (options.yAxis?.ticks?.minorTicks ?? 3) : 0,
286295
ignoreInfiniteTickEndings: false,
@@ -352,7 +361,7 @@ const PGplots = {
352361
? true
353362
: false,
354363
insertTicks: false,
355-
ticksDistance: options.xAxis.ticks?.distance ?? 2,
364+
ticksDistance: options.xAxis.ticks?.positions ?? options.xAxis.ticks?.distance ?? 2,
356365
scale: options.xAxis.ticks?.scale ?? 1,
357366
scaleSymbol: options.xAxis.ticks?.scaleSymbol ?? '',
358367
minorTicks: options.xAxis.ticks?.minorTicks ?? 3,
@@ -451,7 +460,7 @@ const PGplots = {
451460
? true
452461
: false,
453462
insertTicks: false,
454-
ticksDistance: options.yAxis.ticks?.distance ?? 2,
463+
ticksDistance: options.yAxis.ticks?.positions ?? options.yAxis.ticks?.distance ?? 2,
455464
scale: options.yAxis.ticks?.scale ?? 1,
456465
scaleSymbol: options.yAxis.ticks?.scaleSymbol ?? '',
457466
minorTicks: options.yAxis.ticks?.minorTicks ?? 3,

lib/Plots/Axes.pm

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,37 @@ difference between the C<max> and C<min> divided by the C<tick_num>. Default: 0
100100
101101
=item tick_labels
102102
103-
This can be either 1 (show) or 0 (don't show) the labels for the major ticks.
103+
This can be set to 1 to show the labels for the major ticks, 0 to not show the
104+
labels for the major ticks, or to a reference to a hash whose keys are tick
105+
positions, and whose values are tick labels to be shown at those positions.
104106
Default: 1
105107
108+
The following is an example of passing a reference to a hash for this option.
109+
110+
tick_labels => { 5 => '\(a\)' }
111+
112+
In this case if there is a major tick at 5, then the label that will be shown
113+
there is 'a' and the label will be rendered via MathJax. Note that if there is
114+
not a major tick at 5, then the label will be unused. At any other major tick
115+
that is shown, the position will be shown for the label and will be formatted
116+
according to the C<tick_label_format> option.
117+
118+
This option is most useful in combination with the C<tick_positions> option
119+
below. With that option the precise list of major ticks to be shown can be
120+
specified, and the labels for those ticks specified with this option. For
121+
example,
122+
123+
tick_positions => [2, 4],
124+
tick_labels => { 2 => 'a', 4 => 'b' },
125+
126+
would place a tick at 2 labeled 'a', and a tick at 4 labeled 'b'. No other ticks
127+
would be shown on the axis.
128+
129+
Note that if the hash reference value is used, the C<tick_label_format> does not
130+
apply. You are responsible for formatting the labels as you would like them to
131+
appear. If you want the labels rendered via MathJax, then wrap the labels in
132+
C<\(> and C<\)>.
133+
106134
=item tick_label_format
107135
108136
This can be one of "decimal", "fraction", "multiple", or "scinot". If this is
@@ -133,6 +161,15 @@ C<tick_delta>. Default: 1
133161
134162
This is appended to major tick labels. Default: ''
135163
164+
=item tick_positions
165+
166+
Set this to a reference to an array of values to be used for the positions of
167+
ticks that will be shown on the axis. In this case the C<tick_delta>,
168+
C<tick_distance>, and C<tick_scale> options will not be used to generate tick
169+
positions. If this is set to 0 (or is not an array reference), then the tick
170+
positions will be computed using the values of the C<tick_delta>,
171+
C<tick_distance>, and C<tick_scale> options. Default: 0
172+
136173
=item show_ticks
137174
138175
This can be either 1 (show) or 0 (don't show) the tick lines. If ticks are
@@ -316,6 +353,7 @@ sub axis_defaults {
316353
tick_distance => 0,
317354
tick_scale => 1,
318355
tick_scale_symbol => '',
356+
tick_positions => 0,
319357
show_ticks => 1,
320358
tick_delta => 0,
321359
tick_num => 5,

lib/Plots/JSXGraph.pm

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ sub HTML {
9090
$options->{xAxis}{ticks}{scale} = $axes->xaxis('tick_scale');
9191
$options->{xAxis}{ticks}{distance} = $axes->xaxis('tick_distance');
9292
$options->{xAxis}{ticks}{minorTicks} = $grid->{xminor};
93+
94+
my $xticks = $axes->xaxis('tick_positions');
95+
$options->{xAxis}{ticks}{positions} = $xticks if ref $xticks eq 'ARRAY';
9396
}
9497

9598
$options->{yAxis}{visible} = $yvisible;
@@ -100,6 +103,9 @@ sub HTML {
100103
$options->{yAxis}{ticks}{scale} = $axes->yaxis('tick_scale');
101104
$options->{yAxis}{ticks}{distance} = $axes->yaxis('tick_distance');
102105
$options->{yAxis}{ticks}{minorTicks} = $grid->{yminor};
106+
107+
my $yticks = $axes->yaxis('tick_positions');
108+
$options->{yAxis}{ticks}{positions} = $yticks if ref $yticks eq 'ARRAY';
103109
}
104110

105111
if ($show_grid) {

lib/Plots/Tikz.pm

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -213,25 +213,41 @@ sub generate_axes {
213213
my $x_tick_distance = $axes->xaxis('tick_distance');
214214
my $x_tick_scale = $axes->xaxis('tick_scale') || 1;
215215

216-
my @xticks =
217-
grep { $_ > $xmin && $_ < $xmax }
218-
map { -$_ * $x_tick_distance * $x_tick_scale }
219-
reverse(1 .. -$xmin / ($x_tick_distance * $x_tick_scale));
220-
push(@xticks, 0) if $xmin < 0 && $xmax > 0;
221-
push(@xticks,
222-
grep { $_ > $xmin && $_ < $xmax }
223-
map { $_ * $x_tick_distance * $x_tick_scale } (1 .. $xmax / ($x_tick_distance * $x_tick_scale)));
216+
my $xtick_positions = $axes->xaxis('tick_positions');
217+
my @xticks;
218+
if (ref $xtick_positions eq 'ARRAY') {
219+
@xticks = @$xtick_positions;
220+
} else {
221+
@xticks =
222+
grep { $_ > $xmin && $_ < $xmax }
223+
map { -$_ * $x_tick_distance * $x_tick_scale }
224+
reverse(1 .. -$xmin / ($x_tick_distance * $x_tick_scale));
225+
push(@xticks, 0) if $xmin < 0 && $xmax > 0;
226+
push(@xticks,
227+
grep { $_ > $xmin && $_ < $xmax }
228+
map { $_ * $x_tick_distance * $x_tick_scale } (1 .. $xmax / ($x_tick_distance * $x_tick_scale)));
229+
}
224230

231+
my $xtick_labels_value = $axes->xaxis('tick_labels');
225232
my $xtick_labels =
226233
$xvisible
227234
&& $axes->xaxis('show_ticks')
228-
&& $axes->xaxis('tick_labels')
229-
? (",\nxticklabel shift=9pt,\nxticklabel style={anchor=center},\nxticklabels={"
230-
. join(',', map { $self->formatTickLabelText($_ / $x_tick_scale, 'xaxis') } @xticks) . '}')
235+
&& $xtick_labels_value
236+
? (
237+
",\nxticklabel shift=9pt,\nxticklabel style={anchor=center},\nxticklabels={" . (join(
238+
',',
239+
map {
240+
ref $xtick_labels_value eq 'HASH' && defined $xtick_labels_value->{$_}
241+
? $xtick_labels_value->{$_}
242+
: $self->formatTickLabelText($_ / $x_tick_scale, 'xaxis')
243+
} @xticks
244+
))
245+
. '}'
246+
)
231247
: ",\nxticklabel=\\empty";
232248

233249
my @xminor_ticks;
234-
if ($grid->{xminor} > 0) {
250+
if ($grid->{xminor} > 0 && ref $xtick_positions ne 'ARRAY') {
235251
my @majorTicks = @xticks;
236252
unshift(@majorTicks, ($majorTicks[0] // $xmin) - $x_tick_distance * $x_tick_scale);
237253
push(@majorTicks, ($majorTicks[-1] // $xmax) + $x_tick_distance * $x_tick_scale);
@@ -246,25 +262,41 @@ sub generate_axes {
246262
my $y_tick_distance = $axes->yaxis('tick_distance');
247263
my $y_tick_scale = $axes->yaxis('tick_scale') || 1;
248264

249-
my @yticks =
250-
grep { $_ > $ymin && $_ < $ymax }
251-
map { -$_ * $y_tick_distance * $y_tick_scale }
252-
reverse(1 .. -$ymin / ($y_tick_distance * $y_tick_scale));
253-
push(@yticks, 0) if $ymin < 0 && $ymax > 0;
254-
push(@yticks,
255-
grep { $_ > $ymin && $_ < $ymax }
256-
map { $_ * $y_tick_distance * $y_tick_scale } (1 .. $ymax / ($y_tick_distance * $y_tick_scale)));
265+
my $ytick_positions = $axes->yaxis('tick_positions');
266+
my @yticks;
267+
if (ref $ytick_positions eq 'ARRAY') {
268+
@yticks = @$ytick_positions;
269+
} else {
270+
@yticks =
271+
grep { $_ > $ymin && $_ < $ymax }
272+
map { -$_ * $y_tick_distance * $y_tick_scale }
273+
reverse(1 .. -$ymin / ($y_tick_distance * $y_tick_scale));
274+
push(@yticks, 0) if $ymin < 0 && $ymax > 0;
275+
push(@yticks,
276+
grep { $_ > $ymin && $_ < $ymax }
277+
map { $_ * $y_tick_distance * $y_tick_scale } (1 .. $ymax / ($y_tick_distance * $y_tick_scale)));
278+
}
257279

280+
my $ytick_labels_value = $axes->yaxis('tick_labels');
258281
my $ytick_labels =
259282
$yvisible
260283
&& $axes->yaxis('show_ticks')
261-
&& $axes->yaxis('tick_labels')
262-
? (",\nyticklabel shift=-3pt,\nyticklabels={"
263-
. join(',', map { $self->formatTickLabelText($_ / $y_tick_scale, 'yaxis') } @yticks) . '}')
284+
&& $ytick_labels_value
285+
? (
286+
",\nyticklabel shift=-3pt,\nyticklabel style={anchor=left},\nyticklabels={" . (join(
287+
',',
288+
map {
289+
ref $ytick_labels_value eq 'HASH' && defined $ytick_labels_value->{$_}
290+
? $ytick_labels_value->{$_}
291+
: $self->formatTickLabelText($_ / $y_tick_scale, 'yaxis')
292+
} @yticks
293+
))
294+
. '}'
295+
)
264296
: ",\nyticklabel=\\empty";
265297

266298
my @yminor_ticks;
267-
if ($grid->{yminor} > 0) {
299+
if ($grid->{yminor} > 0 && ref $ytick_positions ne 'ARRAY') {
268300
my @majorTicks = @yticks;
269301
unshift(@majorTicks, ($majorTicks[0] // $ymin) - $y_tick_distance * $y_tick_scale);
270302
push(@majorTicks, ($majorTicks[-1] // $ymax) + $y_tick_distance * $y_tick_scale);

0 commit comments

Comments
 (0)