# CalendarComponent Code
```{topic} In this tutorial you will:
- Retrieving and processing assessment data using Pandas.
- Creating a timeline chart with Plotly.
- Integrating the chart into an Anvil app.
- Handling special cases, such as exams with identical start and due dates.-
- Refining chart display by adjusting axes and labels.
```
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:
- **Learn Pandas & Python for Data Analysis**
- **Plotly tutorial**
## 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
```{admonition} Dataframes
:class: note
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
```{code-block} python
:linenos:
:lineno-start: 1
:emphasize-lines: 6-7
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.server
import plotly.express as px
import pandas as pd
```
```{admonition} Code explaination
:class: notice
- **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
```
```{admonition} Import aliases
:class: note
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.
```
3. move to the bottom of the **assessments_service**
4. the highlighted code to retrieve the necessary data
```{code-block} python
:linenos:
:lineno-start: 44
:emphasize-lines: 1-7
@anvil.server.callable
def get_chart():
# Fetch assessments from the data table
user = anvil.users.get_user()
assessments = app_tables.assessments.search(tables.order_by('due_date'),
user=user,
completed=False)
```
```{admonition} Code explaination
:class: notice
- **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
```
5. to process the data, add the code highlighted below
```{code-block} python
:linenos:
:lineno-start: 44
:emphasize-lines: 9 - 19
@anvil.server.callable
def get_chart():
# Fetch assessments from the data table
user = anvil.users.get_user()
assessments = app_tables.assessments.search(tables.order_by('due_date'),
user=user,
completed=False)
# Create a DataFrame from the assessments data
data = []
for assessment in assessments:
data.append({
"Subject": assessment['subject'],
"Details": assessment['details'],
"Start": assessment['start_date'],
"Due": assessment['due_date']
})
df = pd.DataFrame(data)
```
```{admonition} Code explaination
:class: notice
- **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
```
6. add the highlighted code to create the chart
```{code-block} python
:linenos:
:lineno-start: 44
:emphasize-lines: 21 - 30
@anvil.server.callable
def get_chart():
# Fetch assessments from the data table
user = anvil.users.get_user()
assessments = app_tables.assessments.search(tables.order_by('due_date'),
user=user,
completed=False)
# Create a DataFrame from the assessments data
data = []
for assessment in assessments:
data.append({
"Subject": assessment['subject'],
"Details": assessment['details'],
"Start": assessment['start_date'],
"Due": assessment['due_date']
})
df = pd.DataFrame(data)
# Create the Gantt chart using Plotly
fig = px.timeline(df,
x_start="Start",
x_end="Due",
y="Subject",
text="Details",
title="Assessment Schedule"
)
return fig
```
```{admonition} Code explaination
:class: notice
- **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
```{code-block} python
:linenos:
:lineno-start: 24
:emphasize-lines: 1 - 5
def load_chart(self):
fig = anvil.server.call('get_chart')
# Assign the Plotly figure to the Anvil Plot component
self.plot_timeline.figure = fig
```
```{admonition} Code explaination
:class: notice
- **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**
```
3. Finally we need to call the **load_chart** method so it displays when the component is loaded
4. Go to the bottom of the `__init__` method and had the highlighted line
```{code-block} python
:linenos:
:lineno-start: 12
:emphasize-lines: 9
def __init__(self, **properties):
# Set Form properties and Data Bindings.
self.init_components(**properties)
# Any code you write here will run before the form opens.
if anvil.users.get_user():
self.card_details.visible = True
self.card_error.visible = False
self.load_chart()
else:
self.card_details.visible = False
self.card_error.visible = True
```
```{admonition} Code explaination
:class: notice
- **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.

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

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
```{code-block} python
:linenos:
:lineno-start: 52
:emphasize-lines: 4-9
# Create a DataFrame from the assessments data
data = []
for assessment in assessments:
# adjust for exams
start_date = assessment['start_date']
due_date = assessment['due_date']
if start_date == due_date:
due_date += pd.Timedelta(days=1)
data.append({
"Subject": assessment['subject'],
"Details": assessment['details'],
"Start": assessment['start_date'],
"Due": assessment['due_date']
})
df = pd.DataFrame(data)
```
```{admonition} Code explaination
:class: notice
- **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.
3. Adjust the highlighted line of code
```{code-block} python
:linenos:
:lineno-start: 52
:emphasize-lines: 15
# Create a DataFrame from the assessments data
data = []
for assessment in assessments:
# adjust for exams
start_date = assessment['start_date']
due_date = assessment['due_date']
if start_date == due_date:
due_date += pd.Timedelta(days=1)
data.append({
"Subject": assessment['subject'],
"Details": assessment['details'],
"Start": assessment['start_date'],
"Due": due_date
})
df = pd.DataFrame(data)
```
```{admonition} Code explaination
:class: notice
- **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.

### 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.
```{code-block} python
:linenos:
:lineno-start: 71
:emphasize-lines: 10
# Create the Gantt chart using Plotly
fig = px.timeline(df,
x_start="Start",
x_end="Due",
y="Subject",
text="Details",
title="Assessment Schedule"
)
fig.update_yaxes(title_text="")
return fig
```
```{admonition} Code explaination
:class: notice
- **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.

## Final code state
By the end of this tutorial your code should be the same as below:
### Final CalendarComponent
```{code-block} python
:linenos:
from ._anvil_designer import CalendarComponentTemplate
from anvil import *
import plotly.graph_objects as go
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.users
class CalendarComponent(CalendarComponentTemplate):
def __init__(self, **properties):
# Set Form properties and Data Bindings.
self.init_components(**properties)
# Any code you write here will run before the form opens.
if anvil.users.get_user():
self.card_details.visible = True
self.card_error.visible = False
self.load_chart()
else:
self.card_details.visible = False
self.card_error.visible = True
def load_chart(self):
fig = anvil.server.call('get_chart')
# Assign the Plotly figure to the Anvil Plot component
self.plot_timeline.figure = fig
```
### Final assessment_service
```{code-block} python
:linenos:
import anvil.users
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables
import anvil.server
import plotly.express as px
import pandas as pd
@anvil.server.callable
def add_assessment(subject, details, start_date, due_date):
user = anvil.users.get_user()
app_tables.assessments.add_row(user= user,
subject= subject,
details=details,
start_date=start_date,
due_date=due_date,
completed=False)
@anvil.server.callable
def get_assessment():
user = anvil.users.get_user()
return app_tables.assessments.search(tables.order_by('due_date'),
user=user,
completed=False)
@anvil.server.callable
def update_assessment_completed(assessment_id, completed):
assessment = app_tables.assessments.get_by_id(assessment_id)
if assessment:
assessment["completed"] = completed
@anvil.server.callable
def update_assessment(assessment_id, subject, details, start_date, due_date, completed):
assessment = app_tables.assessments.get_by_id(assessment_id)
if assessment:
assessment['subject'] = subject
assessment['details'] = details
assessment['start_date'] = start_date
assessment['due_date'] = due_date
assessment['completed'] = completed
@anvil.server.callable
def get_chart():
# Fetch assessments from the data table
user = anvil.users.get_user()
assessments = app_tables.assessments.search(tables.order_by('due_date'),
user=user,
completed=False)
# Create a DataFrame from the assessments data
data = []
for assessment in assessments:
# adjust for exams
start_date = assessment['start_date']
due_date = assessment['due_date']
if start_date == due_date:
due_date += pd.Timedelta(days=1)
data.append({
"Subject": assessment['subject'],
"Details": assessment['details'],
"Start": assessment['start_date'],
"Due": due_date
})
df = pd.DataFrame(data)
# Create the Gantt chart using Plotly
fig = px.timeline(df,
x_start="Start",
x_end="Due",
y="Subject",
text="Details",
title="Assessment Schedule"
)
fig.update_yaxes(title_text="")
return fig
```