From 8b9d93ac6bd9907900931589ba9e7460eabe6126 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 4 Feb 2026 13:35:34 -0600 Subject: [PATCH 1/4] Change the weight color used from red to FireBrick. This color has a contrast ratio of 6.68 against the white background. The red color has a contrast ration of 4.00 which is not sufficient for accessibility purposes. --- macros/math/SimpleGraph.pl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/macros/math/SimpleGraph.pl b/macros/math/SimpleGraph.pl index d498a735a..9ed10f5b0 100644 --- a/macros/math/SimpleGraph.pl +++ b/macros/math/SimpleGraph.pl @@ -819,7 +819,7 @@ sub image { $u * $iVertex->[0] + $v * $jVertex->[0] + $perp[0] * 0.06, $u * $iVertex->[1] + $v * $jVertex->[1] + $perp[1] * 0.06, label => "\\\\($self->{adjacencyMatrix}->[$i][$j]\\\\)", - color => 'red', + color => 'FireBrick', rotate => ($perp[0] < 0 ? 1 : -1) * atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / $main::PI - ($perp[1] < 0 ? 180 : 0) @@ -898,7 +898,7 @@ sub gridLayoutImage { $u * $iVertex->[0] + $v * $jVertex->[0] - $vector->[1] / $norm * 2, $u * $iVertex->[1] + $v * $jVertex->[1] + $vector->[0] / $norm * 2, label => "\\\\($self->{adjacencyMatrix}[$i][$j]\\\\)", - color => 'red' + color => 'FireBrick' ); } } @@ -1002,7 +1002,7 @@ sub bipartiteLayoutImage { $u * $point1->[0] + $v * $point2->[0] - $vector->[1] / $norm * 5 / 4, $u * $point1->[1] + $v * $point2->[1] + $vector->[0] / $norm * 5 / 4, label => "\\\\($self->{adjacencyMatrix}[ $top->[$i] ][ $bottom->[$j] ]\\\\)", - color => 'red' + color => 'FireBrick' ); } } @@ -1071,7 +1071,7 @@ sub wheelLayoutImage { 0.5 * $iVertex->[0] + $iVertex->[1] / $norm * 0.1, 0.5 * $iVertex->[1] - $iVertex->[0] / $norm * 0.1, label => "\\\\($self->{adjacencyMatrix}->[ $self->{wheelLayout} ][$i]\\\\)", - color => 'red', + color => 'FireBrick', rotate => ($perp[0] < 0 ? 1 : -1) * atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / $main::PI - ($perp[1] < 0 ? 180 : 0) @@ -1096,7 +1096,7 @@ sub wheelLayoutImage { 0.5 * $iVertex->[0] + 0.5 * $jVertex->[0] + $vector[1] / $norm * 0.1, 0.5 * $iVertex->[1] + 0.5 * $jVertex->[1] - $vector[0] / $norm * 0.1, label => "\\\\($self->{adjacencyMatrix}->[$i][$j]\\\\)", - color => 'red', + color => 'FireBrick', rotate => ($perp[0] < 0 ? 1 : -1) * atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / $main::PI - ($perp[1] < 0 ? 180 : 0) From 87f4a0b1315a009315b67a6b427dd24514b7d092 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Mon, 2 Mar 2026 19:06:42 -0600 Subject: [PATCH 2/4] Add a `components` method that returns the components of the graph. The method returns an array containing references to arrays that form a partition the vertex indices into the connected components of the graph. For example, for the graph with vertices E, F, G, H, I, J, K, and L, and edge set {{{E, L}, {F, G}, {F, L}, {G, J}, {H, I}, {J, K}, {J, L}}}, the method will return ([E, F, G, J, K, L], [H, I]). --- macros/math/SimpleGraph.pl | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/macros/math/SimpleGraph.pl b/macros/math/SimpleGraph.pl index 9ed10f5b0..acb5611c0 100644 --- a/macros/math/SimpleGraph.pl +++ b/macros/math/SimpleGraph.pl @@ -645,6 +645,37 @@ sub numComponents { return $result; } +sub components { + my $self = shift; + + my @adjacencyMatrix = map { [@$_] } @{ $self->{adjacencyMatrix} }; + + for my $i (0 .. $#adjacencyMatrix) { + for my $j ($i + 1 .. $#adjacencyMatrix) { + if ($adjacencyMatrix[$i][$j] != 0) { + for my $k (0 .. $#adjacencyMatrix) { + $adjacencyMatrix[$j][$k] += $adjacencyMatrix[$i][$k]; + $adjacencyMatrix[$k][$j] += $adjacencyMatrix[$k][$i]; + } + } + } + } + + my @components; + for my $i (reverse(0 .. $#adjacencyMatrix)) { + my $componentFound = 0; + for (@components) { + next unless $adjacencyMatrix[ $_->[-1] ][$i]; + $componentFound = 1; + unshift(@$_, $i); + last; + } + push(@components, [$i]) unless $componentFound; + } + + return main::PGsort(sub { $_[0][0] < $_[1][0] }, @components); +} + sub edgeWeight { my ($self, $i, $j, $weight) = @_; if (defined $weight) { @@ -2242,6 +2273,13 @@ =head2 numComponents This method returns the number of connected components in the graph. +=head2 components + + @c = $graph->components; + +This method returns an array containing references to arrays that form a +partition of the vertex indices into the connected components of the graph. + =head2 edgeWeight $c = $graph->edgeWeight($i, $j); From 2abaee7fc41a2623ecc8c72ff9e771b2cf7a4530 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 3 Mar 2026 19:07:57 -0600 Subject: [PATCH 3/4] Improve label positioning. Instead of trying to place the labels using the coordinates, use the `anchor` and padding options to get better positioning. The primary advantage is now the labels don't float away when the image is enlarged. This was not done originally because at the time this macro was implemented the `anchor` and `padding` options didn't exist. The weights for the default layout and the wheel layout are still positioned along the perpendicular vector for now. The problem is that those labels are rotated, and that does not work well with the anchor. This is a TikZ issue (I implemented the anchor for JSXGraph to work the same as the TikZ anchor option). The problem is that the rotation is around the position of the anchor, and not around the center of the text. The usual solution for this in TikZ is to use a `\rotatebox` on the node contents. Perhaps another rotation option could be addded to the plots macro that would rotate the text instead of rotating around the anchor position. --- macros/math/SimpleGraph.pl | 71 ++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/macros/math/SimpleGraph.pl b/macros/math/SimpleGraph.pl index acb5611c0..d9a126dea 100644 --- a/macros/math/SimpleGraph.pl +++ b/macros/math/SimpleGraph.pl @@ -135,7 +135,7 @@ sub randomGraphWithoutEulerTrail { my $graph; do { - $graph = simpleGraphWithDegreeSequence([ map { main::random(2, $size - 1, 1) } 0 .. $size - 1 ], %options); + $graph = simpleGraphWithDegreeSequence([ map { main::random(2, $size - 1) } 0 .. $size - 1 ], %options); } while !defined $graph || $graph->hasEulerTrail; return $graph->setRandomWeights( @@ -827,11 +827,10 @@ sub image { $plot->add_point(@$iVertex, color => 'blue', mark_size => 3); $plot->add_label( - 1.25 * $iVertex->[0], 1.25 * $iVertex->[1], - label => "\\\\($self->{labels}[$i]\\\\)", + $iVertex->[0], $iVertex->[1], "\\\\($self->{labels}[$i]\\\\)", color => 'blue', - h_align => 'center', - v_align => 'middle' + anchor => 180 + $i * $gap * 180 / $main::PI, + padding => 8 ) if $graphOptions{showLabels}; my $u = 0.275; @@ -849,7 +848,7 @@ sub image { $plot->add_label( $u * $iVertex->[0] + $v * $jVertex->[0] + $perp[0] * 0.06, $u * $iVertex->[1] + $v * $jVertex->[1] + $perp[1] * 0.06, - label => "\\\\($self->{adjacencyMatrix}->[$i][$j]\\\\)", + "\\\\($self->{adjacencyMatrix}->[$i][$j]\\\\)", color => 'FireBrick', rotate => ($perp[0] < 0 ? 1 : -1) * atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / @@ -898,11 +897,10 @@ sub gridLayoutImage { my $y = $gridGap * ($self->{gridLayout}[0] - $i - 1); $plot->add_point($x, $y, color => 'blue', mark_size => 3); $plot->add_label( - $x - $labelShift, $y + 2 * $labelShift, - label => "\\\\($self->{labels}[$i + $self->{gridLayout}[0] * $j]\\\\)", + $x, $y, "\\\\($self->{labels}[$i + $self->{gridLayout}[0] * $j]\\\\)", color => 'blue', - h_align => 'center', - v_align => 'middle' + anchor => -atan2(2, 1) * 180 / $main::PI, + padding => 8, ) if $graphOptions{showLabels}; } } @@ -922,14 +920,14 @@ sub gridLayoutImage { ($self->{gridLayout}[0] - ($j % $self->{gridLayout}[0]) - 1) * $gridGap ]; $plot->add_dataset($iVertex, $jVertex, color => 'black', width => 1); - my $vector = [ $jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1] ]; if ($graphOptions{showWeights}) { - my $norm = sqrt($vector->[0]**2 + $vector->[1]**2); + my $vector = [ $jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1] ]; $plot->add_label( - $u * $iVertex->[0] + $v * $jVertex->[0] - $vector->[1] / $norm * 2, - $u * $iVertex->[1] + $v * $jVertex->[1] + $vector->[0] / $norm * 2, - label => "\\\\($self->{adjacencyMatrix}[$i][$j]\\\\)", - color => 'FireBrick' + $u * $iVertex->[0] + $v * $jVertex->[0], + $u * $iVertex->[1] + $v * $jVertex->[1], + "\\\\($self->{adjacencyMatrix}[$i][$j]\\\\)", + color => 'FireBrick', + anchor => atan2(-$vector->[0], $vector->[1]) * 180 / $main::PI ); } } @@ -1000,21 +998,21 @@ sub bipartiteLayoutImage { for my $i (0 .. $#$top) { $plot->add_point($i * $width + $shift[0], $high, color => 'blue', mark_size => 3); $plot->add_label( - $i * $width + $shift[0], $high + 2 / 3, - label => "\\\\($self->{labels}[$top->[$i]]\\\\)", + $i * $width + $shift[0], $high, "\\\\($self->{labels}[$top->[$i]]\\\\)", color => 'blue', h_align => 'center', - v_align => 'bottom' + v_align => 'bottom', + padding => 8 ) if $graphOptions{showLabels}; } for my $j (0 .. $#$bottom) { $plot->add_point($j * $width + $shift[1], $low, color => 'blue', mark_size => 3); $plot->add_label( - $j * $width + $shift[1], $low - 2 / 3, - label => "\\\\($self->{labels}[$bottom->[$j]]\\\\)", + $j * $width + $shift[1], $low, "\\\\($self->{labels}[$bottom->[$j]]\\\\)", color => 'blue', h_align => 'center', - v_align => 'top' + v_align => 'top', + padding => 8 ) if $graphOptions{showLabels}; } @@ -1028,12 +1026,13 @@ sub bipartiteLayoutImage { $plot->add_dataset($point1, $point2, color => 'black'); if ($graphOptions{showWeights}) { my $vector = [ $point2->[0] - $point1->[0], $point2->[1] - $point1->[1] ]; - my $norm = sqrt($vector->[0]**2 + $vector->[1]**2); $plot->add_label( - $u * $point1->[0] + $v * $point2->[0] - $vector->[1] / $norm * 5 / 4, - $u * $point1->[1] + $v * $point2->[1] + $vector->[0] / $norm * 5 / 4, - label => "\\\\($self->{adjacencyMatrix}[ $top->[$i] ][ $bottom->[$j] ]\\\\)", - color => 'FireBrick' + $u * $point1->[0] + $v * $point2->[0], + $u * $point1->[1] + $v * $point2->[1], + "\\\\($self->{adjacencyMatrix}[ $top->[$i] ][ $bottom->[$j] ]\\\\)", + color => 'FireBrick', + anchor => atan2($vector->[0], -$vector->[1]) * 180 / $main::PI + 180, + padding => 2 ); } } @@ -1070,11 +1069,10 @@ sub wheelLayoutImage { $plot->add_point(0, 0, color => 'blue', mark_size => 3); $plot->add_label( - 0.1, 0.2, - label => "\\\\($self->{labels}[ $self->{wheelLayout} ]\\\\)", + 0, 0, "\\\\($self->{labels}[ $self->{wheelLayout} ]\\\\)", color => 'blue', - h_align => 'center', - v_align => 'middle' + anchor => 180 + $gap * 90 / $main::PI, + padding => 10 ) if $graphOptions{showLabels}; for my $i (0 .. $self->lastVertexIndex) { @@ -1086,11 +1084,10 @@ sub wheelLayoutImage { $plot->add_point(@$iVertex, color => 'blue', mark_size => 3); $plot->add_label( - 1.25 * $iVertex->[0], 1.25 * $iVertex->[1], - label => "\\\\($self->{labels}[$i]\\\\)", + $iVertex->[0], $iVertex->[1], "\\\\($self->{labels}[$i]\\\\)", color => 'blue', - h_align => 'center', - v_align => 'middle' + anchor => 180 + $iRel * $gap * 180 / $main::PI, + padding => 8 ) if $graphOptions{showLabels}; if ($self->hasEdge($self->{wheelLayout}, $i)) { @@ -1101,7 +1098,7 @@ sub wheelLayoutImage { $plot->add_label( 0.5 * $iVertex->[0] + $iVertex->[1] / $norm * 0.1, 0.5 * $iVertex->[1] - $iVertex->[0] / $norm * 0.1, - label => "\\\\($self->{adjacencyMatrix}->[ $self->{wheelLayout} ][$i]\\\\)", + "\\\\($self->{adjacencyMatrix}->[ $self->{wheelLayout} ][$i]\\\\)", color => 'FireBrick', rotate => ($perp[0] < 0 ? 1 : -1) * atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / @@ -1126,7 +1123,7 @@ sub wheelLayoutImage { $plot->add_label( 0.5 * $iVertex->[0] + 0.5 * $jVertex->[0] + $vector[1] / $norm * 0.1, 0.5 * $iVertex->[1] + 0.5 * $jVertex->[1] - $vector[0] / $norm * 0.1, - label => "\\\\($self->{adjacencyMatrix}->[$i][$j]\\\\)", + "\\\\($self->{adjacencyMatrix}->[$i][$j]\\\\)", color => 'FireBrick', rotate => ($perp[0] < 0 ? 1 : -1) * atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / From da670d05950d59caff6668bc73c7685e28d7eb2d Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Wed, 4 Mar 2026 15:23:16 -0600 Subject: [PATCH 4/4] Fix a bug in the `edgeSet` method that results in the method returning an invalid edge set for a graph with a single edge. --- macros/math/SimpleGraph.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/math/SimpleGraph.pl b/macros/math/SimpleGraph.pl index d9a126dea..2b86c1b8b 100644 --- a/macros/math/SimpleGraph.pl +++ b/macros/math/SimpleGraph.pl @@ -543,7 +543,7 @@ sub edgeSet { } } - my $edgeSet = GraphTheory::SimpleGraph::Value::EdgeSet->new($context, @edgeSet); + my $edgeSet = GraphTheory::SimpleGraph::Value::EdgeSet->new($context, \@edgeSet); $edgeSet->{open} = '{'; $edgeSet->{close} = '}'; return $edgeSet;