Render pixels at specific coordinates?

I’m looking to render something like the below image which depicts the Chicago Loop train tracks and show train locations. I can get and parse out train locations from their API.

However, looking at the widgets it appears there’s no function to render certain colors at certain points, so either I’ll have to figure out how to render a static image of the tracks and figure out how to change parts of it to show where the trains are in real time, or I’ll have to draw the tracks and train locations.

I started trying the latter approach but I’m struggling to figure out how to use rows, columns, and boxes to try to draw this out. I think the simplest approach would be to try to just draw 1x1 size boxes to fill out pixels. I could do this for a single column or row, but how do I render column 2 or row 2? Here’s my code that renders the single column:

return render.Root(
    render.Column(
        children=[
            render.Box(width=1, height=4, color=black),
            render.Box(width=1, height=1, color=green),
            render.Box(width=1, height=1, color=pink),
            render.Box(width=1, height=3, color=black),
            render.Box(width=1, height=1, color=blue),
            render.Box(width=1, height=18, color=black),
            render.Box(width=1, height=2, color=black),
            render.Box(width=1, height=1, color=blue),
            render.Box(width=1, height=1, color=black),
        ],
    )
)

Thanks in advance!

If you want to draw column by column, then you need a Row widget to hold all the columns since it expands horizontally.

Eg:

return render.Root(
  render.Row(
    children = [
      render.Column(...),
      render.Column(...),
      render.Column(...),
      render.Column(...),
      render.Column(...),
    ]
  )
)

Output:
test

Similarly, if you prefer drawing row by row, then you need a Column widget as the parent, and multiple Row widgets to hold the boxes that represent each pixel.

I would assign numbers to each color (eg: black = 1, white = 2) and create a matrix to represent the map:

matrix = [
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 1, 1, 1,...],
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 1, 1, 1,...],
  [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 1, 1, 1,...],
]

Then you can have a function that simply traverses the matrix and builds the widget tree. Your function could also receive the train positions, so while you are traversing the matrix you can already “place” the trains on their designated positions.

1 Like

Got it, that makes sense, wrapping in a row did the trick. Matrix approach makes sense too and would make it very easy to place train locations. Thank you!

1 Like

For anyone that stumbles across this in the future, here’s a function that lets you define a matrix and will generate a render.Root object that fills it in. The colors you see are just variables globally defined to have their hex codes. My map isn’t totally filled in here yet, but you can get the picture. You can also tweak to adjust the matrix values to directly have hex color codes, etc.

def render_static_map():
    map = [
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 7, 4, 4, 4, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 3, 7, 6, 6, 6, 6, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 7, 3, 3, 3, 3, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 7, 7, 7, 7, 7, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 3, 7, 5, 5, 5, 5, 9, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 3, 7, 5, 2, 2, 2, 9, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    ]

     # Number of columns in the first row (assuming all rows are of the same length)
    num_columns = len(map[0])

    # Initialize a list to hold the column objects
    columns = []

    # Loop through each index in the length of a row
    for col_index in range(num_columns):
        # List to hold the Box objects for the current column
        boxes = []

        # Loop through each row and pick the current column index
        for row in map:
            cell = row[col_index]
            # Create a Box object based on the cell value and append it to the list of boxes
            if cell == 0:
                color = black
            elif cell == 1:
                color = red
            elif cell == 2:
                color = blue
            elif cell == 3:
                color = brown
            elif cell == 4:
                color = green
            elif cell == 5:
                color = orange
            elif cell == 6:
                color = pink
            elif cell == 7:
                color = purple                
            else:
                color = white
            boxes.append(render.Box(width=1, height=1, color=color))

        # Create a Column object with the list of boxes and append to the columns list
        columns.append(render.Column(children=boxes))

    # Create the Row object with all the columns
    matrix = render.Row(children=columns)

    # Create the Root object
    root = render.Root(child=matrix)

    return root
3 Likes

Great work!

One improvement here is to throw the colors into a list following the same order of the numbers (0 is black, 1 red and so on).

colors = ["#000", "#f00", "#00f", ...]

Then you don’t need all those if..elif statements, you can use the cell value directly:

color = colors[row[col_index]]
boxes.append(render.Box(width=1, height=1, color=color))

:wink:

1 Like

Yep for sure! I’m planning to have the hex colors themselves be in the matrix instead of just ints, likely just setting some common variable names for them. Thanks again for the help, you unstuck me for sure.