My five favorite cards in Aura Dashboards

When Dashboards first became available in Aura, I started to move my reporting away from custom tools. Here are the five cards I use the most, and a Cypher statement behind them.

Before Aura Dashboards I built our course analytics from scratch, mostly in Python, and then either shared the output with the team or had them log in to code I'd written. Now they log in to a dashboard. They access the data live through the Aura console.

Access to the data means they can solve problems they couldn't before. Support emails, requests, and the "I can't find my certificate" kind of query are now self-served within a dashboard that's available to the whole team.

These are the five cards I use most, and the Cypher behind them.

1. Sometimes all you need is a number

A card displaying a single number clearly and concisely

The single value card takes a Cypher statement that returns one row and one column.

cypher
RETURN COUNT { (:User) }

This card shows the number front and center for all to see.

2. Inline comparisons with line charts

The All time enrolment growth card, a line chart on a logarithmic axis from 100 to 100 000. Months 1 to 12 along the bottom and one series per year from 2022 to 2026. Every series except 2022 sits in a tight band between roughly 7 000 and 13 000, crossing repeatedly.

I often need to compare changes over time. The card I open first draws the last few weeks of enrollments and the few weeks before them as two lines, so I can see which way the numbers are going.

Each row the query returns is one day, with this period's count in one column and the previous period's count in another.

cypher
WITH $daysToShow as daysToShow
 
MATCH (e:Enrolment)
WHERE e.createdAt >= datetime() - duration({days: (daysToShow+2)*2})
 
WITH daysToShow, date(e.createdAt) AS date, duration.inDays(datetime.truncate('day', e.createdAt), datetime.truncate('day')).days AS days, count(*) AS count
 
WITH daysToShow, collect({
  date: date,
  days: days,
  count: count
}) AS days
 
UNWIND range(1, daysToShow) AS day
RETURN date() - duration({days: day}) AS day,
  [ row IN days WHERE row.days = day ][0].date AS thisMonthDate,
  [ row IN days WHERE row.days = day ][0].count AS thisMonthCount,
  [ row IN days WHERE row.days = day+daysToShow ][0].date AS lastMonthDate,
  [ row IN days WHERE row.days = day+daysToShow ][0].count AS lastMonthCount
ORDER BY day DESC

$daysToShow sets the length of each period. The query pulls (daysToShow+2)*2 days of enrollments, buckets them by how many days ago each one was created, then uses UNWIND to walk back a day at a time. thisMonthCount reads the count at that offset and lastMonthCount reads the same offset shifted by daysToShow, which is what lays one period on top of the other.

The card uses the day as the category and the enrollment count for each month as the values, so the two periods share an axis.

The Enrolment trends card in the Aura console, configured as a line chart with day as the category and thisMonthCount and lastMonthCount as values, linear scale. Two series cross repeatedly between roughly 140 and 560 daily enrollments through July.

My "All time enrolment growth" card is a line chart with one series per year and months along the bottom.

3. Making a static dashboard dynamic with filters

$daysToShow comes from a filter card which turns a static dashboard into one that can be updated to answer different questions. You create a parameter, give it a default value, and the cards that reference it re-run when the value changes. Reducing the value to 7 turns a monthly view into a weekly one.

This can also be used for property lookups. On our Users dashboard, we use a filter to look up users by email address and display the information about that user below. Anyone answering a support email can look the learner up themselves instead of asking me to run a query.

4. Nothing beats a table

The Enrolment table card. Columns for course, count, completed and completion, one row per course, sorted by count descending. Showing 1 to 10 of 70 results.

A table gives me the actual numbers rather than a chart I have to interpret. I can sort by any column, and the card splits the rest across pages.

The Enrolment table shows how many people have started each course and how many have finished it, which is how we tell which courses are working.

The properties of a node or relationship naturally are returned as rows in a table. We use this trick to unwind the list of property names, to reorient the data into rows and columns to work in the user view of our user dashboard.

cypher
MATCH (u:User {email: $email})
WITH u LIMIT 1
 
UNWIND [
  {
    key: 'Profile', // (1)
    property: 'id', // (2)
    prefix: 'https://graphacademy.neo4j.com/u/' // (3)
  },
  {key: 'Country',       property: 'country'},
  {key: 'Job Role',      property: 'role'},
  {key: 'Company',       property: 'company'},
  {key: 'LinkedIn',      property: 'linkedin'},
  {key: 'X (Twitter)',   property: 'twitter'},
  {key: 'Unsubscribed?', property: 'unsubscribed'}
] AS row
 
RETURN row.key AS key,
       coalesce(row.prefix, '') + u[ row.property ] AS value
  1. 1
    • property is the property to read on the node, and u[ row.property ] looks it up by name.
  2. 2
    • key is what I want to display in the left column.
  3. 3
    • prefix provides a string to be placed before the value.

The User information table card. Two columns, key and value, with one row per property: Profile as a graphacademy.neo4j.com link, Country GB, Job Role Manager Developer Education, Company Neo4j, LinkedIn, X (Twitter), and Unsubscribed? false. Showing 1 to 7 of 7 results.

5. Side by side comparisons with bar charts

The Year-on-year card displays the numbers on a logarithmic axis, with the count up the side and the months across the bottom, and a bar each for 2025 and 2026.

The line chart above looks great, but it hid a serious problem that I only spotted when I looked at the same data another way, using a bar chart. Displaying two values side by side made it obvious that we had a drop in numbers that needed to be addressed.

A decline in enrollment numbers wasn't apparent with all of those lines on the line chart, each one competing for attention.

Viewing the two numbers side by side made this discrepancy apparent.

This query counts enrollments by month, then returns one row for each of the twelve months with a column for 2025 and a column for 2026.

cypher
MATCH (e:Enrolment)
WHERE e.createdAt >= datetime.truncate('year') - duration('P1Y')
  AND e.createdAt <= datetime.truncate('month')
WITH e.createdAt.year AS year, e.createdAt.month AS month, count(*) AS count
WITH collect({year: year, month: month, count: count}) AS months
 
UNWIND range(1, 12) AS i
 
RETURN i AS Month,
  [ n in months where n.year = 2025 and n.month = i | n.count ][0]  AS `2025`,
  [ n in months where n.year = 2026 and n.month = i | n.count ][0]  AS `2026`
 
ORDER BY Month ASC

Both years are hardcoded in the list comprehensions. datetime.truncate('year') - duration('P1Y') opens the window at the start of last year, and e.createdAt <= datetime.truncate('month') removes the current partial month so the last pair of bars compares two complete ones.

Bonus: the one chart I wouldn't use

Pie charts make it hard to compare values by the size of a slice. If I see one, I instantly wish it was a table or a bar.

Build your own dashboards

Learn everything you need to build your own demo on Neo4j Aura, from Cypher to dashboard cards.

Comments (0)

Loading comments...