Deneb: Timeline with conditional markers

In my quest to make cool stuff in Deneb I took to Reddit to find some suggestions for visuals people would like to see in Power BI that currently are not catered for. Thanks to u/geophizx for your suggestion of a simple timeline visual:

I agree - the timeline visuals in Power BI aren’t very good, and if you are project managing a Power BI project, what would be more logical presenting project updates using Power BI too? Geophizx also really helpfully provided a reference image:

So the main components we are interested in here are having a time line running from left to right, with milestone dates denoted by points on the timeline. Then we want a label and connecting line to those dots. One challenge is the position of the labels seems to be pretty random, so I didn’t worry too much. On the original the labels are both above and below, but that created issues with my marks clipping over my axis so I decided to orient everything upwards. Anyway, here is my attempt:

You’ll notice as well standardising the axis a bit (it feels a bit messy to start a chart on the 23rd of a month!) I’ve also included an option for conditional formatting, so you can also report on the status of a particular event within the same visual. This was just coded into my data. So before I jump into the code, here is the sample data I created. If you would like to use this chart yourself, you’ll need to shape your data akin to mine. You’ll need:

  • A date field with the date of each milestone

  • A position field to determine where the label should sit - this can be anything you like really, it’s quite arbitrary

  • A text field for your labels - again these labels could say anything you like

  • A numerical field with a code for your status. I used 1 for completed, 2 for in progress and 3 for not started, but again you can tweak this to be anything you want. If you wanted to highlight the status using a tooltip you’d just need to add a text field with the status description in.

Similar to my previous attempt, I’m sure this is not the most efficient way to write this visual, but due to the small amount of data it is running very rapidly anyway, so maybe doesn’t matter too mcuh. If anyone knows a way to reuse shared axis instead of writing it out each time (or if you can tell me whether that even matters or not, maybe it doesn’t!) please let me know!

For anyone new to Deneb if you want to create this chart, you need to get the Deneb custom visual into your PBI file. You need your data shaped as per the above (hint: if you name all your fields the same as mine, you don’t have to tweak any code - should be plug and play! Otherwise you just need to replace my field references with the names of your fields). Once you have that, pop your fields into the deneb visual and enter the editor. Then you can just clear whatever is in there and replace it with the below:


{
  "data": {"name": "dataset"},
  "description": "Minimalist Timeline with conditional formatting",
  "author": "Andy Murphy | DaytoDataStuff",
  "layer": [
    {
      "description": "Timeline Points",
      "mark": {
        "type": "point",
        "filled": true,
        "size": 75
      },
      "encoding": {
        "x": {
          "field": "Date",
          "type": "temporal",
          "scale": {
            "domain": [
              {
                "year": 2022,
                "month": "jan",
                "date": 1
              },
              {
                "year": 2022,
                "month": "dec",
                "date": 1
              }
            ]
          },
          "axis": {
            "offset": 15,
            "domain": true,
            "title": false
          }
        },
        "y": {
          "datum": 0,
          "type": "quantitative",
          "axis": false
        },
        "color": {
          "legend": false,
          "field": "Status",
          "type": "nominal",
          "scale": {
            "range": [
              "gold",
              "orange",
              "grey"
            ]
          }
        }
      }
    },
    {
      "description": "Timeline Outline",
      "mark": {
        "tooltip": true,
        "type": "point",
        "filled": false,
        "size": 200
      },
      "encoding": {
        "x": {
          "field": "Date",
          "type": "temporal"
        },
        "y": {
          "datum": 0,
          "type": "quantitative",
          "axis": false
        },
        "color": {
          "legend": false,
          "field": "Status",
          "type": "nominal",
          "scale": {
            "range": [
              "gold",
              "orange",
              "grey"
            ]
          }
        }
      }
    },
    {
      "description": "Timeline Labels",
      "mark": {
        "type": "text",
        "dy": -10
      },
      "encoding": {
        "text": {
          "field": "Milestone Text"
        },
        "x": {
          "field": "Date",
          "type": "temporal"
        },
        "y": {
          "field": "Position",
          "type": "quantitative",
          "axis": "false"
        }
      }
    },
    {
      "description": "LabelLines",
      "mark": "bar",
      "encoding": {
        "x": {
          "field": "Date",
          "type": "temporal"
        },
        "y": {
          "field": "Position",
          "type": "quantitative",
          "axis": "false"
        }
      }
    }
  ]
}

As previous, I’ll post the code out in full here and write any notes to explain what is going on over on this side.

As we have a few distinct visual elements to create this, we need to put everything within a “layer”:[] array.

Here I’ve included descriptions so when troubleshooting you can easily identify what each bit of code does, just makes life easier once the code grows a bit.

First we will build our timeline points. This is pretty easy, we just plot some points and define the size, as well as also defining they should be filled. To get that solid circle with an outline we need to layer two circles, which is why I didn’t bother including the fill status on the config file, as it is different for both point layers.

Now we need to use our “encoding” to assign these marks to axis. If you are “borrowing” this code, then you need to change all the “field” arguments to reflect the names of your data. The “Date” field needs to be replaced with your date field.

We cast our date as a temporal field, and, because our project dates as a bit messy, I’ve also fixed the axis using a “domain” to run between a “neat” set of dates. If you tweak the year / month / date values, you can fix the start and end point of the axis respectively.

Here we just offset the x axis a touch, otherwise our points will occlude the axis labels, so we use the “offset” argument to nudge the data up a few pixels.

Now, for our timeline points, we want them to lay flat along our timeline, so here we are coding the y value they should be plotted at to 0, and we disable the axis - because the position value we use to give height to the labels is completely arbitrary, we have no need or desire to make that visible.

Here we set the colouring for the dots. My status is quantitative, so I just set a scale of 3 colours which are assigned in numerical order to my status’. In other words, “gold” is the colour if your status is 1, “orange” for 2, etc. To change the colour scheme, just change the values here - you can use the words or a specific hex code if you wish.

Layer 2 - we are now adding the outline around the circle.

You can see here we are specificying that we don’t want a fill, otherwise it would occlude the smaller central circle, so we disable that. We are also specifically enabling tooltips here, so if you hover over the point you can see what the actual date of the milestone is.

Pretty much a repeat of the previous steps encoding here.

<- Again, just swap your date field out here.

Next up is our milestone labels. For this we are using a text mark, and set the “dy” which is the vertical anchoring of the text to the anchor point at -10. This means the label will always default to sit above the end of line connecting it to the timeline point.

If you are playing along at home, here is where you swap out the “Milestone Text” field to whatever label field you want to use.

This time, we want to use our position field to give a height to the labels. So in the background although we have hidden the axis, each mile actually has an integer associated with it, plotted on the y axis (here is where you swap to use your position field). The higher the number the further from the timeline your label will render.

Last bit for our spec - just need to add the faint grey line to connect the label to the timeline point. For this I used a bar, as this mark will default to start at 0 and ascend to the “position” value, making it perfect for use as our linking line.

Last thing to swap out is the date field here. Interestingly, it doesn’t matter if your date field is a date or not in PowerBI as the Vegalite spec overrides that, which is why it is specifically cast as a “temporal” data type here.


OK, that wasn’t too tricky! Last thing to do is to add the config file, so you can basically just clear whatever is there and replace it with this - not too much to comment on here, this is just setting formatting options that run across the entire visual.


{
  "view": {"stroke": "transparent"},
  "font": "Segoe UI",
  "bar": {
    "color": "grey",
    "opacity": 0.1,
    "width": 1.5
  },
  "text": {
    "font": "Segoe UI",
    "fontSize": 12,
    "fill": "#605E5C",
    "offset": 100
  },
  "axis": {
    "nice": true,
    "ticks": true,
    "grid": false,
    "labelColor": "#605E5C",
    "labelFontSize": 12
  },
  "axisQuantitative": {
    "tickCount": 12,
    "grid": true,
    "gridColor": "#C8C6C4",
    "labelFlush": false
  },
  "axisX": {"labelPadding": 5}
}

This is just the first iteration of this visual, I may work to refine it if there are any requests for additional functionality etc.

Otherwise, thanks to u/Geophizx for the suggestion - I hope you like it mate!

Previous
Previous

Deneb: Lollipops