Skip to main content

Data Buffers

Data buffers are in-memory data structures designed to hold a card's tabular data.

Data buffers make it convenient to separate data (what is displayed) from presentation (how it is displayed). You can declare a card once, and update its underlying data buffer multiple times. A card can hold zero or more data buffers.

Cards and buffers​

Data buffers are tabular data structures containing columns and rows. Different cards utilize data buffers in different ways. For example, the plot in the ui.small_series_stat_card() uses a data buffer called plot_data to hold time series data.

CPU Usage

card = ui.small_series_stat_card(
box=f'1 1 1 1',
title='CPU',
value='10%',
plot_data=data('time usage', -15),
plot_category='time',
plot_value='usage',
))

In the above snippet, data('time usage', -15) defines a placeholder for a table with two columns (time and usage) and 15 rows (we'll get to why the 15 is negative shortly), and the card's plot_category and plot_value point to the two columns time and usage, respectively.

Appending rows (tuples or records) to the data buffer make the card plot those rows.

while True:
card.plot_data[-1] = [get_time(), get_usage()]
time.sleep(1)

The while loop above does something like this:

card.plot_data[-1] = ['2020-10-05T02:10:20Z', 42.5]
card.plot_data[-1] = ['2020-10-05T02:10:21Z', 43.1]
card.plot_data[-1] = ['2020-10-05T02:10:22Z', 39.2]
card.plot_data[-1] = ['2020-10-05T02:10:23Z', 38.7]

Types of buffers​

There are three types of data buffers:

  • Array buffers hold tabular data with a fixed number of rows, and allow random access to rows using 0-based integers as keys.
  • Cyclic buffers also hold tabular data with a fixed number of rows, but do not allow random access to rows. They can only be appended to. Rows are first-in first-out (FIFO), and adding rows beyond its capacity overwrites the oldest rows.
  • Map buffers (or dictionary buffers) hold tabular data with a variable number of rows, and allow random access to rows using string keys.
  • List buffers (or dynamic array buffers) hold tabular data with a variable number of rows, allow random access to rows using 0-based integers as keys (negative indexing supported), and allow appending via += operator.
caution

Map buffers are convenient to use, but use more memory on the server compared to array buffers and cyclic buffers, so use them sparingly.

The data function​

Use the data() function to declare a data buffer. The Wave server uses this declaration to allocate memory for the data buffer.

data() takes multiple arguments:

  • fields: The names of columns, in order; a space-separated string or a list or a tuple ('foo bar' or ['foo', 'bar'] or ('foo', 'bar').
  • size: The number of rows to allocate.
    • A positive row count creates an array buffer.
    • A negative row count creates a cyclic buffer.
    • A zero row count (or None) creates a map buffer.
  • rows: A dict or list of rows to initialize the data buffer with. Each row can be a list or tuple.
    • For array or cyclic buffers, pass a list of rows.
    • For map buffers, pass a dict with strings as keys and rows as values.
  • columns: A list of columns to initialize the data buffer with. All columns must be of the same length. The columns are automatically transposed to rows.
  • pack: True to pack (compress) the provided rows or columns, use less memory on the server-side, and improve performance. Set pack=True if you intend to never make any changes to the data buffer once created. Defaults to False.
  • t: Buffer type. One of 'list', 'map', 'cyclic' or 'fixed'. Overrides the buffer type inferred from the size.

Buffers in practice​

Declare a 5-row buffer with columns donut and price.

# Array buffer
b = data(fields='donut price', size=5)

# Cyclic buffer
b = data(fields='donut price', size=-5)

# Map buffer
b = data(fields='donut price')

# List buffer
b = data(fields='donut price', t='list')

Declare and initialize a 5-row buffer with columns donut and price.

# Array buffer
data(fields='donut price', size=5, rows=[
['cream', 3.99],
['custard', 2.99],
['cinnamon', 2.49],
['sprinkles', 2.49],
['sugar', 1.99],
])

# Cyclic buffer
data(fields='donut price', size=-5, rows=[
['cream', 3.99],
['custard', 2.99],
['cinnamon', 2.49],
['sprinkles', 2.49],
['sugar', 1.99],
])

# Map buffer
data(fields='donut price', rows=dict(
crm=['cream', 3.99],
cst=['custard', 2.99],
cin=['cinnamon', 2.49],
spr=['sprinkles', 2.49],
sug=['sugar', 1.99],
))

# List buffer
data(fields='donut price', t='list', rows=[
['cream', 3.99],
['custard', 2.99],
['cinnamon', 2.49],
['sprinkles', 2.49],
['sugar', 1.99],
])

A buffer can only be modified via card. To get a reference to the buffer:

page['example'] = ui.plot_card(
box='1 1 4 5',
title='Line',
data=data(fields='donut price', size=5, rows=[
['cream', 3.99],
['custard', 2.99],
['cinnamon', 2.49],
['sprinkles', 2.49],
['sugar', 1.99],
])
plot=ui.plot([ui.mark(type='line', x_scale='time', x='=year', y='=value', y_min=0)])
)
b = page['example'].data

Modify a buffer row:

# Array buffer
b[2] = ['cinnamon', 2.99]

# Cyclic buffer
# Appends to the end, shifts all elements by 1,
# dropping the first one as it would be the 6th element.
b[-1] = ['fruit', 2.99]

# Map buffer (the following two forms are equivalent)
b['cin'] = ['cinnamon', 2.99]
b.cin = ['cinnamon', 2.99]

# List buffer
b[2] += ['fruit', 2.99]
b[-2] += ['fruit', 2.99] # Modify second index from the end.

Modify a buffer value:

# Array and list buffer (the following two forms are equivalent).
b[2]['price'] = 2.99
b[2].price = 2.99

# Map buffer (the following three forms are equivalent).
b['cin']['price'] = 2.99
b['cin'].price = 2.99
b.cin.price = 2.99

# List buffer (the following two forms are equivalent)
b[2]['price'] = 2.99 # third donut on menu now costs 2.99
b[2].price = 2.99

b[-1]['price'] = 2.99 # last donut from end on menu now costs 2.99
b[-1].price = 2.99

Packed buffers​

The following is true for Wave scripts only. Unicast apps do not store data buffers on server unless started with -editable option.

If you intend to create tabular data once and never change individual rows or values, it is better to avoid allocating memory on the server by using a packed buffer. Packed buffers use less memory on the server and improve performance. To create a packed buffer, use data(..., pack=True). Note that size is not required, and is ignored if provided.

b = data(fields='donut price', rows=[
['cream', 3.99],
['custard', 2.99],
['cinnamon', 2.49],
['sprinkles', 2.49],
['sugar', 1.99],
], pack=True)
b = data(fields='donut price', columns=[
['cream', 'custard', 'cinnamon', 'sprinkles', 'sugar'],
[3.99 , 2.99, 2.49, 2.49, 1.99],
], pack=True)