4

I have to draw a triangle in Python using mathplotlib.
This is how it should eventually look like:

My objective is, once drawn the triangle, to plot some points on it.

At the moment I can draw the triangle just fine:

import matplotlib.pyplot as plt 
from matplotlib.patches import Polygon 
fig = plt.figure() 
ax = fig.add_subplot(111, aspect='equal') 
ax.add_patch(Polygon([[0,0],[0,1],[1,0]], closed=True,fill=True)) 
ax.set_xlim((0,1)) 
ax.set_ylim((0,1)) 
plt.show()

But I can only fill it with a solid color. How do I add a gradient like shown in the picture?

Can some one help me?

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
Gianni Spazi
  • 65
  • 1
  • 5
  • 1
    Is this a homework task? Have you tried anything yet? In how far do other examples and solutions not help you? Is the problem related to the triangle or the points or the filling? Please be more specific and add the code that you already have such that we know where to help you out. – ImportanceOfBeingErnest Feb 06 '17 at 08:46
  • Your edit reflects perfectly what I am looking for. Thanks – Gianni Spazi Feb 06 '17 at 09:46

2 Answers2

6

There is an example on the matplotlib page showing how to use a clip path for an image.
Adapting this to your case would give this:

import matplotlib.pyplot as plt 
import numpy as np
from matplotlib.path import Path
from matplotlib.patches import PathPatch


fig = plt.figure() 
ax = fig.add_subplot(111, aspect='equal') 
path = Path([[0,0],[0,1],[1,0],[0,0]])
patch = PathPatch(path, facecolor='none')
ax.add_patch(patch) 
Z, Z2 = np.meshgrid(np.linspace(0,1), np.linspace(0,1))
im = plt.imshow(Z-Z2, interpolation='bilinear', cmap=plt.cm.RdYlGn,
                origin='lower', extent=[0, 1, 0, 1],
                clip_path=patch, clip_on=True)
im.set_clip_path(patch)
ax.set_xlim((0,1)) 
ax.set_ylim((0,1)) 
plt.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
0

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:

enter image description here

whereas rotating it -10 degrees ccw yields:

enter image description here

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.

a.t.
  • 2,002
  • 3
  • 26
  • 66