CalendarComponent Code

A note before we start. In this tutorial we will be using features of two libraries beyond the scope of course.

  • Pandas - a fast and flexible Python library for data manipulation and analysis

  • Plotly - a versatile Python library for creating interactive, web-based data visualizations

Plotly is the library that supports Anvil’s plots, while Pandas provides the computing backbone for Plotly. We will only be scraping the surface of both of these libraries, so their functions will not be explained in detail.

If you wish to know more about these libraries, then you can do so through the following tutorials:

Plan

The nature of Plotly means that we will be creating the chart on the backend, and then sending it to frontend to be displayed. Therefore the majority of our code will be written in the assessment_service module.

We will need to create a get_chart method that:

  • retrieve details from the assessments table

  • create a dataframe for the assessment details

  • use Plotly to create a timeline chart from the dataframe

  • send the chart back to the frontend

Dataframes

DataFrames are digital tables where Pandas stores, organises, and works with data, just like you would with rows and columns in a spreadsheet.

Now we have worked out what we need to do, we can start coding.

Coding

get_chart

To code the get_chart method we need to:

  1. Open the assessment_service server module

  2. add the highlighted code to the import section

1import anvil.users
2import anvil.tables as tables
3import anvil.tables.query as q
4from anvil.tables import app_tables
5import anvil.server
6import plotly.express as px
7import pandas as pd

Code explaination

  • line 6 → imports the plotly.express library and assigns it the px alias

  • line 7 → imports the pandas library and assigns it the pd alias

Import aliases

Import aliases in Python allow you to import a module or a function using a shorter or more convenient name, making your code easier to write and read.

  1. move to the bottom of the assessments_service

  2. the highlighted code to retrieve the necessary data

44@anvil.server.callable
45def get_chart():
46    # Fetch assessments from the data table
47    user = anvil.users.get_user()
48    assessments = app_tables.assessments.search(tables.order_by('due_date'),
49                                       user=user,
50                                       completed=False)

Code explaination

  • line 44 → makes get_chart callable from the frontend

  • line 45 → creates the get_chart method

  • line 47 → gets the current user

  • line 48 → retrieves all the outstanding assessments and stores them in the assessments variable

  1. to process the data, add the code highlighted below

44@anvil.server.callable
45def get_chart():
46    # Fetch assessments from the data table
47    user = anvil.users.get_user()
48    assessments = app_tables.assessments.search(tables.order_by('due_date'),
49                                       user=user,
50                                       completed=False)
51    
52    # Create a DataFrame from the assessments data
53    data = []
54    for assessment in assessments:
55        data.append({
56            "Subject": assessment['subject'],
57            "Details": assessment['details'],
58            "Start": assessment['start_date'],
59            "Due": assessment['due_date']
60        })
61    
62    df = pd.DataFrame(data)

Code explaination

  • line 53 → creates the data list to be used for processing the data

  • line 54 → iterates over all the assessments stored in the assessments variable

  • line 55 - 60 → creates a dictionary of each assessment and then adds this to the data list

  • line 62 → converts all the assessment dictionaries in a dataframe

  1. add the highlighted code to create the chart

44@anvil.server.callable
45def get_chart():
46    # Fetch assessments from the data table
47    user = anvil.users.get_user()
48    assessments = app_tables.assessments.search(tables.order_by('due_date'),
49                                       user=user,
50                                       completed=False)
51    
52    # Create a DataFrame from the assessments data
53    data = []
54    for assessment in assessments:
55        data.append({
56            "Subject": assessment['subject'],
57            "Details": assessment['details'],
58            "Start": assessment['start_date'],
59            "Due": assessment['due_date']
60        })
61    
62    df = pd.DataFrame(data)
63    
64    # Create the Gantt chart using Plotly
65    fig = px.timeline(df, 
66                      x_start="Start", 
67                      x_end="Due", 
68                      y="Subject", 
69                      text="Details", 
70                      title="Assessment Schedule"
71                     )
72    
73    return fig

Code explaination

  • lines 65 - 71 → creates the timeline chart and stores it in the variable fig

    • df → the dataframe for the chart

    • x_start=”Start” → identifies the dataframe column which will be used at the starting point for each bar

    • x_end=”Due” → identifies the dataframe column which will be used at the end point for each bar

    • y=”Subject” → provide the column that will be the y axis

    • text=”Details” → labels each bar with the details

  • line 73 → send the chart to the frontend

CalendarComponent

Now that we have a backend method that provides the chart, we need to call that from the CalendarComponent:

  1. Open calendarComponent in the Code mode.

  2. Add the highlighted code to create the load_chart method

24  def load_chart(self):
25    fig = anvil.server.call('get_chart')
26        
27    # Assign the Plotly figure to the Anvil Plot component
28    self.plot_timeline.figure = fig

Code explaination

  • line 24 → creates the load_chart method

  • line 25 → gets the chart by calling the get_chart backend function we just created

  • line 28 → displays the chart on the self.plot_timeline

  1. Finally we need to call the load_chart method so it displays when the component is loaded

  2. Go to the bottom of the __init__ method and had the highlighted line

12  def __init__(self, **properties):
13    # Set Form properties and Data Bindings.
14    self.init_components(**properties)
15
16    # Any code you write here will run before the form opens.
17    if anvil.users.get_user():
18      self.card_details.visible = True
19      self.card_error.visible = False
20      self.load_chart()
21    else:
22      self.card_details.visible = False
23      self.card_error.visible = True

Code explaination

  • line 20 → runs the load_chart method when the Calendar Component is loaded.

Testing

Time to test all that code. Launch your web app and navigate to the Calendar page. You should see something similar to below.

test

If you look closely, there are a couple of problems with our chart.

  1. There is no bar for the English exam

  2. The y-axis title (subject) is unnecessary

test

So lets fix those two.

Exams not appearing

The reason that exams do not appear on the chart, is that the start date and the due date are the same. The timeline chart plots the time between the start and finish, and in the case of exams, this is nothing.

The easiest way to solve this problem is to move the due date back one day. But be careful, we only want to do this for exams, ie. where the due date is the same as the start date.

Lets implement this:

  1. Open the assessment_service server module

  2. Add the highlighted code inside the dataframe creation code

52    # Create a DataFrame from the assessments data
53    data = []
54    for assessment in assessments:
55        # adjust for exams
56        start_date = assessment['start_date']
57        due_date = assessment['due_date']
58                
59        if start_date == due_date:
60            due_date += pd.Timedelta(days=1)
61      
62        data.append({
63            "Subject": assessment['subject'],
64            "Details": assessment['details'],
65            "Start": assessment['start_date'],
66            "Due": assessment['due_date']
67        })
68    
69    df = pd.DataFrame(data)

Code explaination

  • line 56 → stores the start date in the start_date variable

  • line 57 → stores the due date in the due_date variable

  • line 59 → checks to see if the start date and due date are the same

  • line 60 → uses Pandas’ Timedelta method to add one day to the due_date

Now we have adjusted the start and due dates, we need to put these values into the diction before it is added to the data list.

  1. Adjust the highlighted line of code

52    # Create a DataFrame from the assessments data
53    data = []
54    for assessment in assessments:
55        # adjust for exams
56        start_date = assessment['start_date']
57        due_date = assessment['due_date']
58                
59        if start_date == due_date:
60            due_date += pd.Timedelta(days=1)
61      
62        data.append({
63            "Subject": assessment['subject'],
64            "Details": assessment['details'],
65            "Start": assessment['start_date'],
66            "Due": due_date
67        })
68    
69    df = pd.DataFrame(data)

Code explaination

  • line 66 → add our adjusted due_date to the dataframe

Testing Exams

Launch your web app and navigate to the calendar page (note: make sure you have added an exam assessment). You should now see your exams.

exam test

y-axis title

Plotly provides a wide range of formatting options. These options can be found in the Anvil documentation or the Plotly documentation.

We will only be concerned with removing the y-axis title.

  1. Open the assessment_service

  2. At the bottom of the creating Gantt chart section add the highlighted code.

71    # Create the Gantt chart using Plotly
72    fig = px.timeline(df, 
73                      x_start="Start", 
74                      x_end="Due", 
75                      y="Subject", 
76                      text="Details", 
77                      title="Assessment Schedule"
78                     )
79
80    fig.update_yaxes(title_text="") 
81    
82    return fig

Code explaination

  • line 80 → makes the y-axis blank

Test the y-axis

Once again launch you web app and check that the y-axis title has gone.

test y-axis

Final code state

By the end of this tutorial your code should be the same as below:

Final CalendarComponent

 1from ._anvil_designer import CalendarComponentTemplate
 2from anvil import *
 3import plotly.graph_objects as go
 4import anvil.server
 5import anvil.tables as tables
 6import anvil.tables.query as q
 7from anvil.tables import app_tables
 8import anvil.users
 9
10
11class CalendarComponent(CalendarComponentTemplate):
12  def __init__(self, **properties):
13    # Set Form properties and Data Bindings.
14    self.init_components(**properties)
15
16    # Any code you write here will run before the form opens.
17    if anvil.users.get_user():
18      self.card_details.visible = True
19      self.card_error.visible = False
20      self.load_chart()
21    else:
22      self.card_details.visible = False
23      self.card_error.visible = True
24
25  def load_chart(self):
26    fig = anvil.server.call('get_chart')
27        
28    # Assign the Plotly figure to the Anvil Plot component
29    self.plot_timeline.figure = fig

Final assessment_service

 1import anvil.users
 2import anvil.tables as tables
 3import anvil.tables.query as q
 4from anvil.tables import app_tables
 5import anvil.server
 6import plotly.express as px
 7import pandas as pd
 8
 9@anvil.server.callable
10def add_assessment(subject, details, start_date, due_date):
11  user = anvil.users.get_user()
12  
13  app_tables.assessments.add_row(user= user,
14                                 subject= subject,
15                                 details=details,
16                                 start_date=start_date,
17                                 due_date=due_date,
18                                 completed=False)
19
20@anvil.server.callable
21def get_assessment():
22  user = anvil.users.get_user()
23
24  return app_tables.assessments.search(tables.order_by('due_date'),
25                                      user=user,
26                                      completed=False)
27
28@anvil.server.callable
29def update_assessment_completed(assessment_id, completed):
30  assessment = app_tables.assessments.get_by_id(assessment_id)
31  if assessment:
32    assessment["completed"] = completed
33
34@anvil.server.callable
35def update_assessment(assessment_id, subject, details, start_date, due_date, completed):
36    assessment = app_tables.assessments.get_by_id(assessment_id)
37    if assessment:
38        assessment['subject'] = subject
39        assessment['details'] = details
40        assessment['start_date'] = start_date
41        assessment['due_date'] = due_date
42        assessment['completed'] = completed
43
44@anvil.server.callable
45def get_chart():
46    # Fetch assessments from the data table
47    user = anvil.users.get_user()
48    assessments = app_tables.assessments.search(tables.order_by('due_date'),
49                                       user=user,
50                                       completed=False)
51    
52    # Create a DataFrame from the assessments data
53    data = []
54    for assessment in assessments:
55        # adjust for exams
56        start_date = assessment['start_date']
57        due_date = assessment['due_date']
58                
59        if start_date == due_date:
60            due_date += pd.Timedelta(days=1)
61      
62        data.append({
63            "Subject": assessment['subject'],
64            "Details": assessment['details'],
65            "Start": assessment['start_date'],
66            "Due": due_date
67        })
68    
69    df = pd.DataFrame(data)
70    
71    # Create the Gantt chart using Plotly
72    fig = px.timeline(df, 
73                      x_start="Start", 
74                      x_end="Due", 
75                      y="Subject", 
76                      text="Details", 
77                      title="Assessment Schedule"
78                     )
79
80    fig.update_yaxes(title_text="") 
81    
82    return fig