Bokeh Wrap-Up: ColumnDataSource, Layered Styling, Bokeh Server, and Course Recap
Prerequisite Knowledge
This lecture builds on the following concepts from earlier lectures. If any feel unfamiliar, review the linked notes before proceeding.
Previously Covered in This Subject
- Exploratory vs explanatory analysis — covered in Lecture 2 (Exploratory vs Explanatory Analysis)
- Clutter and white space — covered in Lecture 2 (Clutter)
- Pre-attentive attributes — covered in Lecture 2 (Pre-Attentive Attributes)
- The Gartner quadrant and the tools landscape — covered in Lecture 4 (Data Visualization Tools in the Market)
- Flourish template-driven visuals — covered in Lecture 5 (Flourish: A Live Demo of a Storytelling Tool)
- Geospatial mapping — covered in Lecture 4 (Geospatial Data Visualization)
- Dashboard actions — covered in Lecture 11 (Dashboard Actions)
- Dashboards and dashboard best practices — covered in Lecture 11 (Dashboard Best Practices)
- Visual perception and Gestalt principles — covered in Lecture 10 (Visual Perception and Why Visualization Matters)
- Pre-attentive attributes vs Gestalt principles — covered in Lecture 10 (Pre-Attentive Attributes vs Gestalt Principles)
- Worksheet–dashboard–story hierarchy and story building — covered in Lecture 11 (The Worksheet–Dashboard–Story Hierarchy)
- Matplotlib for Python visualization — covered in Lecture 12 (What Is Matplotlib)
Welcome to the final session of the course. After fifteen sessions of building visualizations — from hand-drawn dashboards in Tableau to Python libraries — this session closes the loop on Bokeh and then on the whole course.
The plan for today. Four things happen in this last session:
- ColumnDataSource (CDS) — the object that sits between your data and every glyph, and unlocks most of Bokeh's power. The session goes deep on it.
- Layered styling — a tour of every layer of a Bokeh chart, from the broadest visual properties down to individual tick marks and gridlines.
- Bokeh server applications — the server-side mode that turns plots into live, browser-based dashboards.
- Full course recap — a quick memory refresh of all 15 earlier sessions, drawn from the recap slides of every previous deck.
This session covers contact hours 31–32. There is no new math, no formulas, and no new library to learn — the emphasis is on deepening the practical knowledge of Bokeh you built in the previous sessions, and then consolidating the entire course into one coherent picture. By the end, you should be able to explain why Bokeh is interactive at runtime, how its data layer works, how far its styling reaches, and how the course's visualization principles connect across tools.
16.1 ColumnDataSource — Bokeh's Data Backbone
Hook — the data you never handed over. You have been using ColumnDataSource since your very first Bokeh plot, without ever writing its name. Every time you passed a Python list to a glyph, Bokeh quietly built a ColumnDataSource behind the scenes. This session makes that invisible layer visible — because once you create it yourself, plots stop being static pictures and become live, interactive data applications.
16.1.1 What Is ColumnDataSource and Why It Matters
This is the final session of the course (session 16, covering contact hours 31–32), and its job is to wrap up the last few details of Bokeh. With that, the course comes to a conclusion. The plan for the session: go in depth into a very powerful component called the ColumnDataSource (CDS for short), which helps Bokeh connect with data and do many more things; learn how to control visuals across the many layers of Bokeh; take a brief look at Bokeh's server-side applications; and finally do a full recap of everything covered in the course.
To understand where we stand, it helps to recall what the last session established. Bokeh is known first and foremost for its interactivity — that is the main differentiator between Bokeh and other plotting libraries. We saw what Bokeh needs in terms of software, hardware, and conceptual prerequisites, and we ran a parameter-by-parameter comparison across Matplotlib, Seaborn, and Bokeh (the earlier Matplotlib-versus-Seaborn comparison was extended to all three). We also met the two Bokeh interfaces: the low-level Bokeh models and the higher-level Bokeh plotting interface, where nearly every plot starts. Glyphs — the building blocks of Bokeh — are the individual components such as markers, circles, and lines that make up a chart. We tried the basic glyphs: scatter markers, line plots, bar plots, rectangles, and histograms. Today we go to the next level.
A ColumnDataSource is, in practical terms, the core of most Bokeh plots. It is the object that provides the data to the glyphs: any data you plot, or pass on to a glyph, goes through a column data source. The surprise is that this has been true all along. In the earlier examples where we passed Python lists and NumPy arrays directly as x and y parameters, a column data source was being created for us automatically, in the background, without us ever calling it by name. That works fine — Bokeh takes care of it. But when you build your plots by creating a ColumnDataSource yourself as an explicit component, you unlock many advanced features for data handling and for the plots themselves. This is what makes it such a powerful piece of the Bokeh stack: it decouples the development (your plotting code) from the data, so the two halves of a visualization can be managed independently.
Intuition — the shared spreadsheet behind the chart. Picture a ColumnDataSource as a well-organized spreadsheet with named columns, like a store's stock sheet: one column for the day, one for the product, one for the amount sold. The glyphs are the chart makers in the next room — they do not rummage through loose papers (raw Python variables); they read the named columns of that one sheet. In the analogy, a spreadsheet can hold numbers and words in separate columns, and every entry in a column must have the same meaning — exactly the constraint a ColumnDataSource enforces.
The hidden layer. And here is the twist that makes this component central: even when you hand Bokeh loose lists (x=[1,2,3], y=[5,4,3]), Bokeh itself writes those numbers into a ColumnDataSource before drawing anything. You never saw it, but it was there — the layer through which all glyph data passes. Creating the source yourself does not change that it is used; it changes who is in control of it.
16.1.2 Three Ways Bokeh Connects to Data
Bokeh can connect with multiple data sources. The previous sessions showed two of them, and today's topic is the third:
- Python lists and arrays. The simplest route: pass x and y arrays directly as parameters to a glyph, and the whole chart is built from those Python lists.
- NumPy arrays. Pass NumPy-generated data straight into the plot. The example we saw earlier created a sine and a cosine wave, generated with NumPy, and plotted them directly.
- ColumnDataSource. An explicit source object created in the code, to which we hand the data and from which the glyphs read their fields.
The key insight about the first two routes: even when we pass the Python list or the NumPy array, a ColumnDataSource is created implicitly for us. We never wrote the code for it, but it was being used. That is how central the column data source is — it is the layer through which all glyph data passes. When we create that object ourselves, we can do far more than when Bokeh creates it for us.
| Route | What you type | Who builds the CDS | Best for |
|---|---|---|---|
| Python lists | p.circle(x=[1,2,3], y=[4,5,6]) |
Bokeh, implicitly | Quick one-off exploration |
| NumPy arrays | p.line(x=t, y=np.sin(t)) |
Bokeh, implicitly | Math-generated curves, fast array math |
| ColumnDataSource | p.circle(x="col_x", y="col_y", source=src) |
You, explicitly | Shared, updating, interactive data |
The rule of thumb: the first two routes are fine when the plot is static and the data is small. The moment you need live updates, filtering, or several glyphs reading the same data, create the source yourself — that is when you need its features.
16.1.3 First Worked Example: A Dictionary-Driven Circle Plot
Here is a plain, straightforward example of the syntax. The idea is to show how a column data source is used programmatically, so that the theory that follows has a concrete anchor.
The imports are the familiar ones: from the plotting interface we need figure and show (the first line of any plot: we need plotting, we need figure, we need show — output_file is optional, though often handy). We also import pandas, because it is pandas that gives us the DataFrame we may want to use later.
Worked example — a dictionary becomes a circle plot. The full setup, step by step:
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource
import pandas as pd
data = {
"x_value": [1, 2, 3, 4, 5],
"y_value": [5, 4, 3, 2, 1],
"color": ["red", "green", "blue", "orange", "purple"],
}
source = ColumnDataSource(data=data)
p = figure(title="Circle plot from a ColumnDataSource", width=400, height=300)
p.circle(x="x_value", y="y_value", color="color", source=source)
show(p)
One part of this data holds the x values, another holds the y values, and the third holds the color for each point of the series. The values are stored as three parallel columns — every column has five entries, so every column lines up row by row:
| Row | x_value | y_value | color |
|---|---|---|---|
| 1 | 1 | 5 | red |
| 2 | 2 | 4 | green |
| 3 | 3 | 3 | blue |
| 4 | 4 | 2 | orange |
| 5 | 5 | 1 | purple |
ColumnDataSource(data=data) takes the whole dictionary and builds a column data source from it, treating every key as a column name and every list as that column's values. The glyph call then reads column names instead of raw values: x="x_value" means "take the column called x_value", y="y_value" means "take the column called y_value", and color="color" means "take the color column". The rendered plot shows one circle per row, each colored by its color-field entry: red, green, blue, orange, purple.
Sense-check. The first circle sits at x = 1, y = 5 (top left, red) and the last at x = 5, y = 1 (bottom right, purple) — a descending diagonal from the top-left corner to the bottom-right, which matches the descending numbers in y_value. Same result as passing lists directly, but now with an explicit, reusable data object behind the plot.
The point of the exercise: instead of passing the values inline as arrays, we pass the names of the columns as strings and mention the source object. Everything is then plotted based on the fields that live inside the column data source.
16.1.4 How a Dictionary Becomes Columns
This example needed a Python dictionary, passed to the object's data parameter. Two things happen automatically when you do this. First, the column names of the data are taken from the dictionary's keys: whatever variables you pass in the data object become column keys — here, x_value and y_value were taken up as parameter names automatically. Second, the values of the dictionary become the data values for those columns: the numbers under each key are what the column data source stores, and those are the values it will hand to the plot. The source then uses these columns, one as the x axis, one as the y axis, to create the plot.
There is one structural rule hidden in this step: a ColumnDataSource is a table, so every column must have the same length. If one dictionary list has three entries and another has five, the source cannot line the rows up — Bokeh will reject the mismatched columns with an error. Think of it as a table where every column must have the same number of rows.
16.1.5 The Minimum Three Parameters
Whenever we render with a glyph that reads from a column data source, we have to pass at least three parameters — a minimum that applies every time. First, x: the name of the column that contains the x values. Second, y: the name of the column that contains the y values. Third, source: the column data source object itself. Many other parameters exist — coloring, fonts, and a lot more — and we will see them gradually, but those three are the required core whenever the source is a ColumnDataSource.
Two details are worth locking in. The x and y values are strings — the names of columns — because that is how the glyph says "go fetch this field from the source". The source value is the object. If you pass a plain list to x while also passing a source, Bokeh falls back to the list for that one parameter and ignores the source's column — mixing the two styles usually means the chart silently stops updating from the source, which is a classic source of confusion.
16.1.6 What ColumnDataSource Can Do
With the syntax settled, we turn to what makes the component so unique and so popular — its capabilities with data:
Worked example — the CDS data operations. Five operations, one source:
Add a new column to the existing source. While the existing data stays right where it is, we can add a third column (with any name we like) on top of the first two. The syntax is simple — assign a new key into source.data:
source.data["third"] = [10, 20, 30, 40, 50]
The source now has three columns; the original two fields and their data points are untouched. That is one of the strengths of the column data source — new columns slot in without rebuilding anything.
Replace all the data. Assigning a completely new dictionary to source.data wipes everything: whether the source already held data or is freshly created, the past dictionary is dropped and the new one is held instead. If the earlier source had three columns and the new dictionary brings five, it still replaces everything — effectively delete-all followed by insert-all, in a single command.
source.data = {
"x_value": [10, 20, 30],
"y_value": [1, 4, 9],
"third": ["a", "b", "c"],
}
The old rows are gone; the plot now reads the new table. This is the "reset" lever — useful when a completely new dataset arrives (a new month of figures, a new sensor readout).
Accept a pandas DataFrame. Instead of arrays and plain values, we can pass a whole pandas DataFrame to the data parameter. The DataFrame can be prepared anywhere else in the code (it might be the result of a groupby or any other DataFrame operation), and every feature the DataFrame offers is available before the data reaches the plot.
df = records.groupby("region")["amount"].sum().reset_index()
source = ColumnDataSource(data=df) # column names come from the DataFrame columns
A DataFrame is already a labeled table, so the mapping is direct: DataFrame columns become CDS columns, DataFrame rows become CDS rows.
Stream — append data on top. With the stream method we can append new data to the column data source. If the source holds 10 values, we can add two more and it becomes 12 rows.
source.stream({"x_value": [6, 7], "y_value": [0, -1]})
The interesting part is efficiency: the stream call sends only the newly added data to the browser, never the whole dataset — so a chart fed by a live sensor or a stock feed can grow indefinitely without re-sending its entire history on every tick. The browser receives the two new rows, appends them to what it already has, and redraws only what changed.
Patch — update slices of data. Patching lets us update a very specific slice of the data: we can say, I only want to update selected rows of my data. It is a very efficient way to update slices, because with a patch we send only the new data to the browser instead of the entire dataset. We map exactly which cells should change, and only those specific cells are updated based on the patch.
source.patch({"y_value": [(2, 50)]}) # row index 2 of y_value becomes 50
The patch payload is a dictionary: the key is the column, and the value is a list of (row, new_value) pairs. Only the named cells change; every other cell stays untouched — like correcting two cells of a spreadsheet instead of retyping the whole sheet.
Stream and patch type rule. A common principle of database management applies to appending: whenever we append, the columns and their data types must stay consistent — we cannot append text into a field that is numeric, for example. If x_value holds numbers, source.stream({"x_value": ["new"]}) breaks the column's type, and the browser-side table no longer knows how to plot it. The same discipline holds for patches: a patch that swaps a number for a string in a numeric column corrupts the column's consistency. Keep the data types stable and appending works cleanly, with only the appended portion shown on the webpage.
The professor's own wrap on the feature list: all this time, in the earlier sessions, we were just passing the parameters and the data ourselves — but look at what the column data source actually does for us. That flexibility and control when dealing with data is the beauty of the component.
16.1.7 Why ColumnDataSource Matters
The features above are just a subset of what CDS can do, and together they add up to real efficiency. With a column data source we can manage our data much better and control our visualization much better — we are in control: we can apply filters, show subsets of data, squeeze data, and add data at runtime, and that is exactly why Bokeh is interactive on runtime. A great deal of Bokeh's interactivity relies on ColumnDataSource features.
CDS is also very flexible about data structure. It works with various data formats: it can take a list, a NumPy array, or a DataFrame directly. So it can draw on data from many structures without conversion fuss.
That flexibility feeds the interactivity itself. Tooltips and hover interactions that show real-time data points rely on the column data source to fetch the data point values. With such a powerful way of managing data underneath, the interactivity layer gets better too.
There is also a code-quality argument. Without CDS, imagine the same code holding 10 lines of pure visualization code, then five lines of data management, then more plotting — the code quickly becomes difficult to manage. Because the column data source separates the data from the plot creation, the code is much better organized, cleaner, more readable, and more maintainable — easier to manage and update. Keeping data and code separate is one of its quietest but most valuable advantages.
The database-view analogy (the professor's own). For anyone from a database background (think of Oracle), there is a handy way to think about it: you can create your own views in a database — joining tables, merging columns, doing the work once — and then query the view. ColumnDataSource is similar: it hands already-processed data to the glyphs, and because the data work is done up front, plotting becomes even more powerful. It treats data like a whole table.
Where the analogy breaks. A database view is recomputed from its base tables on every query — it stays fresh automatically. A ColumnDataSource is a copy of the data you handed it: if the original Python list changes after the source is built, the source does not follow. You must explicitly stream or patch the source when the outside data moves. That manual refresh step is the price of the speed you gain.
Visual intuition — the layers of control. Picture the chart as a storefront: the data table is the stockroom (rows and columns of facts), and the glyphs are the display shelves that show a selection of those facts. With an implicit source, the stockroom is a black box — you cannot rearrange it after the shelves are stocked. With an explicit CDS, you own the stockroom: you can add a new shelf's worth of data (add column), clear the stock and restock (replace), top up deliveries (stream), fix individual price tags (patch), and filter which facts reach the shelves — all while the storefront stays open in the browser. The one-sentence takeaway: control over the table is control over the interactivity.
Assumptions & scope. A ColumnDataSource assumes your data is rectangular and column-typed — equal-length columns where each column has one consistent type. When that holds (nearly all tabular data: sensor logs, sales records, simulation outputs), CDS is the right tool and its interactivity pays off. When your data is a single scalar, a small throwaway list, or highly irregular nested structures, the implicit route is simpler and the CDS machinery adds nothing. The scope boundary is also performance-related: streaming works because the browser holds a copy of the table; if your updates arrive faster than the browser can render them, the data layer is fine but the render layer becomes the bottleneck — that is a signal to downsample or throttle, not to abandon CDS.
Pitfalls.
- Passing values instead of column names. With a source present, write
x="x_value", notx=data["x_value"]— passing raw lists bypasses the source and quietly breaks every interactive feature (tooltips, selection, updates) that reads from it. - Mixing styles. Passing a list to
xwhile using a source for everything else creates a half-explicit, half-implicit chart: the list-based series never updates, and you will not see an error to warn you. - Unequal column lengths. A dictionary whose lists have different lengths cannot form a table; Bokeh raises an error at source creation. Always align every column to the same number of rows.
- Type drift in stream and patch. Appending text to a numeric column, or patching a number into a text column, corrupts the column's consistency and produces broken or empty renders rather than an obvious error.
Recap. The ColumnDataSource is the table that sits between your data and your glyphs — created implicitly even when you pass plain lists — and creating it explicitly unlocks add, replace, DataFrame, stream, and patch operations that power Bokeh's runtime interactivity. The handoff: the same layering idea — from the whole canvas down to a single tick — drives the styling tour of the next section, and the same data object will be what Bokeh's server watches for live updates.
Real-world & domain. This is the component that powers interactive dashboards and live-updating data applications built on Bokeh. Concretely: for a chart that compares sales figures across regions, you store the sales data in a column data source as a table with region and sales columns, then plot the sales trend across the region directly from it — and when the next quarter's numbers arrive, one stream call updates the chart on every viewer's screen. In practice, CDS is the workhorse behind financial tick charts (append a new price point per second), IoT dashboards (patch the latest sensor reading into a live panel), and business analytics tools where managers filter a shared dataset at runtime without a developer in the loop. Combined with the Bokeh server, CDS is the engine of real-time, browser-based analytics — the direct route from Python data analysis to interactive products.
16.2 Controlling Visuals Across Bokeh's Layers
Hook — one chart, many dials. Almost every visual fact about a Bokeh chart — the color of a grid line, the width of an axis, whether a series is shown at all — is a property you can set. This section is a tour of every layer of control, from the broadest (line, fill, and text) down to the finest (a single tick's length), with a working demo at each rung of the ladder.
16.2.1 The Styling Layers of a Bokeh Chart
We keep hearing that Bokeh charts are very powerful, allow a lot of customization, and that one can really control almost everything. This section shows how, working through examples. The professor organizes the styling controls into a ladder of layers and walks from the broadest to the finest: general visual properties (line, fill, text), the visible property, plot-level (figure) properties, glyph-level properties, the axis, and finally the grids. Each layer owns its parameters, and the demos below follow that order.
Intuition — the styling ladder. Picture the chart as a building with floors of control, each floor owning its own switches:
- General visual properties — the three families (line, fill, text) that apply anywhere those elements appear.
- Visible property — the on/off switch for whole elements.
- Plot level — the canvas itself: dimensions, title, background, border.
- Glyph level — the circles, lines, and markers sitting on the canvas.
- Axis — the rulers: labels, line widths, colors, tick marks.
- Grids — the guides behind the data: lines, bands, and bounds.
The ladder is hierarchical: a change at a higher floor is broader in effect, and a change at a lower floor is finer — down to individual ticks. The demos below walk the ladder from top to bottom, so the tour itself maps to the mental model.
16.2.2 General Visual Properties: Line, Fill, and Text
At the top of the ladder sit the general visual properties, which fall into three families. The line properties control anything drawn as a line: line color, line width, line alpha, and more — the full table of line parameters is available for combination. The fill properties do the same for filled regions. The text properties control text rendering: text font, text font size, style, color, outline color, and similar. The examples only exercise one or two parameters each, but the tables list all the potential permutations and combinations for controlling and managing lines and text. These are the broad, per-property knobs that apply across charts.
| Family | Example parameters | What it touches |
|---|---|---|
| Line | line_color, line_width, line_alpha, line_dash, line_cap, line_join |
Strokes of lines and glyph outlines |
| Fill | fill_color, fill_alpha |
Interior of filled shapes (boxes, bars, circles) |
| Text | text_font, text_font_size, text_font_style, text_color, text_alpha, text_outline_color |
Labels, titles, annotations |
The key idea at this rung: these are general — the same line_width knob works on a plotted line, on a glyph's outline, and on the figure border. Once you know the family, you know the parameter names everywhere else in the ladder.
16.2.3 The Visible Property
The visible property is a general property that can turn whole elements on and off — and that is what starts to make things interactive. In the demo, the chart contains two elements: a green box and a pink line. Click the green-box button and the green box disappears; click the pink-box button and the pink line disappears; click again and the element comes back. We can toggle anything visible or invisible through this interactivity.
Worked example — toggling a green box and a pink line. The pattern: create the figure, pass the parameters, create the pink line and the box, then set up buttons with their parameters — an active state, and instructions to make the element visible.
p = figure(width=400, height=300)
box = p.rect(x=[2, 6], y=[2, 4], width=1.5, height=1.0,
fill_color="green", line_color="green")
line = p.line(x=[1, 2, 3, 4, 5], y=[3, 4, 3.5, 5, 4.5], line_color="pink",
line_width=4)
def hide_box():
box.visible = False
def show_box():
box.visible = True
# each button is wired to one toggle; clicking flips the element's visibility
The key move is box.visible = False — one assignment hides the whole element, and visible = True brings it back. One toggle does one thing, the second toggle does something else; both flip the visibility of their element on the runtime. The result is a chart whose parts appear and vanish on demand — a cheap but effective way to let users focus on one series at a time.
Sense-check. After clicking the box toggle, the green rectangle vanishes while the pink line stays; clicking again restores it. The rendered chart behaves exactly like a series being switched off in a dashboard filter — only the display state changed, the data is untouched.
16.2.4 Passing Colors Many Ways
Colors are a second general area with many ways to be expressed. The demo shows one chart with triangles, a circle, and a line, where every control sets its color differently: one element is colored with a single RGBA value; another takes a list of different colors (three, in this case, one per item) again via RGBA; and the scatter of triangles is colored from an array of colors. So a line accepts a single color variable, circles accept a list of colors, and triangles accept an array of colors — the color parameter can be passed very differently for each glyph.
Worked example — one chart, three ways to pass color. Every glyph in the same figure receives its color in a different form, and Bokeh accepts them all:
# a line: ONE single color value
p.line(x, y, line_color="firebrick")
# circles: a LIST of colors, one per circle
p.circle(x, y2, size=12, fill_color=["red", "green", "blue"])
# triangles: an ARRAY of colors, one per triangle
p.triangle(x, y3, size=12, fill_color=np.array(["orange", "purple", "teal"]))
The same fill_color parameter carries a single string, a Python list, or a NumPy array — Bokeh matches the color list positionally to the data rows, so the first circle gets the first color, the second gets the second, and so on. A single-value color applies to the whole glyph; a per-row color vector colors each marker individually.
More broadly, the supported color forms are: named colors (like red or firebrick), RGBA values, RGB triples of integers, hex values, and 32-bit numbers. The whole idea of the demo: we can call colors in very different ways depending on the situation, and the glyphs accept them all.
16.2.5 Plot-Level Styling: The Canvas
Going one level down from general properties, we reach the plot (figure) level. None of these controls touches the glyphs — no circle or triangle has been drawn yet. We are controlling the canvas itself: should there be a boundary on the canvas, what should its dimensions be, what does its background look like.
Worked example — the canvas: height, title, background, border. The demo sets several plot properties in one figure:
p = figure(width=500, height=300)
p.title.text = "Styled canvas"
p.title.text_color = "olive"
p.title.text_font_style = "italic"
p.background_fill_color = "lightgrey"
p.background_fill_alpha = 0.5
p.outline_line_width = 3
p.outline_line_alpha = 0.8
p.outline_line_color = "navy"
- Height 300 — the figure's vertical size in pixels.
- The title — the text color is set to olive, the font style to italic, by reaching into the figure's title object (a chain like figure → title → text color / font / style, reminiscent of the property chains in Visual Basic-style modular languages, where you can reach the lowest level with command-dot-dot).
- The background — the background fill color with an alpha of 0.5 visibly changes the background shade.
- The border — the figure's outline line width, line alpha, and line color give the canvas a navy border.
Sense-check. A 300-pixel-tall canvas with an olive, italic title, a half-transparent grey background, and a navy border — every setting lives on the figure object, not on any glyph.
Alpha — the shading of a color. Alpha (written as an alpha value, usually between 0 and 1) controls the transparency of a color: 1 is fully opaque, 0 is fully invisible, and 0.5 is halfway — the color at half strength over whatever sits behind it. It is like the shading of a color: crank the alpha down and the element fades into the background; raise it and the element solidifies. The same parameter name (*_alpha) appears on lines, fills, titles, and outlines — the alpha of the background, of a glyph's fill, or of a gridline.
16.2.6 Glyph-Level Styling and Selection
One more level down, and we are styling the glyphs themselves — the circles, lines, and markers that sit on the canvas. In the demo, the glyph is captured in a variable (r.glyph), and all properties are applied to it: the size is changed to 60, the fill alpha to a value such that the fill is quite transparent, the line color to firebrick (that reddish tone), the line style to dashed, and the line width to 2. Since no marker parameter was passed, the glyph defaults to a circle — and so we see circles of size 60 with a transparent fill, a firebrick dashed outline, and a border width of 2. Every one of those settings came from the glyph object, not the figure.
Worked example — styling the glyph object directly. The glyph is pulled out of the renderer and restyled after creation:
r = p.circle(x=[1, 2, 3], y=[1, 2, 1]) # no marker passed -> default circle
r.glyph.size = 60 # circle diameter in screen units
r.glyph.fill_alpha = 0.2 # quite transparent fill
r.glyph.line_color = "firebrick" # reddish outline
r.glyph.line_dash = "dashed" # dashed outline style
r.glyph.line_width = 2 # outline thickness
Because the glyph defaults to a circle, the chart shows circles of size 60 with a transparent fill, a firebrick dashed outline, and a border width of 2. This is the finer-grained twin of the general line properties from section 16.2.2: the same line_color-style knobs, now attached to one specific glyph.
The click-to-select demo. A second example shows how glyphs can respond to user action: selecting and unselecting. Clicking one glyph selects it and unselects the other; click again and the roles swap. One glyph gets selected while the other gets deselected, purely through runtime control of glyph properties — the code passes the parameters, tells Bokeh what to do on the selected state and on the non-selected state, and Bokeh brings the interactivity for us. The visual pattern: the selected glyph gets its highlight properties (for example, a thicker outline or a brighter fill), and the unselected glyph falls back to its normal properties — the swap is instant because only the property states change, not the data.
16.2.7 Axis Styling
The next layer is the axis. Here we style the attributes on the axis object itself — the x axis, the y axis, and the various attributes attached to each. The demo starts from a figure with dimensions set, then adds a scatter plot of the chosen variables. Then the axis styling begins: for this figure, the x-axis axis label should be temp, the x-axis line width should be 3, and the x-axis line color should be red; the y-axis label is pressure, its color orange, and its orientation vertical. When rendered, the x axis shows the temp label with a visibly thicker, red line (compare the thick axis against the thin default), and the y axis shows pressure in orange with vertical orientation. The same mechanism also configures the major and minor ticks, which the next sections tune further.
Worked example — axis label and axis line styling. The axis is reached through p.xaxis and p.yaxis, and every visible facet is a settable property:
p = figure(width=500, height=350)
p.scatter(x, y)
p.xaxis.axis_label = "temp"
p.xaxis.axis_line_width = 3
p.xaxis.axis_line_color = "red"
p.yaxis.axis_label = "pressure"
p.yaxis.axis_line_color = "orange"
p.yaxis.major_label_orientation = "vertical"
Rendered, the x axis shows the temp label with a visibly thicker, red line, and the y axis shows pressure in orange with vertical orientation. The same object model covers the major and minor ticks — the next subsections tune those further.
16.2.8 Labels and Tick Label Formats
Labels get their own controls. The demo sets the axis label color (it, too, can be set differently), and the label standoff to 30 — the standoff controls the distance between the label and its axis. The y-axis label is set to bin count, and the label style is italic. So we can manage many aspects of how axis labels look and where they sit.
Tick label formats are a separate control: the number formatting of the axis itself can change. The demo says: whatever the x axis shows, present it as a percentage; whatever the y axis shows, present it as a dollar value. The rendered chart then shows x values formatted with percentages and y values formatted with dollars — useful in financial or proportional dashboards, where raw numbers are less readable than formatted ones.
Worked example — label position, label style, and number formats.
p.xaxis.axis_label = "temperature"
p.xaxis.axis_label_standoff = 30 # distance (pixels) between label and axis
p.xaxis.axis_label_text_color = "black"
p.yaxis.axis_label = "bin count"
p.yaxis.axis_label_text_font_style = "italic"
from bokeh.models import NumeralTickFormatter
p.xaxis.formatter = NumeralTickFormatter(format="0.0%") # percentages
p.yaxis.formatter = NumeralTickFormatter(format="0,0") # dollar-style grouping
- Standoff 30 — the label sits 30 pixels away from its axis instead of hugging it.
- bin count in italic — the y label is styled independently of the x label.
- Formats — the x values render as percentages (for example, 0.25 shows as 25%) and the y values as dollar-style figures (a thousand five hundred shows as 1,500, with the dollar symbol added by the currency format string in the real demo). The underlying numbers never change — only the presentation.
Sense-check. A chart whose x axis reads like a proportion (35%, 40%, 45%) and whose y axis reads like a budget (1,200 then 1,500, shown as dollar values) — no reformatting of the data was needed, the formatter does it at render time.
16.2.9 Bounds and Tick Locations
The bounds control decides how much of a particular axis we want to show. The demo sets the x bounds between 2 and 4, and the chart only displays that range of x values — everything else is ignored, no matter what the data holds. It is a way of zooming the plot to the region of interest while keeping the data untouched.
Tick locations can be fixed by hand as well. The demo sets specific tick locations at 2, 3.5, and 4. Although the natural scale might suggest ticks at 1, 2, 3, 4, 5, the chart draws ticks exactly where we asked: 2, 3.5, and 4. Custom tick placement is how we align axis marks with meaningful values (thresholds, targets, milestones) instead of round numbers.
Worked example — bounds and hand-placed ticks.
p.x_range.bounds = (2, 4) # only x values in [2, 4] are displayed
p.xaxis.ticker = [2, 3.5, 4] # ticks exactly at these values
The chart only displays the x range from 2 to 4 — data outside is clipped from view but never deleted. And although the natural scale might suggest ticks at 1, 2, 3, 4, 5, the chart draws ticks exactly where we asked: 2, 3.5, and 4.
Sense-check. A chart zoomed to the 2–4 strip of its data with a non-round tick at 3.5 — the axis marks now sit on meaningful values (a threshold or milestone), not on round numbers.
16.2.10 Tick Lines
Tick lines get their own styling. The same plot is created again and again, changing one parameter at a time. In the demo: the major tick color is firebrick, the major tick width is 3 (the tick thickness is visibly three), the minor tick color is orange, and no ticks at all are drawn on the y axis. Beyond color and width, the demo sets the tick in/out geometry — the major tick out length is 10, and a minor tick out is set as well (the exact minor value was not stated). The rendered chart shows the firebrick major ticks, the orange minor ticks, the absent y ticks, and the longer outward major ticks. Every visual facet of the tick marks — color, thickness, presence per axis, and protrusion from the axis line — is under our control.
Worked example — styling the tick lines themselves.
p.xaxis.major_tick_line_color = "firebrick"
p.xaxis.major_tick_line_width = 3
p.xaxis.major_tick_out = 10
p.xaxis.minor_tick_line_color = "orange"
p.yaxis.major_tick_line_color = None # no ticks at all on the y axis
- Major ticks — firebrick, three pixels thick, protruding 10 pixels out from the axis line (the tick in/out geometry controls how far the mark extends into the plot and away from it).
- Minor ticks — orange, smaller than the majors, giving finer guidance between the major marks.
- Y axis — no ticks at all, by setting the tick color to
None.
Sense-check. Firebrick major ticks, orange minor ticks, a bare y axis, and visibly longer outward majors — color, thickness, presence per axis, and protrusion are each controlled independently.
16.2.11 Grid Styling
Grids are the last styling layer. The demo starts with a scatter plot and says: no line color for the x grid (the x gridline is switched off entirely), and for the y grid an alpha of 0.5 with a dashed line style — a 6-4 dash pattern. Rendered, the chart shows no vertical grid at all, while the horizontal grid lines are faint, thin, light dashes. The x grid and the y grid are styled independently through their own properties.
A second demo handles the minor grid lines. The minor-line parameters set the color of the grid's minor lines — navy here — and their alpha. The minor grid alpha of 0.1 is so low that the minor lines are nearly invisible on screen, which is exactly what a very light minor grid should be: subtle guidance rather than noise. So both major and minor grid lines can be styled.
Worked example — major and minor grid lines.
p.xgrid.grid_line_color = None # x grid switched off entirely
p.ygrid.grid_line_color = "grey"
p.ygrid.grid_line_alpha = 0.5
p.ygrid.grid_line_dash = [6, 4] # 6 pixels on, 4 pixels off
p.xgrid.minor_grid_line_color = "navy"
p.xgrid.minor_grid_line_alpha = 0.1
Rendered, the chart shows no vertical grid at all, while the horizontal grid lines are faint, thin, light dashes (the 6-4 dash pattern draws 6 pixels of line then 4 pixels of gap). The minor lines are navy at an alpha of 0.1 — so faint they are nearly invisible on screen, which is exactly what a very light minor grid should be: subtle guidance rather than noise.
Sense-check. One axis has no grid, the other has a soft dashed grid, and the minor grid adds a barely-there navy texture — the two axes and the two grid levels are styled entirely independently.
16.2.12 Bands, Hatching, and Grid Bounds
The band control decides how much of the plot the grid applies to. The demo sets the grid line color to none, then gives the band a fill color and an alpha, applied to the y grid — the result is that the grid area on the y side is colored by the band fill. This is how you manage the area behind (or between) grid regions.
A second band example uses a hatch instead of a plain fill: the band pattern is set to a horizontal hatch, light gray, with a specified hatch weight. The grid region is filled with that patterned texture rather than a solid color. Both plain band fills and patterned hatches are available.
Finally, grid bounds: we can decide in which area of the grid the grid commands apply. The demo sets the bounds between 2 and 4, and in that strip — between 2 and 4 — there is no grid at all; everywhere else the grid rules apply. Bands, hatching, and grid bounds together let us decorate only the parts of the chart we care about.
Worked example — bands, hatching, and grid bounds.
# a band fill behind the y grid: the grid area is colored, not just the lines
p.ygrid.band_fill_color = "lightblue"
p.ygrid.band_fill_alpha = 0.4
p.ygrid.grid_line_color = None # grid lines off; only the band shows
# or a patterned band instead of a solid fill
p.ygrid.band_fill_color = None
p.ygrid.band_hatch_pattern = "horizontal"
p.ygrid.band_hatch_color = "lightgray"
p.ygrid.band_hatch_weight = 2
# grid bounds: apply grid commands only outside the 2-4 strip
p.grid.bounds = (2, 4)
- Band — the strip of the plot where the grid sits takes the band fill color and alpha; with the grid line color set to none, the band alone colors the grid area on the y side.
- Hatch — instead of a solid fill, the band carries a horizontal light-gray hatch at the given weight — a patterned texture.
- Grid bounds — between 2 and 4 there is no grid at all; everywhere else the grid rules apply.
Sense-check. A chart whose grid area is a soft shaded band (or hatched texture) everywhere except the 2–4 strip, which stays clean — decoration confined exactly to the regions you care about.
That closes the styling tour, and the professor's summary is worth keeping: first we saw how powerful the column data source is — you can literally control the data source, append, delete, and refresh almost in real time — and now we have seen how much we can control when it comes to styling. Between the two, Bokeh gives the user an unusual degree of command over every layer of a chart.
Visual intuition — the ladder in one picture. Imagine a cross-section of the chart as six stacked floors. Floor 6 (top): line/fill/text families — knobs that name what kind of thing each property is. Floor 5: visibility switches — whole elements appear or vanish. Floor 4: the canvas — dimensions, title, background, border. Floor 3: glyphs — size, fill, outline of each mark. Floor 2: axes — labels, line color and width, ticks, formats. Floor 1: grids — lines, bands, hatches, bounds. The takeaway: any visible feature you can name lives on exactly one floor, and reaching it is a matter of walking the property chain — figure → xaxis → major_tick_line_color — like command-dot-dot in a modular language.
Assumptions & scope. Every styling property assumes the element it targets exists: styling the x grid before adding any glyph still renders (grids and axes exist by default), but styling a legend, title, or annotation you never created does nothing visible. The demo pattern of "same plot created again and again, changing one parameter at a time" shows the honest scope of each control — one property changes exactly one visual fact, so debugging a chart means isolating which floor holds the unwanted effect. Styling also assumes reasonable geometry: an alpha of 0.1 is intentionally nearly invisible (subtle minor grid), and a bounds setting hides data only from view — it never filters the data itself, which matters when users later interact with the chart.
Pitfalls.
- Styling the wrong object.
p.circle(...)returns a renderer; the glyph is inside it asr.glyph. Settingr.fill_color = ...does nothing — the property lives onr.glyph.fill_color(or as a parameter on the original call). - Confusing bounds with filtering. Bounds hide the out-of-range data from view but keep it in the source. Tooltips, selections, and streams can still touch hidden points, which surprises users who expected a true filter.
- Tick formats vs. data. The NumeralTickFormatter changes only the label text. Readers can misread 25% as 25 unless the format matches the data's meaning — a percentage format on a decimal-encoded column (0.25) requires the
0.0%form, not plain0.0. - Over-decorating. Every layer accepts color, width, alpha, and dash — a chart with a styled title, border, both grids, bands, hatches, and hand-placed ticks everywhere is the cluttered chart the course's earlier sessions warned about. Style with intent: emphasize data, not decoration.
Recap. Bokeh's styling is a six-floor ladder — general properties, visibility, plot, glyphs, axes, grids — and every visual fact is a settable property on one of those objects. The handoff: these same layered objects (figure, glyphs, widgets) are what the Bokeh server watches and serves to a browser, which is the next stop on the tour.
Real-world & domain. This level of per-layer control is what makes Bokeh charts presentable in real products: financial dashboards format their axes as dollars and percentages, engineering panels colorize grid bands to mark safe operating zones, and scientific figures use dashed, low-alpha grids so data markers stay the loudest thing on the canvas. In business analytics, the visible property alone is a live filter — a manager can turn a competitor's series on and off during a meeting without touching the data. Across the course's design principles (clutter, pre-attentive attributes, white space), these controls are the toolset that lets a developer implement the theory: thin grids for subtle guidance, color used once and deliberately, and nothing on the canvas that does not earn its place.
16.3 Bokeh Server Applications
Hook — from notebook to web application. Everything Bokeh has done so far runs in your own Python session and sends a finished plot to the browser. The Bokeh server flips the direction: the plot lives on a server, the browser is the window into it, and every slider, dropdown, and button you touch sends a message back — so the chart itself becomes an application. This is the component that turns Bokeh from a plotting library into a dashboard platform.
16.3.1 What Bokeh Server Is
Because Bokeh is a very, very interactive tool, it also has a server-side application. Bokeh server is a powerful component that enables creating interactive web applications based on visual applications: we can literally create dashboards and many other things with it. It bridges the gap between Python data analysis and interactive visualization delivered through the web browser. Everything we saw in Tableau-style tools is achievable: dropdowns, sliders, parameter changes, moving fields around, filtering values — all of that can be done with the help of the Bokeh server. Real-world: this is the route to a fully interactive, browser-based analytics application whose widgets drive the charts live.
Intuition — the bridge between Python and the browser. Think of the Bokeh server as a translator working live at a conference: the Python data pipeline (on the server side) and the web browser (on the client side) do not speak the same language, and the server translates both ways, continuously. The analyst writes pure Python — the same code used in the earlier sessions — and the server turns it into a served web page; when the user drags a slider, the browser sends the new value back through the server to Python, which recomputes and sends the fresh chart forward. Where a static Bokeh plot is a delivered document, a Bokeh server application is a two-way conversation.
Compared with the Tableau-style tools. The professor's framing: everything we saw in Tableau-style tools is achievable here — dropdowns, sliders, parameter changes, moving fields, filtering values. The difference is the engine: Tableau builds those interactivities in its own desktop product, while Bokeh server builds them with Python code the developer writes and controls. When to pick which: Tableau-style tools win when speed of assembly by non-programmers matters; Bokeh server wins when the interaction logic must be custom, data-driven, or embedded inside a Python workflow.
16.3.2 Key Concepts: Document, Session, and Callback
Three concepts carry the whole model.
A document is the fundamental unit of Bokeh server — it represents the collection of Bokeh plots and other visualizations that make up one application. The document is served by the server to the client, and it can be modified dynamically through callbacks.
A session represents the connection between a client and the Bokeh server. Each client that connects to the server gets its own session. The server executes the application code afresh for every new connection and creates a new document, so every session is managed independently — widget value changes and similar events run in parallel across clients. Per-client sessions are exactly what makes this a web interactive application rather than a static one.
A callback is what executes the response to a user action: if a user moves a slider or selects from a dropdown, the callback fires and updates back to the server, which is how the application can behave differently based on interaction. Document, session, and callback together form the key flow that makes Bokeh server flexible and web-interactive.
Analogy — the restaurant model. A document is the menu-plus-kitchen of the restaurant: the fixed set of plots and widgets that make up the application. A session is one table's experience: every group that walks in gets its own table (its own fresh copy of the app), and what one table orders never changes what another table sees — even though the same kitchen runs all of them at once. A callback is the waiter: when a diner asks for more salt (a slider move), the waiter carries the request to the kitchen (the server), the kitchen adjusts the dish (recomputes the chart), and the updated plate comes back to exactly that table. Where the analogy breaks: a waiter serves one table, but Bokeh's server code serves many sessions at once — the isolation of sessions, not the one-to-one staffing, is the point.
16.3.3 How a Bokeh Server Application Works
The working flow, step by step:
Worked example — the six-step server flow, traced on a small app. The demo app: a slider that controls the frequency of a sine wave, and a plot that redraws when the slider moves.
- Create a Bokeh application. We define a script that sets up the Bokeh document — with plots, widgets, and callbacks. In code, this is a function that receives the current document and fills it:
from bokeh.layouts import column
from bokeh.models import Slider
from bokeh.plotting import figure, curdoc
def app(doc):
slider = Slider(start=0.5, end=5.0, value=1.0, step=0.1, title="frequency")
p = figure(width=500, height=300)
line = p.line([0], [0], line_width=2)
def update(attr, old, new):
import numpy as np
x = np.linspace(0, 10, 200)
line.data_source.data = {"x": x, "y": np.sin(slider.value * x)}
slider.on_change("value", update) # attach the callback to the widget
doc.add_root(column(slider, p))
app(curdoc()) # run the application
- Define the layout. The same script defines what the layout of the application will be — here, the slider stacked above the plot in one column.
- Add the callbacks. A callback is nothing but a description of how the document should update in response to user interaction — a mouse click, a mouse drag, or the click of a button. The
updatefunction above is the description: "when the slider value changes, recompute the sine wave". - Write the callback behavior. We decide what the callback function should do once those updates arrive from the respective documents. Here the behavior is: take the slider's new frequency, regenerate the x–y pairs with NumPy, and write them into the line's data source.
- Attach the callbacks to widgets. Each callback is attached to the appropriate widget: for this scatterplot we attach this widget, to that one we attach that dropdown. Based on those values, the chart refreshes. The single line
slider.on_change("value", update)is the attachment. - Run the Bokeh server. The commands get executed, the server starts, it listens, and it behaves and acts on requests. Then we open a web browser, navigate to the server, and it serves the interactive visualization application to the client.
Trace of a tiny interaction. The app starts with the slider at 1.0 and the chart drawing . The user drags the slider to 2.5. The browser sends the new value; the server fires the update callback; Python recomputes and pushes only the new data points to the browser; the line redraws with the higher-frequency wave. A second user opening the same URL gets her own session at 1.0 — the first user's slider position does not leak into her chart. That is the whole model in one interaction: server + document + callback.
Sense-check. One slider, one plot, one line of attachment code — and the behavior matches the description: the chart refreshes exactly when the slider value changes, and only for the user who changed it.
That is the basic flow of the Bokeh server: the key parts are the server, the document, and the callback — how the document should behave whenever an action triggers a callback.
Complexity & cost. The flow is simple in concept but carries the cost of a running process: the server holds every session's document in memory, one per connected client, so heavy applications with many users multiply their memory use. Because each interaction round-trips through Python, the callback must be fast — a callback that does a five-second query freezes the chart's responsiveness for that user. When the workload is a live stream (sensor data, tickers), the stream method from section 16.1 becomes the pattern: the server pushes only the appended rows to each session.
Visual intuition — the flow as a cycle. Draw the diagram as a loop: browser on the left, server in the middle, Python app on the right. Arrows: the browser sends an event (slider drag, dropdown change, button click) to the server; the server invokes the attached callback; the callback updates the document; the document's changes flow back to the browser, which redraws. The loop repeats for every user action, with one independent loop per session. The landmark to notice: the callback is the only part the developer writes — the server and the browser plumbing are already built. Takeaway: the application is the document, the interaction is the callback, and the session is the isolation.
When to use / alternatives, and the traps.
When this is the right tool: any dashboard that must react to user input (sliders, dropdowns, filters), any app that needs live data flowing into the browser, and any multi-user analytics tool where each viewer's view must stay independent.
Alternatives: a static Bokeh plot with output_file and show is enough when the chart never changes after delivery — no server needed. Tools like Tableau or Power BI cover the same dashboard territory without code. And for one-off exploratory charts in the notebook, the server adds process management with no payoff.
Traps:
- Forgetting that sessions are independent. If the app keeps global variables outside the document, one user's slider position can leak into another user's chart — the number-one real-world bug in server apps. State that must be per-user lives in the session's document, not in globals.
- Slow callbacks. Every slider move fires the callback synchronously; expensive work makes the UI feel frozen. Precompute what you can and keep the callback thin.
- Scaling naively. One process serves all sessions; a few hundred concurrent users can exhaust it. In production, the app runs behind a load balancer with several server processes — not a concern for learning, but the moment a demo becomes a product, it matters.
Recap. The Bokeh server serves one document per session and lets callbacks update each document in response to user actions — create the application, define the layout, add callbacks, attach them to widgets, run the server — and the result is a browser-based interactive application built entirely in Python. The handoff: this is the last piece of Bokeh the course covers; next, the whole course is pulled together in a fifteen-session recap.
16.3.4 Future Trends and Opportunities
The agenda for this final session promised one more item: the future opportunities and trends for Bokeh — which fields it is likely to move into further and what to expect from Bokeh in the near future. In the event, the class moved straight from the Bokeh server into the full course recap, and the course ended there, so no specific trend details were delivered in this session. The announced topic remains a useful one to follow on the project's own roadmap: interactivity and server-driven dashboards are the directions Bokeh keeps pushing, and the column data source + server combination is the engine behind them.
Real-world & domain. The Bokeh server is the deployment layer of real analytics products: engineering teams serve live process dashboards over the web, finance teams build parameter-exploration tools where analysts drag sliders over model assumptions, and data-science consultancies ship interactive proof-of-concept apps to clients without any web-framework code. In the broader landscape, the server closes the gap this course traced from the start — data visualization as a tool of collaboration and decision-making (the session-1 framing) needs an audience to interact with it. Tableau-style tools showed that market exists; Bokeh server is the open, code-driven route into it, and the ColumnDataSource plus server combination is the engine behind the live-updating dashboards that run on it.
16.4 Full Course Recap (Sessions 1–15)
The final block of the session was a quick recap of all 15 earlier sessions, drawn from the recap slides of every previous deck — a memory refresh of the whole course. It is the strongest revision resource of the course: one guided sweep from the foundations of visualization to the Python libraries that close it. Each item below is a compressed version of a full earlier session; use them as anchors and go back to the original decks for detail.
16.4.1 Session 1 — Foundations: Overview, Use Cases, Exploratory vs Explanatory Analysis
The journey began with a data visualization overview: the best practices, and the use cases — finance, the medical field, and many others where data visualization plays a very important role. In this digital age, when there is a lot of data and many tools available, the importance of data visualization is even greater. Visualization works as a discovery tool: it helps find hidden insights, explore and inquire into information, and it fosters collaboration. It is also becoming a skill for the masses — learning is easy, tools are plentiful, and their acceptability is likely to keep growing.
One of the key concepts of the first class was the difference between exploratory and explanatory analysis.
The trek-and-map analogy (the professor's own). Exploratory analysis is like going on a trek where you do not know the path — you are exploring new things; explanatory analysis is like, after the hike is done, drawing the map of how you reached there, and telling others what the map should be. The two are two phases of one journey, not two kinds of charts.
Formally: exploratory analysis is used to explore and understand the data, uncover patterns, trends, and outliers, and generate initial hypotheses; explanatory analysis is used to confirm those initial hypotheses identified in the exploratory phase, explain the underlying relationships, and draw conclusions. The audiences differ too: exploratory analysis is mostly for the analysts themselves, while explanatory analysis targets a broader audience — stakeholders and decision makers, because now you are telling them what the findings are.
Related to this, we learned why the context of data presentation matters: context is what we need to engage our end users. A chart is not self-explanatory; its message lands only when the audience's background and needs shape how it is framed.
16.4.2 Session 2 — Choosing Effective Visuals: Cheat Sheets, Cluttering, Pre-Attentive Attributes
The second session was about choosing an effective visual. There were cheat sheets: if we have a time series, which chart should we take; if we have a comparison of values, which chart should we take — in which scenario which chart is better. The cheat-sheet logic is the course's recurring decision procedure: match the question you are answering to the chart type designed for it.
Then came cluttering, one of the most neglected areas of visualization. The rule of thumb: the less content in a graph, the better. White space is a friend of charts — the more white space a chart has, the easier it is for the user to grasp or understand the data. Pack too many things onto a chart and the end user struggles to understand it.
We also met pre-attentive attributes with a live exercise: a grid of about 20–25 gray numbers, and the request to count the threes. The average time was some seven to ten seconds. Then the threes were shaded black while the rest stayed gray — and within a second, with no effort at all, everyone could count the threes.
Worked example — the counting-threes exercise. Twenty to twenty-five gray numbers sit in a grid (for example, four lines of twelve digits, like 756395068473 / 658663037576 / 860372658602 / 846589107830). Task: count the threes.
- Before shading: participants hunt digit by digit, line by line, treating every "3" as one small shape to find among many. Result: seven to ten seconds on average, with real effort and some missed threes.
- After shading: the threes are colored black, everything else stays gray. The answer is visible in under a second, with no effort at all — the threes "pop" because the brain processes the color difference before any deliberate scanning.
Sense-check. The numbers did not change, only their shading — and the task time fell from seconds to an instant. That speed-up is the whole point: pre-attentive attributes are properties we notice unconsciously, immediately, without any effort. Shading, color, size, and position are the tools that put a viewer's attention exactly where we want it.
Finally, some good design concepts: formatting typically flows from the top left corner — that is where information starts to flow; keep a consistent color scheme and theme; and keep the flow of the story or the dashboard consistent. These are the design principles to implement in every visual.
16.4.3 Session 3 — Histograms, Glyph Charts, and the Tools Landscape (Gartner Quadrant)
We saw demos of charts used to compare categories: histograms, and a radial (circular) chart among them. On the same class we started the journey with data visualization tools, and with the question of how many visualization tools exist. That is where the Gartner quadrant came in: every year Gartner publishes the top tools in the industry, and in the data visualization tool category the three top leaders were Microsoft (Power BI), Tableau, and Qlik. The quadrant is the industry's yearly report card: leaders sit top-right, and the placement shifts as products and markets evolve.
From there the tool journey went deeper. There are desktop-based tools — Tableau Desktop and the Power BI desktop version. There are online visualization tools — Tableau Public, and Power BI online as well. In addition to full tools there are visualization libraries — Python's pyplot, Matplotlib, and the other libraries this course went on to learn. And there are open-source and proprietary tools, each with its place. A useful way to file the landscape: full platforms (Power BI, Tableau, Qlik) for end-to-end analytics products, template studios (Flourish) for fast polished visuals, and code libraries (Matplotlib, Bokeh, and the rest) for full control inside a programming workflow.
16.4.4 Flourish Studio: Template-Driven Visuals
One class included a demo of Flourish Studio, a free, interactive, very rich studio with hundreds of templates. The workflow: choose a template, give it the data source, and map which field goes to which column — after that the studio takes care of everything itself.
Worked example — Flourish in under 30 minutes. In a single session, the demo produced several beautiful, animated graphs in less than 30 minutes. The steps: pick a template from the library (hundreds available), upload or paste the data source, and drag the fields onto the template's columns (which column holds the category, which holds the value, which drives the animation). The studio renders the visual, animates the transitions, and handles the polish.
Sense-check. No code was written and no default was left to chance — template choice plus field mapping produced a finished animated chart in minutes. That is the measure of how far template-driven tools have come: the entire design work is delegated to the template, and the user's only job is mapping fields to columns.
16.4.5 Tableau: Setup, Data Sources, and the Visual Interface
The Tableau journey covered the full stack. We saw the components of Tableau and how to install it: a one-year subscription, or Tableau Public — either works, though Tableau Public has limited features. Data connections offer three types of options: file-based, server-based, and saved data sources. And we learned the live-connection versus extract distinction: with a live connection, whenever anything changes in the data source, the visuals change; the extract feature is for working offline.
Tableau's data types: six types — number, string, geospatial, boolean, and the rest. The data interpreter was shown at work on messy data: Tableau has the intelligence to guess what a field can be when data is not clean, so a merged cell can be broken up into logical columns and logical values — a feature for data that is not clean and structured.
Viewing data: after loading, right-click the data source and quickly view the data — how many rows and columns there are. The Tableau visual interface was toured in detail: the shelf and filter area; the dimensions pane on the left where all the fields appear; the top bar where rows and columns can be placed; the chart types in the top right corner; the sheets tabs at the bottom; and the dashboard and story tabs.
Two concepts matter for placing values correctly: dimensions and measures. And we learned to create hierarchies — logically grouping fields, so product can have product type, and region, and country can have its own levels. Sorting and grouping were part of the same session.
16.4.6 Tableau Maps and Actions
Maps: Tableau has an inbuilt geospatial database that captures longitude and latitude. In addition, we can upload our own geospatial data as long as the names are consistent with what Tableau expects — then it interprets them; if it cannot recognize something, it gives an error, and we fix it (a spelling mistake, for example). Besides the inbuilt geospatial data, custom geocoding data can be uploaded.
Actions: we added filter by action, highlight by action, and go to URL actions to sheets. The setup involves a source sheet and a destination sheet: we tell Tableau that if I do this on this sheet, highlight that in the destination sheet, or filter it. The example: a US city map where clicking a city on the map opened the Wikipedia page for that city — the city name field was merged into the Wikipedia URL, and the page opened for us. Actions turn static dashboards into navigation tools.
Intuition — sheets, actions, and the click-through. The source sheet owns the click, the destination sheet owns the response, and the action is the bridge between them. In the Wikipedia example the bridge was a URL: click a city → Tableau builds https://en.wikipedia.org/wiki/<city-name> from the data field → the browser opens the page. Replace the URL with a highlight or a filter and the same bridge links any two sheets of a dashboard — the mechanism that turns a dashboard from a report into a navigation tool.
16.4.7 Dashboards: Types, Characteristics, and Mistakes
With individual sheets mastered, we moved to dashboards: a brief introduction, the difference between a report and a dashboard, and the benefits of dashboards. There are different types — strategic, operational, analytical, and tactical — who uses each, and in which scenario which dashboard should be used. We learned the key characteristics of a good dashboard, and that dashboards can use both quantitative data and non-quantitative data — sometimes we only have to share, say, a newsletter or other non-quantitative content. We also covered the mistakes, or do-nots, of dashboard design: what to avoid when it comes to designing, displaying, and the data itself.
| Dashboard type | Typical user | When it fits |
|---|---|---|
| Strategic | Executives | High-level health of the business, checked occasionally |
| Operational | Front-line teams | Day-to-day activity, watched constantly |
| Analytical | Analysts | Deep exploration of why numbers move |
| Tactical | Managers | Tracking progress toward a specific goal |
The core message of the session: a dashboard is a display of the most important information arranged for rapid scanning — if a screen cannot be read in a glance, it has stopped being a dashboard and become a report.
16.4.8 Perception, Gestalt, and DIY Platforms
One extra topic (not in the course handout) was DIY platforms: what these DIY data visualization tools are, what they are not, and what the future of these platforms looks like. In the same class we studied the power of visual perception — how the brain stores information, including short-term memory and visual encoding, and how these must be considered when building a dashboard. Then came the Gestalt principles: a variety of principles, briefly touching foreground–background and many others. And because people often get confused, we ran a quick comparison of pre-attentive attributes versus Gestalt principles — what each contributes and where they differ.
| Pre-attentive attributes | Gestalt principles | |
|---|---|---|
| What they are | Properties noticed instantly and without effort (color, size, position) | Rules of how the brain groups and organizes what it sees (proximity, similarity, closure, continuity) |
| Time scale | Sub-second, before conscious thought | Constant, automatic organization of the scene |
| Job in a visual | Draw the eye to a specific element | Decide how elements are perceived as groups and wholes |
| Practical use | Highlight the one number that matters | Lay out a chart so related items read as a unit |
The payoff: principles of visual perception for dashboard design, so that our dashboards become more attractive and easier to read.
16.4.9 Stories: Worksheet vs Dashboard vs Story
We compared worksheet, dashboard, and story — what each is and how we typically start with dashboards and stories. The hands-on session built several dashboards: panes placed next to each other, horizontally or vertically, and how to set up the layout. We used tile and floating objects, image objects, containers, and dynamic sizing, plus different dashboard formatting and actions added inside dashboards (opening a country map, for example). We learned dashboard best practices, the Tableau story, the seven types of data stories recognized by Tableau, and the characteristics of dashboards.
| Level | What it holds | Role |
|---|---|---|
| Worksheet | One chart built from one view of the data | The building block |
| Dashboard | Several worksheets arranged on one screen | The display for scanning and monitoring |
| Story | A sequence of dashboards/worksheets in order | The narrative that leads the audience through a conclusion |
The session's lesson: a worksheet shows, a dashboard displays, and a story explains — and the seven story types recognized by Tableau are the narrative shapes (change over time, drill-down, zoom, contrast, intersection, factors, and the like) that a data narrative can take.
16.4.10 Python Libraries: Matplotlib, Bokeh, and Seaborn
The course ended where Python began: we went into Matplotlib — what its components are, its architecture, and some basic plots. Then Bokeh, in the last few sessions, with its focus on interactivity; and Seaborn in the second-last class, where the comparison between Matplotlib and Seaborn was made, later extended to include Bokeh. The course wrapped up with Bokeh — today's final details of the column data source, layered styling, and server applications.
| Library | Strength | Best for |
|---|---|---|
| Matplotlib | Mature, complete, publication-grade static plots | Every standard chart, maximum control over detail |
| Seaborn | High-level statistical charting, built on Matplotlib | Attractive statistical plots with little code |
| Bokeh | Interactivity: data sources, widgets, server apps | Live and interactive web visualizations |
With that, all contents were covered: the course is complete. The final words of the session were for the students — all the best, for the exams and for the careers ahead.
Recap. The course's arc, in one breath: visualization as a discovery and communication tool (session 1); choosing the right chart and fighting clutter with pre-attentive attributes and design principles (sessions 2–3); the tools landscape from Gartner's quadrant to Flourish, Tableau, and the Python libraries (sessions 3–15); and finally the Bokeh engine — data source, layered styling, and server — that powers interactive visualizations (session 16). Everything connects: the same principles of perception and clarity apply whether the visual is drawn in Tableau, Flourish, or Bokeh.
Real-world & domain. The recap's named industries are the same ones from session 1: finance and the medical field, where visualization sits at the center of the work — a finance team comparing regional sales in a live Bokeh dashboard, a hospital board reading a strategic dashboard of operational metrics, a pharma analyst exploring a dataset before explaining findings to stakeholders. The thread that ties all 15 sessions together is the move from seeing the data yourself (exploratory) to showing others what it means (explanatory) — and every tool in the course exists to make one of those two jobs easier.
Exam Guidance Summary
The final session carried no exam-specific instructions — no mark distribution, question pattern, or study tips were announced. What it did provide is the strongest revision resource of the course: the full recap of all 15 sessions above. Treat it as the map for revision.
Exam note: if you need to refresh any topic, work through the recap in order — visualization foundations and exploratory-versus-explanatory analysis, chart selection and cluttering, pre-attentive attributes, design principles, the tools landscape (Gartner's quadrant: Microsoft Power BI, Tableau, Qlik), Flourish Studio, the Tableau stack (data connections, live versus extract, data types, data interpreter, dimensions and measures, hierarchies, maps and actions), dashboards and their types, visual perception and Gestalt, stories, and finally the Python libraries (Matplotlib, Bokeh, Seaborn).
Within the final session's own material, the most examinable pieces are the conceptual cores: what a ColumnDataSource is and the three parameters a glyph needs (x, y, source); the five data operations (add column, replace, DataFrame, stream, patch) and the type-consistency rule for stream and patch; the six-layer styling ladder and what each layer controls; and the Bokeh server trio — document, session, callback — with the six-step flow for building a server application. For each, the reliable way to revise is to reproduce the small worked examples from the session: the dictionary circle plot, the visible-property toggle, and the slider-driven server app.
The session closed by wishing everyone good exams and a wonderful future.
Key Industry Applications
- Interactive dashboards: Bokeh's ColumnDataSource powers runtime filtering, tooltips with real-time data points, and live-updating charts — the foundation of interactive analytics applications. Real-world: business analytics, scientific visualization, and real-time graphs are all named as beneficiaries of CDS.
- Server-side web applications: Bokeh server bridges Python data analysis and the web browser, delivering dashboards with dropdowns, sliders, and parameter controls — the tooling behind browser-based analytics products.
- Database practice: ColumnDataSource is compared to Oracle-style database views — join and merge the data once, hand the processed result to the glyphs.
- The analytics tools landscape: Gartner's yearly quadrant places Microsoft Power BI, Tableau, and Qlik as the three leaders in the data visualization tool category; desktop (Tableau Desktop, Power BI Desktop) and online (Tableau Public, Power BI online) options both exist, plus open-source Python libraries (Matplotlib, Seaborn, Bokeh).
- Template-driven tools: Flourish Studio — free, hundreds of templates, map fields to columns and get animated graphs, shown producing many charts in under 30 minutes.
- Tableau in practice: Tableau Public for limited-feature free use; live connections for real-time visuals versus extract for offline work; the data interpreter for cleaning messy data; inbuilt and custom geocoding for maps; and URL actions that link charts to the web (the Wikipedia city-map example).
- Domain use cases: finance and the medical field are the recurring named industries where data visualization plays a central role.
The pattern behind all of it. Every application above is the same workflow at a different scale: get the data into a structured form (a DataFrame, a Tableau data source, a template's columns), build the visual (glyph, sheet, dashboard, story), and hand the audience a way to interact with it (tooltips and stream, actions, sliders). The skills from this course — choosing the right chart, respecting perception and white space, and controlling every layer of a tool — are what separate a chart that merely displays data from a visualization that drives decisions.
DVI Lecture 16 notes · Bokeh Wrap-Up: ColumnDataSource, Layered Styling, Bokeh Server, and Course Recap
Sections Breakdown
The data table behind every Bokeh plot: created implicitly when lists or NumPy arrays are passed, and explicitly to unlock add, replace, DataFrame, stream, and patch operations that power runtime interactivity.
A six-layer styling ladder: general line/fill/text properties, the visible toggle, plot-level canvas styling, glyph-level styling, axes with tick formats, and grids with bands, hatching, and bounds.
The server-side mode that turns plots into live web applications: documents hold plots and widgets, each client gets its own session, and callbacks update the document on user interaction.
A guided sweep through all fifteen earlier sessions: visualization foundations, chart selection and clutter, pre-attentive attributes, the tools landscape, Flourish, Tableau, dashboards, perception and Gestalt, stories, and the Python libraries Matplotlib, Seaborn, and Bokeh.
No exam-specific instructions were announced in the final session; the fifteen-session recap is the revision map, with ColumnDataSource, the styling ladder, and the Bokeh server trio as the most examinable cores.
Interactive Bokeh dashboards, server-side analytics applications, database-style data handling, the Gartner tools landscape, template-driven Flourish visuals, Tableau in practice, and the finance and medical industries.
Exam Revision Notes
Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.
Session Overview
Must-know: The final session covers ColumnDataSource, layered styling, Bokeh server, and the full course recap.
Self-check: What are the four agenda items of the final session?
Connects to: ColumnDataSource — Bokeh’s Data Backbone (16.1), Controlling Visuals Across Bokeh’s Layers (16.2), Bokeh Server Applications (16.3), Full Course Recap (16.4)
ColumnDataSource — Bokeh’s Data Backbone
Must-know: A ColumnDataSource stores data as named, equal-length columns; glyphs reference columns by string names with the minimum parameters x, y, and source; add column via source.data[key], replace all via source.data = {...}, stream appends rows (sending only new data to the browser), and patch updates specific cells.
⚠️ Top pitfall: Passing raw lists instead of column-name strings when a source is present, and appending text into a numeric column via stream or patch.
Self-check: Which three parameters are required when a glyph reads from a ColumnDataSource, and what does each one name?
Connects to: Controlling Visuals Across Bokeh’s Layers (16.2), Bokeh Server Applications (16.3)
Controlling Visuals Across Bokeh’s Layers
Must-know: Styling layers run from general line/fill/text families, visible property, plot level (figure canvas), glyph level (r.glyph), axis (p.xaxis/p.yaxis), to grids (major/minor lines, band fills, hatches, bounds); alpha controls transparency from opaque (1) to invisible (0).
⚠️ Top pitfall: Setting properties on the renderer (r) instead of the glyph object (r.glyph), and confusing axis bounds (view-only) with data filtering.
Self-check: What does alpha control, and what do the axis bounds (2, 4) do to the chart?
Connects to: ColumnDataSource — Bokeh’s Data Backbone (16.1), Bokeh Server Applications (16.3)
Bokeh Server Applications
Must-know: Document = the collection of plots and widgets of one application; session = one client’s independent connection; callback = the function that updates the document on user action; flow: create application, define layout, add callbacks, write callback behavior, attach callbacks to widgets, run the server.
⚠️ Top pitfall: Storing per-user state in global variables instead of the session’s document, leaking one user’s interactions into another user’s session.
Self-check: Why does each client that connects to the Bokeh server get its own independent session?
Connects to: ColumnDataSource — Bokeh’s Data Backbone (16.1)
Full Course Recap (Sessions 1–15)
Must-know: The course recap arc: exploratory analysis generates hypotheses for the analyst, explanatory analysis confirms and communicates them to stakeholders; white space is a friend; pre-attentive attributes (color, size, position) are noticed instantly; Gestalt principles describe perceptual grouping; the tools landscape: Gartner quadrant leaders Power BI, Tableau, Qlik; Tableau dimensions vs measures; dashboards: strategic/operational/analytical/tactical; story vs dashboard vs worksheet; Python libraries Matplotlib, Seaborn, Bokeh.
⚠️ Top pitfall: Confusing pre-attentive attributes with Gestalt principles: pre-attentive attributes draw attention instantly (sub-second), Gestalt principles govern how the brain groups what it sees.
Self-check: What is the difference between exploratory and explanatory analysis, and who is each audience for?
Connects to: ColumnDataSource — Bokeh’s Data Backbone (16.1), Controlling Visuals Across Bokeh’s Layers (16.2), Bokeh Server Applications (16.3)
Exam Guidance Summary
Must-know: No exam intel was announced in the final session; revise via the 15-session recap in order, and reproduce the final session’s worked examples (dictionary circle plot, visible-property toggle, slider-driven server app).
Self-check: Which three parameters must a glyph always pass when reading from a ColumnDataSource?
Connects to: ColumnDataSource — Bokeh’s Data Backbone (16.1), Controlling Visuals Across Bokeh’s Layers (16.2), Bokeh Server Applications (16.3)
Key Industry Applications
Must-know: ColumnDataSource powers interactive dashboards and live-updating charts; Bokeh server powers browser-based analytics applications; finance and the medical field are the recurring named industries for data visualization.
Self-check: Which industries are named as the recurring use cases for data visualization?
Connects to: ColumnDataSource — Bokeh’s Data Backbone (16.1), Bokeh Server Applications (16.3), Full Course Recap (16.4)
Was this lecture useful?
BitsNotes AI Assistant
Subject Notes AssistantConfigure AI Chat
Choose how to access the chatbotSigned in as
Powered by BitsNotes — 20 messages per day. No API key needed. Want unlimited access? Use "Bring Your Own Key" mode.
Sign in to use AI Chat
Get 20 free AI messages per day to ask questions about your lecture notes. Sign in with Google or GitHub — it takes 5 seconds.
Sign In to BitsNotesSwitch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.