Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions htdocs/js/Plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,21 @@ const PGplots = {
}
},

// Override the default axis generateLabelText method so that 0 is displayed
// using MathJax if the axis is configured to show tick labels using MathJax.
// Override the default axis generateLabelText method to show custom tick labels if they are set, and so
// that 0 is displayed using MathJax if the axis is configured to show tick labels using MathJax.
generateLabelText(tick, zero, value) {
for (const [axis, coord] of [
['xAxis', 1],
['yAxis', 2]
]) {
if (
this === plot[axis]?.defaultTicks &&
typeof options[axis]?.ticks?.labels === 'object' &&
tick.usrCoords[coord] in options[axis].ticks.labels
) {
return options[axis].ticks.labels[tick.usrCoords[coord]];
}
}
if (JXG.exists(value)) return this.formatLabelText(value);
const distance = this.getDistanceFromZero(zero, tick);
return this.formatLabelText(Math.abs(distance) < JXG.Math.eps ? 0 : distance / this.visProp.scale);
Expand Down Expand Up @@ -216,7 +228,7 @@ const PGplots = {
strokeColor: options.grid.color ?? '#808080',
strokeOpacity: options.grid.opacity ?? 0.2,
insertTicks: false,
ticksDistance: options.xAxis?.ticks?.distance ?? 2,
ticksDistance: options.xAxis.ticks?.positions ?? options.xAxis.ticks?.distance ?? 2,
scale: options.xAxis?.ticks?.scale ?? 1,
minorTicks: options.grid.x.minorGrids ? (options.xAxis?.ticks?.minorTicks ?? 3) : 0,
ignoreInfiniteTickEndings: false,
Expand Down Expand Up @@ -280,7 +292,7 @@ const PGplots = {
strokeColor: options.grid.color ?? '#808080',
strokeOpacity: options.grid.opacity ?? 0.2,
insertTicks: false,
ticksDistance: options.yAxis?.ticks?.distance ?? 2,
ticksDistance: options.yAxis.ticks?.positions ?? options.yAxis.ticks?.distance ?? 2,
scale: options.yAxis?.ticks?.scale ?? 1,
minorTicks: options.grid.y.minorGrids ? (options.yAxis?.ticks?.minorTicks ?? 3) : 0,
ignoreInfiniteTickEndings: false,
Expand Down Expand Up @@ -352,7 +364,7 @@ const PGplots = {
? true
: false,
insertTicks: false,
ticksDistance: options.xAxis.ticks?.distance ?? 2,
ticksDistance: options.xAxis.ticks?.positions ?? options.xAxis.ticks?.distance ?? 2,
scale: options.xAxis.ticks?.scale ?? 1,
scaleSymbol: options.xAxis.ticks?.scaleSymbol ?? '',
minorTicks: options.xAxis.ticks?.minorTicks ?? 3,
Expand All @@ -375,9 +387,17 @@ const PGplots = {
options.xAxis.overrideOptions ?? {}
)
));
xAxis.defaultTicks.generateLabelText = plot.generateLabelText;

xAxis.defaultTicks.formatLabelText = plot.formatLabelText;

if (options.xAxis.ticks?.customLabels) {
xAxis.defaultTicks.generateLabelText = function (tick) {
return options.xAxis.ticks.customLabels[tick.usrCoords[1] / options.xAxis.ticks.distance - 1];
};
} else {
xAxis.defaultTicks.generateLabelText = plot.generateLabelText;
}

if (options.xAxis.location !== 'middle' && options.xAxis.name !== '') {
plot.xLabel = board.create(
'text',
Expand Down Expand Up @@ -451,7 +471,7 @@ const PGplots = {
? true
: false,
insertTicks: false,
ticksDistance: options.yAxis.ticks?.distance ?? 2,
ticksDistance: options.yAxis.ticks?.positions ?? options.yAxis.ticks?.distance ?? 2,
scale: options.yAxis.ticks?.scale ?? 1,
scaleSymbol: options.yAxis.ticks?.scaleSymbol ?? '',
minorTicks: options.yAxis.ticks?.minorTicks ?? 3,
Expand Down
41 changes: 40 additions & 1 deletion lib/Plots/Axes.pm
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,37 @@ difference between the C<max> and C<min> divided by the C<tick_num>. Default: 0

=item tick_labels

This can be either 1 (show) or 0 (don't show) the labels for the major ticks.
This can be set to 1 to show the labels for the major ticks, 0 to not show the
labels for the major ticks, or to a reference to a hash whose keys are tick
positions, and whose values are tick labels to be shown at those positions.
Default: 1

The following is an example of passing a reference to a hash for this option.

tick_labels => { 5 => '\(a\)' }

In this case if there is a major tick at 5, then the label that will be shown
there is 'a' and the label will be rendered via MathJax. Note that if there is
not a major tick at 5, then the label will be unused. At any other major tick
that is shown, the position will be shown for the label and will be formatted
according to the C<tick_label_format> option.

This option is most useful in combination with the C<tick_positions> option
below. With that option the precise list of major ticks to be shown can be
specified, and the labels for those ticks specified with this option. For
example,

tick_positions => [2, 4],
tick_labels => { 2 => 'a', 4 => 'b' },

would place a tick at 2 labeled 'a', and a tick at 4 labeled 'b'. No other ticks
would be shown on the axis.

Note that if the hash reference value is used, the C<tick_label_format> does not
apply. You are responsible for formatting the labels as you would like them to
appear. If you want the labels rendered via MathJax, then wrap the labels in
C<\(> and C<\)>.

=item tick_label_format

This can be one of "decimal", "fraction", "multiple", or "scinot". If this is
Expand Down Expand Up @@ -133,6 +161,15 @@ C<tick_delta>. Default: 1

This is appended to major tick labels. Default: ''

=item tick_positions

Set this to a reference to an array of values to be used for the positions of
ticks that will be shown on the axis. In this case the C<tick_delta>,
C<tick_distance>, and C<tick_scale> options will not be used to generate tick
positions. If this is set to 0 (or is not an array reference), then the tick
positions will be computed using the values of the C<tick_delta>,
C<tick_distance>, and C<tick_scale> options. Default: 0

=item show_ticks

This can be either 1 (show) or 0 (don't show) the tick lines. If ticks are
Expand Down Expand Up @@ -313,9 +350,11 @@ sub axis_defaults {
tick_labels => 1,
tick_label_format => 'decimal',
tick_label_digits => 2,
tick_label_custom => undef, # NEW: Array of custom labels
tick_distance => 0,
tick_scale => 1,
tick_scale_symbol => '',
tick_positions => 0,
show_ticks => 1,
tick_delta => 0,
tick_num => 5,
Expand Down
4 changes: 2 additions & 2 deletions lib/Plots/Data.pm
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ stored in the C<< $data->{function} >> hash, though other data is stored as a st
);

Note, the first argument must be $self->context when called from C<Plots::Plot>
to use a single context for all C<Plost::Data> objects.
to use a single context for all C<Plots::Data> objects.

This is also used to set a two variable function (used for slope or vector fields):

Expand Down Expand Up @@ -116,7 +116,7 @@ Takes a MathObject C<$formula> and replaces the function with either
a JavaScript or PGF function string. If the function contains any function
tokens not supported, a warning and empty string is returned.

$formula The mathobject formula object, either $self->{function}{Fx} or $self->{function}{Fy}.
$formula The MathObject formula object, either $self->{function}{Fx} or $self->{function}{Fy}.
$type 'js' or 'PGF' (falls back to js for any input except 'PGF').
$xvar The x-variable name, $self->{function}{xvar}.
$yvar The y-variable name, $self->{function}{yvar}, for vector fields.
Expand Down
34 changes: 24 additions & 10 deletions lib/Plots/JSXGraph.pm
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ sub HTML {
$options->{xAxis}{ticks}{scale} = $axes->xaxis('tick_scale');
$options->{xAxis}{ticks}{distance} = $axes->xaxis('tick_distance');
$options->{xAxis}{ticks}{minorTicks} = $grid->{xminor};

my $xticks = $axes->xaxis('tick_positions');
$options->{xAxis}{ticks}{positions} = $xticks if ref $xticks eq 'ARRAY';
}

$options->{yAxis}{visible} = $yvisible;
Expand All @@ -100,6 +103,9 @@ sub HTML {
$options->{yAxis}{ticks}{scale} = $axes->yaxis('tick_scale');
$options->{yAxis}{ticks}{distance} = $axes->yaxis('tick_distance');
$options->{yAxis}{ticks}{minorTicks} = $grid->{yminor};

my $yticks = $axes->yaxis('tick_positions');
$options->{yAxis}{ticks}{positions} = $yticks if ref $yticks eq 'ARRAY';
}

if ($show_grid) {
Expand All @@ -122,21 +128,29 @@ sub HTML {
$options->{mathJaxTickLabels} = $axes->style('mathjax_tick_labels') if $xvisible || $yvisible;

if ($xvisible) {
$options->{xAxis}{name} = $axes->xaxis('label');
$options->{xAxis}{ticks}{show} = $axes->xaxis('show_ticks');
$options->{xAxis}{ticks}{labels} = $axes->xaxis('tick_labels');
$options->{xAxis}{ticks}{labelFormat} = $axes->xaxis('tick_label_format');
$options->{xAxis}{ticks}{labelDigits} = $axes->xaxis('tick_label_digits');
$options->{xAxis}{name} = $axes->xaxis('label');
$options->{xAxis}{ticks}{show} = $axes->xaxis('show_ticks');
$options->{xAxis}{ticks}{labels} = $axes->xaxis('tick_labels');
if ($axes->xaxis('tick_label_custom')) {
$options->{xAxis}{ticks}{customLabels} = $axes->xaxis('tick_label_custom');
} else {
$options->{xAxis}{ticks}{labelFormat} = $axes->xaxis('tick_label_format');
$options->{xAxis}{ticks}{labelDigits} = $axes->xaxis('tick_label_digits');
}
$options->{xAxis}{ticks}{scaleSymbol} = $axes->xaxis('tick_scale_symbol');
$options->{xAxis}{arrowsBoth} = $axes->xaxis('arrows_both');
$options->{xAxis}{overrideOptions} = $axes->xaxis('jsx_options') if $axes->xaxis('jsx_options');
}
if ($yvisible) {
$options->{yAxis}{name} = $axes->yaxis('label');
$options->{yAxis}{ticks}{show} = $axes->yaxis('show_ticks');
$options->{yAxis}{ticks}{labels} = $axes->yaxis('tick_labels');
$options->{yAxis}{ticks}{labelFormat} = $axes->yaxis('tick_label_format');
$options->{yAxis}{ticks}{labelDigits} = $axes->yaxis('tick_label_digits');
$options->{yAxis}{name} = $axes->yaxis('label');
$options->{yAxis}{ticks}{show} = $axes->yaxis('show_ticks');
$options->{yAxis}{ticks}{labels} = $axes->yaxis('tick_labels');
if ($axes->yaxis('tick_label_custom')) {
$options->{yAxis}{ticks}{customLabels} = $axes->yaxis('tick_label_custom');
} else {
$options->{yAxis}{ticks}{labelFormat} = $axes->yaxis('tick_label_format');
$options->{yAxis}{ticks}{labelDigits} = $axes->yaxis('tick_label_digits');
}
$options->{yAxis}{ticks}{scaleSymbol} = $axes->yaxis('tick_scale_symbol');
$options->{yAxis}{arrowsBoth} = $axes->yaxis('arrows_both');
$options->{yAxis}{overrideOptions} = $axes->yaxis('jsx_options') if $axes->yaxis('jsx_options');
Expand Down
18 changes: 18 additions & 0 deletions lib/Plots/Plot.pm
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,24 @@ sub add_arc {
return $self->_add_arc(@data);
}

sub _add_rectangle {
my ($self, $pt0, $pt2, %options) = @_;
unless (ref($pt0) eq 'ARRAY' && @$pt0 == 2 && ref($pt2) eq 'ARRAY' && @$pt2 == 2) {
warn 'A rectangle requires two points defined by length two array references.';
return;
}
$options{fill} = 'self' if $options{fill_color} && !defined $options{fill};
return $self->_add_dataset($pt0, [ $pt2->[0], $pt0->[1] ], $pt2, [ $pt0->[0], $pt2->[1] ], $pt0, %options);
}

sub add_rectangle {
my ($self, @data) = @_;
if (ref($data[0]) eq 'ARRAY' && ref($data[0][0]) eq 'ARRAY') {
return [ map { $self->_add_rectangle(@$_) } @data ];
}
return $self->_add_rectangle(@data);
}

sub add_vectorfield {
my ($self, @options) = @_;
my $data = Plots::Data->new(name => 'vectorfield');
Expand Down
80 changes: 56 additions & 24 deletions lib/Plots/Tikz.pm
Original file line number Diff line number Diff line change
Expand Up @@ -213,25 +213,41 @@ sub generate_axes {
my $x_tick_distance = $axes->xaxis('tick_distance');
my $x_tick_scale = $axes->xaxis('tick_scale') || 1;

my @xticks =
grep { $_ > $xmin && $_ < $xmax }
map { -$_ * $x_tick_distance * $x_tick_scale }
reverse(1 .. -$xmin / ($x_tick_distance * $x_tick_scale));
push(@xticks, 0) if $xmin < 0 && $xmax > 0;
push(@xticks,
grep { $_ > $xmin && $_ < $xmax }
map { $_ * $x_tick_distance * $x_tick_scale } (1 .. $xmax / ($x_tick_distance * $x_tick_scale)));
my $xtick_positions = $axes->xaxis('tick_positions');
my @xticks;
if (ref $xtick_positions eq 'ARRAY') {
@xticks = @$xtick_positions;
} else {
@xticks =
grep { $_ > $xmin && $_ < $xmax }
map { -$_ * $x_tick_distance * $x_tick_scale }
reverse(1 .. -$xmin / ($x_tick_distance * $x_tick_scale));
push(@xticks, 0) if $xmin < 0 && $xmax > 0;
push(@xticks,
grep { $_ > $xmin && $_ < $xmax }
map { $_ * $x_tick_distance * $x_tick_scale } (1 .. $xmax / ($x_tick_distance * $x_tick_scale)));
}

my $xtick_labels_value = $axes->xaxis('tick_labels');
my $xtick_labels =
$xvisible
&& $axes->xaxis('show_ticks')
&& $axes->xaxis('tick_labels')
? (",\nxticklabel shift=9pt,\nxticklabel style={anchor=center},\nxticklabels={"
. join(',', map { $self->formatTickLabelText($_ / $x_tick_scale, 'xaxis') } @xticks) . '}')
&& $xtick_labels_value
? (
",\nxticklabel shift=9pt,\nxticklabel style={anchor=center},\nxticklabels={" . (join(
',',
map {
ref $xtick_labels_value eq 'HASH' && defined $xtick_labels_value->{$_}
? $xtick_labels_value->{$_}
: $self->formatTickLabelText($_ / $x_tick_scale, 'xaxis')
} @xticks
))
. '}'
)
: ",\nxticklabel=\\empty";

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

my @yticks =
grep { $_ > $ymin && $_ < $ymax }
map { -$_ * $y_tick_distance * $y_tick_scale }
reverse(1 .. -$ymin / ($y_tick_distance * $y_tick_scale));
push(@yticks, 0) if $ymin < 0 && $ymax > 0;
push(@yticks,
grep { $_ > $ymin && $_ < $ymax }
map { $_ * $y_tick_distance * $y_tick_scale } (1 .. $ymax / ($y_tick_distance * $y_tick_scale)));
my $ytick_positions = $axes->yaxis('tick_positions');
my @yticks;
if (ref $ytick_positions eq 'ARRAY') {
@yticks = @$ytick_positions;
} else {
@yticks =
grep { $_ > $ymin && $_ < $ymax }
map { -$_ * $y_tick_distance * $y_tick_scale }
reverse(1 .. -$ymin / ($y_tick_distance * $y_tick_scale));
push(@yticks, 0) if $ymin < 0 && $ymax > 0;
push(@yticks,
grep { $_ > $ymin && $_ < $ymax }
map { $_ * $y_tick_distance * $y_tick_scale } (1 .. $ymax / ($y_tick_distance * $y_tick_scale)));
}

my $ytick_labels_value = $axes->yaxis('tick_labels');
my $ytick_labels =
$yvisible
&& $axes->yaxis('show_ticks')
&& $axes->yaxis('tick_labels')
? (",\nyticklabel shift=-3pt,\nyticklabels={"
. join(',', map { $self->formatTickLabelText($_ / $y_tick_scale, 'yaxis') } @yticks) . '}')
&& $ytick_labels_value
? (
",\nyticklabel shift=-3pt,\nyticklabel style={anchor=east},\nyticklabels={" . (join(
',',
map {
ref $ytick_labels_value eq 'HASH' && defined $ytick_labels_value->{$_}
? $ytick_labels_value->{$_}
: $self->formatTickLabelText($_ / $y_tick_scale, 'yaxis')
} @yticks
))
. '}'
)
: ",\nyticklabel=\\empty";

my @yminor_ticks;
if ($grid->{yminor} > 0) {
if ($grid->{yminor} > 0 && ref $ytick_positions ne 'ARRAY') {
my @majorTicks = @yticks;
unshift(@majorTicks, ($majorTicks[0] // $ymin) - $y_tick_distance * $y_tick_scale);
push(@majorTicks, ($majorTicks[-1] // $ymax) + $y_tick_distance * $y_tick_scale);
Expand Down
7 changes: 2 additions & 5 deletions macros/core/PGbasicmacros.pl
Original file line number Diff line number Diff line change
Expand Up @@ -2923,7 +2923,7 @@ sub image {
);
next;
}
if (ref $image_item eq 'Plots::Plot') {
if (ref $image_item eq 'Plots::Plot' || ref $image_item eq 'Plots::StatPlot') {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worthwhile to make this more general, for future extension macros. Maybe call your package Plots::Plot::StatPlot, and then have this only check if ref $image_item starts with Plots::Plot, that way this won't have to be updated each time someone wants to extend Plots?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think there are some other places that might need to be updated, I recall having to modify a few places to check for the ref being equal to Plots::Plot beyond just this place (for some older image macros).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this would be an isa call. Then any object that derives from a Plots::Plot object would work here. Unfortunately, to properly do that you need the Scalar::Util::blessed method which is not available here. The correct way to check if an object, say $object, derives from a particular class is if (blessed($object) && $object->isa('Parent::Package')).

When I was creating the SimpleGraph.pl macro, I almost made that package derive from the Plots::Plot package, and then wanted to make this code that way, but ran into the issue with the lack of the blessed method availability. I ended up going a different direction with the SimpleGraph.pl macro though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, line 2946 below needs to check the ref also.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DO NOT add this ref check to either macros/core/VectorField2D.pl or macros/graph/unionImage.pl. There are checks for the Plots::Plot object in those macros.

The VectorField2D.pl macro usage doesn't make sense for this statistical graph macro.

As to the unionImage.pl one, I told @somiaj not to add that there. It should be removed. No one should be using that macro anymore, and it should be moved into the deprecated folder.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering what to do with this. When you say that the Scalar::Util::blessed method isn't available, is this a safe issue?

Suggestions for updating this?

# Update image attributes as needed.
$image_item->{width} = $width if $out_options{width};
$image_item->{height} = $height if $out_options{height};
Expand All @@ -2942,10 +2942,7 @@ sub image {
$width_ratio = 0.001 * $image_item->{tex_size};
}
$image_item = insertGraph($image_item)
if (ref $image_item eq 'WWPlot'
|| ref $image_item eq 'Plots::Plot'
|| ref $image_item eq 'PGlateximage'
|| ref $image_item eq 'PGtikz');
if (grep { ref $image_item eq $_ } ('WWPlot', 'Plots::Plot', 'Plots::StatPlot', 'PGlateximage', 'PGtikz'));
my $imageURL = alias($image_item) // '';
$imageURL = ($envir{use_site_prefix}) ? $envir{use_site_prefix} . $imageURL : $imageURL;
my $id = $main::PG->getUniqueName('img');
Expand Down
Loading
Loading