In response to the comment by Stücke, here is an example of a rotation (in degrees) of a 2-colour pattern for an arbitrary closed geometry:
MWE
This is an example for a geometry with a 2 colour pattern rotated 10 degrees counter clock wise (ccw).
def create_gradient_rectangle():
"""Creates a gradient in arbitrary direction in the shape of a
rectangle."""
fig = plt.figure()
ax = fig.add_subplot(111, aspect="equal")
path = Path([[1, 1], [3, 1], [3, 5], [1, 6], [1, 1]])
patch = PathPatch(path, facecolor="none")
ax.add_patch(patch)
# Create a grid that specifies the grid pattern from 0 to 1 for
# red to blue. Resolution = 50 pixels
resolution = 400
arr = np.zeros([resolution, resolution])
for row in range(resolution):
for col in range(resolution):
arr[row][col] = row / resolution
# TODO: verify the entries start at 0 for the first rows
# Ensure the matrix can be plotted at once.
np.set_printoptions(threshold=np.inf)
np.set_printoptions(linewidth=2000) # default = 75
# Rotate the colour gradient matrix.
angle_ccw_deg = -10 # degrees
arr = rotate(arr, angle=angle_ccw_deg)
if angle_ccw_deg > 90 or angle_ccw_deg < -90:
raise Exception(
"Rotation error too large, swap the colour pattern instead please."
)
# Trim the rotated matrix to remove blank triangles that are generated.
colour_resolution = 4 # 10^5=10.000 different colours.
rounded_flipped_arr = np.flip(np.around(arr, colour_resolution), axis=1)
arr = trim_rotated_square(rounded_flipped_arr, resolution, angle_ccw_deg)
im = plt.imshow(
arr,
interpolation="bilinear",
origin="lower",
cmap=plt.cm.RdYlGn,
extent=[1, 3, 1, 6],
clip_path=patch,
clip_on=True,
)
im.set_clip_path(patch)
ax.set_xlim((0, 10))
ax.set_ylim((0, 10))
plt.show()
plt.cla()
plt.clf()
plt.close()
def trim_rotated_square(arr, resolution, angle_ccw_deg):
"""Removes the right and left sides of the colour gradient matrix because
it contains triangles on which the pattern is not extended due to the
rotation.
:param arr: The original rotated and rounded array.
:param resolution: The resolution of the colour gradient pattern/original
unrotated matrix size.
:param angle_ccw_deg: The angle at which the pattern is rotated.
"""
# Assumes the rotated matrix is a square matrix.
width = arr.shape[0]
# If the rotation is to the ccw, then the top right triangle will move up
# into the edge of the larger matrix that encapsulates the rotated matrix.
if angle_ccw_deg < 0:
# Get the most right column on which the pattern is uninterrupted.
max_col = get_max_col(arr, resolution)
# Get the most left column on which the pattern is uninterrupted.
min_col = width - max_col
# If the rotation is to the cw, then the top left triangle will move up
# into the edge of the larger matrix that encapsulates the rotated matrix.
elif angle_ccw_deg > 0:
# Get the most left column on which the pattern is uninterrupted.
min_col = get_max_col(arr, resolution)
# Get the most right column on which the pattern is uninterrupted.
max_col = width - min_col
cut = arr[:, min_col:max_col]
return cut
def get_max_col(arr, resolution):
"""Returns the maximum column number for which the rotated matrix shows an
uninterrupted pattern.
:param arr: The original rotated and rounded array.
:param resolution: The resolution of the colour gradient pattern/original
unrotated matrix size.
"""
# Loop through the rows from top to bottom until the rotated left or right
# edge is encountered.
for row in range(resolution):
# Scan the columns horizontally until an edge is encountered. Assumes
# the matrix stars with zeros on top for the first colour pattern.
for col in range(resolution):
# Since the matrix is rounded to some digits, the first 0.000x will
# be rounded down to 0, and when the colour value becomes larger,
# it will exceed 0, this indicates the first top edge is found.
# Print the arr to see how this happens.
if arr[row][col] > 0:
# Return the column for which the rotated edge is found.
print(f"row={row},col={col},arr[row][col]={arr[row][col]}")
return col
raise Exception("Did not find rotated corner.")
It yields:

whereas rotating it -10 degrees ccw yields:

Inefficiency
It is quite in-efficient as it first rounds the gradient pattern to some number of variables, then rotates the square matrix by putting it into a larger square matrix, and then I start looping through the larger rotated matrix to find the first top edge position. Then I trim the sides of the larger matrix again to ensure a matrix is returned in which the pattern is propagated completely, instead of with missing triangles.
Recommendation
I have not yet tried multi colour patterns. And it currently only supports a single line as colour pattern. However, if one computes the cut off position for the rotation using sinesoids, the rotation angle and the lenght of the original matrix, then one could do the cut off regardless of the colour pattern.