Skip to main content

Show data & tables

There are two different ways to present data in a view:

  • For a table-like presentation of data, you can use the TableView
  • For a tree-like presentation of grouped data (flat or nested), you can use the DataView

TableView

New in v14.13.0
note

The TableView is a trade-off between simplicity and flexibility. If you need more control on styling, this package might suit your needs. It uses a WebView under the hood.

The TableView provides means to present data in a tabular form. The view has a button to download the data as CSV.

Example TableView

A table view can be created by passing data to a TableResult in a view-method decorated with @TableView:

from datetime import date
from viktor.views import TableView, TableResult


class Controller(ViktorController):
...

@TableView("Student Grades", duration_guess=1)
def table_view(self, params, **kwargs):
data = [
[128, "John", 6.9, False, date(1994, 6, 3)],
[129, "Jane", 8.1, True, date(1995, 1, 24)],
[130, "Mike", 7.5, False, date(1989, 12, 4)],
]

return TableResult(data)

Custom headers

Custom headers can be set on both rows and columns by passing respectively row_headers and column_headers in the TableResult:

    @TableView("Student Grades", duration_guess=1)
def table_view(self, params, **kwargs):
...

return TableResult(
data,
row_headers=["Person 1", "Person 2", "Person 3"],
column_headers=["Student nr.", "Name", "Grade", "Cum laude", "Date of birth"]
)

If no headers are provided, the default headers (1, 2, 3, ...) will be shown.

Sorting & filtering

Sorting & filtering is automatically enabled on the columns if each of the columns contains data of homogeneous type. You can disable sorting & filtering by setting enable_sorting_and_filtering=False on the TableResult:

        return TableResult(..., enable_sorting_and_filtering=False)

Styling

Styling on a per-cell basis can be applied by replacing the value in data with a TableCell instance. The following styling can be applied on a per-cell basis:

  • text_color: color of the text. Can be any Color
  • background_color: color of the cell background. Can be any Color
  • text_style: style of the text. Can be one of: 'bold', 'italic'
from viktor import Color
from viktor.views import TableView, TableResult, TableCell


class Controller(ViktorController):
...

@TableView("Table", duration_guess=1)
def table_view(self, params, **kwargs):
data = [
[1, 2, 1],
[2, TableCell(3, background_color=Color.green()), 2],
[1, 2, 1],
]

return TableResult(data)

Styling on a per-column/row basis can be applied by setting a TableHeader on respectively column_headers or row_headers within TableResult. The following styling can be applied on a per-column/row basis:

  • num_decimals: number of decimals to which the value is rounded (numbers only)
  • align: alignment of the text within the column/row. Can be one of: 'left', 'center', 'right'
from viktor.views import TableView, TableResult, TableHeader


class Controller(ViktorController):
...

@TableView("Student Grades", duration_guess=1)
def table_view(self, params, **kwargs):
data = [
["John", 6.9],
["Jane", 8.1],
["Mike", 7.5],
]

return TableResult(data, column_headers=[
"Name",
TableHeader("Grade", num_decimals=0)
])

Transposing the data

Sometimes it is preferred to present data in a transposed table (with homogeneous types on rows instead of columns), for example when the number of columns would otherwise be much larger than the number of rows. This can simply be achieved by transposing the data yourself, and exchanging row_headers and column_headers in the TableResult, as shown in the following example:

note

Sorting & filtering is disabled if one or more columns contain mixed-type data.

from datetime import date

from viktor import Color
from viktor.views import TableView, TableResult, TableCell


class Controller(ViktorController):
...

@TableView("Student Grades", duration_guess=1)
def table_view(self, params, **kwargs):
data = [
[128, "John", 6.9, False, date(1994, 6, 3)],
[129, "Jane", TableCell(8.1, background_color=Color.green()), True, date(1995, 1, 24)],
[130, "Mike", 7.5, False, date(1989, 12, 4)],
]

transposed_data = [list(d) for d in zip(*data)]

return TableResult(
transposed_data,
row_headers=["Student nr.", "Name", "Grade", "Cum laude", "Date of birth"],
column_headers=["Person 1", "Person 2", "Person 3"],
)

Example of transposed table

Using pandas

It is possible to pass a pandas DataFrame or Styler object directly into a TableResult. The column and index names of the DataFrame will be taken as columns_headers and row_headers respectively:

note

Setting explicit columns_headers and row_headers in TableResult will override the column and index names of the pandas DataFrame.

from datetime import date
import pandas as pd

from viktor.views import TableView, TableResult


class Controller(ViktorController):
...

@TableView("Table", duration_guess=1)
def table_view(self, params, **kwargs):
df = pd.DataFrame(
data=[
[128, "John", 6.9, False, date(1994, 6, 3)],
[129, "Jane", 8.1, True, date(1995, 1, 24)],
[130, "Mike", 7.5, False, date(1989, 12, 4)],
],
columns=["Student nr.", "Name", "Grade", "Cum laude", "Date of birth"],
index=["Person 1", "Person 2", "Person 3"],
)

return TableResult(df)

The following styling properties of a pandas Styler object are supported:

  • Background color (per cell)
  • Text color (per cell)
  • Text style (per cell, bold or italic)
  • Align (per column/row, only if all cells in a row/column have the same alignment)
note

To make use of pandas styling, you need to add the pandas[output-formatting] package to your requirements.txt

from datetime import date
import pandas as pd

from viktor.views import TableView, TableResult


class Controller(ViktorController):
...

@TableView("Table", duration_guess=1)
def table_view(self, params, **kwargs):
df = pd.DataFrame(
data=[
[128, "John", 6.9, False, date(1994, 6, 3)],
[129, "Jane", 8.1, True, date(1995, 1, 24)],
[130, "Mike", 7.5, False, date(1989, 12, 4)],
],
columns=["Student nr.", "Name", "Grade", "Cum laude", "Date of birth"],
index=["Person 1", "Person 2", "Person 3"],
)

df = df.transpose()
styler = df.style.highlight_max(color="green", axis='columns', subset=pd.IndexSlice[['Grade'], :])

return TableResult(styler)

DataView

With a DataView, results can be grouped and be presented in a tree-like form. The data can be flat, or nested up to a maximum of 3 levels.

Example DataView

The following steps have to be performed to visualize the desired results:

  1. Create the data group(s) consisting of DataItems for visualization.
  2. Pass the group(s) to a decorated view method that returns a DataResult.
from viktor.views import DataGroup, DataItem, DataResult, DataView


class Controller(ViktorController):
...

@DataView("OUTPUT", duration_guess=1)
def visualize_data(self, params, **kwargs):
data = DataGroup(
DataItem('Data item 1', 123)
)
return DataResult(data)

Multilayered DataGroup

Below an example is given of a multilayered output group (maximum of three layers deep):

@DataView("OUTPUT", duration_guess=1)
def visualize_data(self, params, **kwargs):
value_a = 1
value_b = 2 * value_a
value_total = value_a + value_b
data = DataGroup(
group_a=DataItem('Group A', 'some result', subgroup=DataGroup(
sub_group=DataItem('Result', 1, suffix='N')
)),
group_b=DataItem('Group B', '', subgroup=DataGroup(
sub_group=DataItem('Sub group', value_total, prefix='€', subgroup=DataGroup(
value_a=DataItem('Value A', value_a, prefix='€'),
value_b=DataItem('Value B', value_b, prefix='€', explanation_label='this value is a result of multiplying Value A by 2')
))
))
)

return DataResult(data)

The explanation_label can be used to inform the user. The output result will look like this:

Example DataView

Positional or key-worded DataItem?

In previous examples, we saw a DataGroup created with and without the use of keyword arguments. Using keywords is necessary to link the corresponding DataItem to the Summary. A detailed guide of the Summary can be found here.

Dynamic group length

The structure of DataGroups allows for dynamic creation/addition of data to the overall result, as long as the maximum number of items is not exceeded. An example of dynamically creating data items is shown below:

shopping_cart = {
'Apple': 0.20,
'Pineapple': 1.80,
'Milk': 1
}

# loop through products
total_cost = 0
data_items = []
for item, price in shopping_cart.items():
data_items.append(DataItem(item, price, prefix='€'))
total_cost += price

# construct data group
data_group = DataGroup(
DataItem('Shopping cart', total_cost, prefix='€', subgroup=DataGroup(*data_items))
)

Setting warning/error statuses

Sometimes it is desired to warn a user when a result exceeds a certain values. For these cases, the status and status_message arguments of a DataItem can be considered. Suppose we want to warn the user if our shopping cart of the example above exceeds 5 euros, we can add the following:

shopping_cart.update({
'Cheese': 2.50
})

# loop through products
...

# construct data group
if total_cost <= 5:
status = DataStatus.SUCCESS
status_msg = ''
else:
status = DataStatus.WARNING
status_msg = 'Mind your wallet!'

data_group = DataGroup(
DataItem('Shopping cart', total_cost, prefix='€', status=status, status_message=status_msg, subgroup=DataGroup(*data_items))
)

Example status message

Testing

New in v13.3.0

mock_View decorator for easier testing of view methods

Methods decorated with @TableView and @DataView need to be mocked within the context of (automated) testing:

import unittest

from viktor.testing import mock_View

from app.my_entity_type.controller import MyEntityTypeController

class TestMyEntityTypeController(unittest.TestCase):

@mock_View(MyEntityTypeController)
def test_data_view(self):
params = ...
result = MyEntityTypeController().data_view(params=params)
self.assertEqual(result.data, ...)