Conditional KPI Cards with Deneb

Published Categorized as Custom Visualisations, DataViz, Deneb, How To's, Vega-Lite 15 Comments on Conditional KPI Cards with Deneb

I’d written a piece recently on exploring the Deneb custom visual for Power BI. In that post I touched on KPI Style visuals. I hadn’t seen much in the way of KPI Style visuals in the Vega-Lite Examples Gallery so I thought I’d revisit them and explore them a little more.

I focused on styles that I’m unable to create with other custom visuals or that I would find difficult to produce in Charticulator. Here are some of the resultant outputs:

One of the most pleasing things about these visuals (aside from the enormous amount of creative freedom I had) was the ability to resize them to be very small compared to native visuals:

Not at all necessary, but satisfying none the same 🙂

The process

Creating the KPI visuals required some forethought. 

There are a number of different ways to achieve a KPI Card as shown, but the following steps outline the approach I took.

To create the KPI chart I would need to layer a few marks like so:

“layer” : [{area}, {line with points}, {circle}, {label 1}, {label 2}]

Layer 1 : Area

For the first layer I would need to declare:

  • Mark Type = area
  • Colour as constant = light gray
  • X mapped to Date field
  • Y mapped to Value field
  • Remove axes
 "mark": {
	        "type" : "area"
	      },
	      "encoding": {
	        "color": {
	          "value": "lightgray"
	        },
	        "x": {
	          "field": "Date",
	          "type": "temporal",
	          "axis": null
	        },
	        "y": {
	          "field": "Value",
	          "type": "quantitative",
	          "axis": null
	        }
	      }

Layer 2 : Line

For the second layer I would need to declare:

  • Mark Type = line with white points and tooltip enabled
  • Colour as constant = gray
  • X mapped to Date field
  • Y mapped to Value field
	{
	      "mark": {
	        "type": "line",
	        "point": {
	          "filled": false,
	          "fill": "white"
	        }, 
	"tooltip" : true
	      },
	      "encoding": {
	        "color": {
	          "value": "gray"
	        },
	        "x": {
	          "field": "Date",
	          "type": "temporal"
	        },
	        "y": {
	          "field": "Value",
	          "type": "quantitative"
	        }
	      }
    },

Layer 3 : Circle

The third layer:

  • Mark Type = circle with tooltip enabled
  • Colour as conditional = if value is less than target then red else green
  • X mapped to the last/maximum Date field value
  • Y mapped to Value field for the maximum Date field value
	{
	      "mark": {"type":"circle", "tooltip":true},
	      "encoding": {
	"color": {
	              "condition": {"test": "datum['Value'] < datum['Target']", "value": "red"},
	          "value": "green"},
	        "size": {
	          "value": 30
	        },
	        "x": {
	          "aggregate": "max",
	          "field": "Date",
	          "type": "temporal"
	        },
	        "y": {
	          "aggregate": {
	            "argmax": "Date"
	          },
	          "field": "Value",
	          "type": "quantitative"
	        }
	      }
    },

Layer 4 : Label

The fourth layer:

  • Mark Type = text
  • X mapped to the maximum Date field value
  • Y mapped to Value field for the maximum Date field value, text is from the Formatted Actual field
	        {
	          "mark": {
	            "type": "text",
	            "dx": 0,
	            "dy": -10,
	            "xOffset": 0,
	            "yOffset": 0,
	            "angle": 0,
	            "align": "right",
	            "baseline": "bottom",
	            "font": "sans-serif",
	            "fontSize": 20,
	            "fontStyle": "normal",
	            "fontWeight": "normal",
	            "limit": 0
	          },
	          "encoding": {
	          "x": {
	            "aggregate": "max","field": "Date", "type": "temporal"},
	        "y": {
	          "aggregate": {"argmax": "Date"},"field": "Value", "type": "quantitative"},
	            "text": 
	{"aggregate": {"argmax": "Date"},
	              "field": "Formatted Actual"
	            }
	          }
        },

Labels can be formatted with Vega-Lite, however, I chose to use DAX as I was more familiar with this and I could easily concatenate additional information.

Example DAX:

Formatted Actual = FORMAT([Value],”,##,,.0m”)

Or

Formatted Variance = [Unicode Arrow] & ” ” &[Percentage] & ” ” & [Above/Below] & ” target”

Tidying up the code

There is certainly opportunity to tidy up the code at this point though I am content to leave it as it is.

Border and Background

I set the border from the visual formatting pane to radius of 6. The border and backgrounds could be conditionally formatted using a DAX expression.

Variations

By changing area mark to rule, line mark to circle, the following can be achieved:

74m 
IOS

By changing argmax and argmin, the highest and lowest values can be highlighted.

To find the min:

	        "x": {
	          "aggregate": {
	            "argmin": "Value"
	          },
	          "field": "Date",
	          "type": "temporal"
	        },
	        "y": {
	          "aggregate": {
	            "argmin": "Value"
	          },
	          "field": "Value",
	          "type": "quantitative"
        }

To find the max:

74m
	        "x": {
	          "aggregate": {
	            "argmax": "Value"
	          },
	          "field": "Date",
	          "type": "temporal"
	        },
	        "y": {
	          "aggregate": {
	            "argmax": "Value"
	          },
	          "field": "Value",
	          "type": "quantitative"
        }

Additional KPI Colours

The Vega-Lite documentation site does not show examples of conditional formatting for more than two values – at least not that I could see. Here’s how to set RAG indicators (which worked for me):

"color" : {"condition": [
{"test": "datum['KPI'] === 2", "value": "red"},
{"test": "datum['KPI'] === 1", "value": "yellow"}
],
"value": "green"}

All in all, I had fun experimenting. I hope you do too 🙂

Kez.

15 comments

    1. No problems

      {
      “data”: {“name”: “dataset”},
      “layer”: [
      {“mark”: {“type”: “area”},
      “encoding”: {
      “color”: {“value”: “lightgray”},
      “x”: {“field”: “Date”,”type”: “temporal”,”axis”: null},
      “y”: {“field”: “Value”,”type”: “quantitative”,”axis”: null}
      }},
      {
      “mark”: {“type”: “line”,
      “point”: {“filled”: false,”fill”: “white”},
      “tooltip”: true
      },
      “encoding”: {
      “color”: {“value”: “gray”},
      “x”: {“field”: “Date”,”type”: “temporal”},
      “y”: {“field”: “Value”,”type”: “quantitative”}
      }},
      {
      “mark”: {“type”: “circle”,”tooltip”: true},
      “encoding”: {
      “color”: {
      “condition”: {
      “test”: “datum[‘Value’] < datum['Target']", "value": "red" }, "value": "green" }, "size": {"value": 30}, "x": {"aggregate": "max","field": "Date","type": "temporal"}, "y": { "aggregate": {"argmax": "Date"}, "field": "Value","type": "quantitative"} } }, { "mark": { "type": "text", "dx": 0, "dy": -10, "xOffset": 0, "yOffset": -10, "angle": 0, "align": "right", "baseline": "bottom", "font": "sans-serif", "fontSize": 20, "fontStyle": "normal", "fontWeight": "normal", "limit": 0 }, "encoding": { "x": {"aggregate": "max","field": "Date","type": "temporal"}, "y": {"aggregate": "max","field": "Value","type": "quantitative"}, "text": {"field": "Format Actual"} } }, { "mark": { "type": "text", "dx": 0, "dy": -10, "xOffset": 0, "yOffset": -10, "angle": 0, "align": "right", "baseline": "top", "font": "sans-serif", "fontSize": 14, "fontStyle": "normal", "fontWeight": "normal", "limit": 0 }, "encoding": { "x": {"aggregate": "max","field": "Date","type": "temporal"}, "y": {"aggregate": "max", "field": "Value", "type": "quantitative"}, "text": {"field": "Format Variance"} } } ] }

      1. Hi Kerry,

        love the KPI cards, I’ve tried testing out your code that you posted in the comments. I copied & pasted it directly but I’m not getting any results. I think since I’m brand new to this code I’m a bit unsure of myself, I have a ‘Date’ field and a measure called ‘Value’ in the values box. I’m sure I’m missing something simply. hope you can help.

          1. Ah, thank you, hadn’t spotted the sample workbook due to my excitement around Deneb’s potential. Thanks again, love the website & content.

  1. Hi There,
    It is really useful for some of the metrics that I am working on, would you be able to share the file or any similar links to get an idea of how to work out this would be really helpful.Thanks again.

  2. If you can share the code for other KPI visuals too for beginners it will help understanding and get familiarized soon.

  3. Thanks for sharing your knowledge. These visuals are amazing.
    I was wondering if using Deneb (and Vega-Lite), we could generate a faceted charts with these KPIs cards, with different columns for the different KPIs (ex. sales, sales % growth…) having the different sales shops on the Y-Axis (as an example).

Leave a comment

Your email address will not be published.