R

ReflexKR

Reflex Docs

Full-Stack Python Web Development with Reflex

·

Course overview

1

Meet Reflex and set up your environment

2

Drawing the screen with components

3

State and Vars: let data draw the screen

4

Events and forms: adding customers

5

Shaping data and charts

6

Wrap-up, debugging and next steps

·

Appendix

Full-Stack Python Web Development with Reflex
Lesson 5

Time 3:05–3:45

Shaping data and charts

By the end of this lesson, a horizontal bar chart of customers per gender will appear at the bottom of the card, and the bars will grow each time you add a customer. Shaping data in Python and showing it on screen is the basic pattern behind every data dashboard.

5-1. The data shape a chart wants (5 min)

Recharts (the React chart library that Reflex wraps for you) takes a list of dictionaries. Each dictionary is one bar.

# the data we have                   # the data the chart wants
[User(..., gender="Male"),          [{"name": "Male",   "value": 1},
 User(..., gender="Female")]   →    {"name": "Female", "value": 1}]

So before you can draw the chart, you need a transformation (aggregation) step.

5-2. Aggregate with Counter (10 min)

from collections import Counter


class State(rx.State):
    users: list[User] = [...]
    users_for_graph: list[dict] = []           # data for the chart
    add_user_dialog_open: bool = False

    @rx.event
    def transform_data(self):
        gender_counts = Counter(user.gender for user in self.users)
        self.users_for_graph = [
            {"name": gender_group, "value": count}
            for gender_group, count in gender_counts.items()
        ]
  • Counter is part of the Python standard library: Counter(["Male", "Female", "Male"]) → {"Male": 2, "Female": 1}.
  • Handlers are real Python running on the server, so you can use the standard library, pandas or anything else as-is. That's impossible inside a UI function (see the table in Lesson 3).

5-3. When to transform: on_load and calling from a handler (5 min)

users_for_graph starts out as an empty list. You need to fill it at two points.

When the page opens: register the handler in add_page's on_load.

app.add_page(
    index,
    title="Customer Data App",
    description="A simple app to manage customer data.",
    on_load=State.transform_data,    # runs every time the page is opened or refreshed
)

When a customer is added: call it directly inside add_user.

@rx.event
def add_user(self, form_data: dict):
    self.users.append(User(**form_data))
    self.transform_data()                 # recompute the chart data
    self.add_user_dialog_open = False
  • description is the page description (a meta tag) shown by search engines and link previews.
  • The weakness of this approach is that you must remember to call transform_data() everywhere you change users. If you add a delete feature and forget the call, the chart and table fall out of sync.

5-4. A better way: computed vars (5 min)

With @rx.var, you can declare "a value computed from other state." Reflex recomputes it automatically whenever users changes.

class State(rx.State):
    users: list[User] = [...]

    @rx.var
    def users_for_graph(self) -> list[dict]:     # return type annotation required
        counts = Counter(user.gender for user in self.users)
        return [{"name": g, "value": c} for g, c in counts.items()]
  • With this change, you can delete transform_data, on_load and the call inside add_user. The UI code (data=State.users_for_graph) stays the same.
  • The original code uses the event approach. In class, we first get the chart working the original way through 5-5, then refactor it to a computed var together in 5-6.

Instructor note — "Store a value and update it every time" vs. "declare how to compute it." A spreadsheet analogy works well: it's the difference between typing a value into a cell and using a formula (=COUNTIF(...)).

5-5. Draw the bar chart (10 min)

def graph():
    return rx.recharts.bar_chart(
        rx.recharts.cartesian_grid(
            horizontal=False, stroke_dasharray="3 3", stroke=rx.color("slate", 4)
        ),
        rx.recharts.x_axis(
            type_="number",
            allow_decimals=False,
            axis_line=False,
            tick_line=False,
            domain=[0, "dataMax + 1"],
        ),
        rx.recharts.y_axis(
            type_="category",
            data_key="name",
            axis_line=False,
            tick_line=False,
            width=64,
        ),
        rx.recharts.bar(
            data_key="value",
            fill=rx.color("accent", 9),
            radius=[0, 6, 6, 0],
            bar_size=28,
        ),
        data=State.users_for_graph,
        layout="vertical",
        width="100%",
        height=160,
        margin={"top": 0, "right": 16, "bottom": 0, "left": 0},
    )

A chart is a component too. You pass its parts as children, and the data and size as keyword arguments.

PartRoleKey arguments
bar_chartThe overall chart framedata (list of dicts), layout="vertical" (horizontal bars)
cartesian_gridBackground grid lineshorizontal=False (vertical lines only), stroke_dasharray="3 3" (dashed)
x_axisHorizontal axis = customer counttype_="number", allow_decimals=False (no ticks like 1.5 customers), domain=[0, "dataMax + 1"] (one extra unit past the max)
y_axisVertical axis = gendertype_="category", data_key="name" (which dict key to use as the label)
barThe barsdata_key="value" (the key used for length), radius=[0, 6, 6, 0] (top-left, top-right, bottom-right, bottom-left corners; rounds only the end)
  • With layout="vertical", the x-axis is numeric and the y-axis is categorical. It's the opposite of vertical bars (the default), and it's the part people mix up most often.
  • rx.color("accent", 9) is the theme's accent color. If you change it in rxconfig.py, for example RadixThemesPlugin(theme=rx.theme(accent_color="indigo")), the buttons and bars change color together.
  • Add graph() as the card's third child, along with a title.
rx.box(
    rx.text("Audience breakdown", size="3", weight="bold"),
    rx.text(
        "Customers by gender",
        size="2",
        color=rx.color("slate", 11),
        margin_top="4px",
        margin_bottom="20px",
    ),
    graph(),
    padding="24px",
),

Check your browser — An "Audience breakdown" section appears below the table, with Male and Female bars each reaching 1. The x-axis goes up to 2 because of domain=[0, "dataMax + 1"]. The bars animate in from the left, so give it 1–2 seconds.

localhost:3000

Try it

Customers

2

Your customer directory and audience breakdown.

NameEmailGender
Danilo Sousadanilo@example.comMale
Zahra Ambessazahra@example.comFemale

Audience breakdown

Customers by gender

End of Lesson 5: adding a customer grows the gender bar chart too.

In the preview above, add one Female customer: the Female bar grows to 2 and the x-axis widens to 3. If the chart doesn't change, check the self.transform_data() call inside add_user.

5-6. Try it yourself: refactor to a computed var (5 min)

Once 5-5 works, switch from the event approach to a computed var while leaving the UI untouched. You'll change three places:

  1. In State, delete the users_for_graph: list[dict] = [] field and the transform_data method, and add the @rx.var method from 5-4. The names clash, so you must delete the field.
  2. Delete the self.transform_data() line inside add_user.
  3. Delete on_load=State.transform_data from app.add_page.
class State(rx.State):
    users: list[User] = [...]
    add_user_dialog_open: bool = False

    @rx.event
    def set_add_user_dialog_open(self, open_: bool):
        self.add_user_dialog_open = open_

    @rx.event
    def add_user(self, form_data: dict):
        self.users.append(User(**form_data))
        self.add_user_dialog_open = False      # no transform_data() call

    @rx.var
    def users_for_graph(self) -> list[dict]:
        counts = Counter(user.gender for user in self.users)
        return [{"name": g, "value": c} for g, c in counts.items()]


app = rx.App()
app.add_page(
    index,
    title="Customer Data App",
    description="A simple app to manage customer data.",
)                                              # no on_load
  • Don't change a single character of data=State.users_for_graph in graph(). From the UI's point of view, a regular state var and a computed var look exactly the same.
  • Keep from collections import Counter as it is.

Check your browser — After you save and hot reload kicks in, the chart looks exactly like it did in 5-5. Add a Female customer: if the bar grows to 2, the refactor worked. It updates without you calling anything, and that's the whole point of computed vars.

Exercises

  1. Add rx.recharts.graphing_tooltip() as a child of the chart so values show up on hover.
  2. Using the same approach as 5-6, create another @rx.var def female_count(self) -> int and show it as "Female: N" next to the chart title.
  3. Build one more chart showing customers per email domain (the part after @).

Checkpoint

  • When you open the page, the chart shows Male 1 and Female 1 bars.
  • Adding a customer makes the matching bar longer.
  • You can explain the difference between the on_load approach and the computed var approach.
  • After switching to a computed var, adding a customer still updates the chart.

← Previous

Lesson 4 · Events and forms: adding customers

Next →

Lesson 6 · Wrap-up, debugging and next steps

Get news about new lessons

We'll email you when this course gets new lessons or a follow-up course opens.

We collect: your email address · Purpose: news about new lessons or the launch of this course · Kept until: you ask us to remove it. We never use it for anything else or share it. Reply to any notice email and we'll delete it right away.

Questions & answers

Ask anything about this lesson. Questions from every language version of this page appear together.

Shown as plain text, without links or formatting.

R

ReflexKR

Learn Python and Reflex by doing. Every example was run and verified on Reflex 0.10.0.

ReflexKR is free, with no banner ads. If it helped you, you can support it with a coffee. ☕ Buy us a coffee

Affiliate disclosure
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex