Deneb Examples - Period Stock Charts

Deneb/Vega-Lite can be used to switch between visuals. The idea for this was discussed among the Deneb Community Experts and the Vega-Lite syntax to achieve this was first developed by Madison Giammaria and posted to LinkedIn earlier this week.

Here’s another version that leverages Power BI for a text box (title), shapes (background rectangle with shadow; slicer separator vertical line), and slicers (period; chart type); this version presents simple financial data (from Yahoo Finance, in this case for Microsoft) for stock price and volume.

This example illustrates a number of Deneb/Vega-Lite features, including:
0 - General:

  • a “params” block with:
    • 2x parameters for the positive and negative colours
    • an expression to retrieve the chart type selected in the Power BI slicer
    • an expression to adjust the top-and-bottom “padding” of the child visuals (price area chart, volume column chart)
  • a shared “transform” block to extend the dataset in-visual with:
    • 2x “calculate” transforms to set/retrieve the maximum date from the dataset and to use the period selected in the Power BI slicer to calculate the minimum date,
    • a “filter” transform to restrict the dataset to the period of interest
    • a “window\rank” transform using the internal row number to allow easy reference to the “first” record
    • a “window\lag” transform to allow easy reference to the previous price
    • 2x “calculate” transforms to determine the price variance amount and percentage
    • a “window\lag” transform to allow easy reference to the previous volume
    • 2x “calculate” transforms to determine the volume variance amount and percentage
  • a “vconcat” block to vertically concatenate the child visuals (price area chart, volume column chart)

1 - Price:

  • a “layer” block to allow for shared X encoding
  • a shared “encoding” block for the X axis (temporal, by date)
  • a nested “layer” block with:
    • an “area” mark for the daily [close] price with dark line, gradient colours, and custom tooltip
    • a nested “transform” block with:
      • a “window\last_value” transform to determine the last (period) price
      • a “window\first_value” transform to determine the first (period) price
      • a “filter” transform to restrict the dataset to a single record (using the rank calculated above)
      • 2x “calculate” transforms to determine the (last - first) price variance amount and percentage
    • 2x “text” marks for the last price and variance percent (displayed in colour: green for positive, red for negative)
    • 4x “text” marks for the first price and labels (displayed in black)
    • a “rule” mark and an “point\triangle” mark for the variance direction arrow (both displayed in colour: green for positive, red for negative)

2 - Volume:

  • a “layer” block to allow for shared X encoding
  • a shared “encoding” block for the X axis (temporal, by date)
  • a nested “layer” block with:
    • a “bar” mark for the daily volume with variances displayed in colour (green for positive, red for negative) and custom tooltip
    • a nested “transform” block with:
      • a “window\last_value” transform to determine the last (period) volume
      • a “window\first_value” transform to determine the first (period) volume
      • a “filter” transform to restrict the dataset to a single record (using the rank calculated above)
      • 2x “calculate” transforms to determine the (last - first) volume variance amount and percentage
    • a “text” mark for the last volume (displayed in colour: green for positive, red for negative)
    • 3x “text” marks for the first volume and labels (displayed in black)
Deneb/Vega-Lite JSON Code:
{
  "data": {
    "name": "dataset"
  },
  "params": [
    {
      "name": "_positive_colour",
      "value": "#09AA64"
    },
    {
      "name": "_negative_colour",
      "value": "#B51C36"
    },
    {
      "name": "_chart_type",
      "expr": "data('dataset')[0]['Type']"
    },
    {
      "name": "padding",
      "expr": "{top: _chart_type == 'Price' ? 20 : -600 + 10, bottom: _chart_type == 'Price' ? -600 - 2 : 60}"
    }
  ],
  "transform": [
    {
      "calculate": "toDate( '2024-07-12' )",
      "as": "_max_date"
    },
    {
      "calculate": "datum['Range'] == '5D' ? timeOffset( 'day', datum['_max_date'], -5 ) : datum['Range'] == '1M' ? timeOffset( 'month', datum['_max_date'], -1 ) : datum['Range'] == '3M' ? timeOffset( 'month', datum['_max_date'], -3 ) : datum['Range'] == '6M' ? timeOffset( 'month', datum['_max_date'], -6 ) : datum['Range'] == 'YTD' ? datetime( year( datum['_max_date'] ), 0, 1 ) : datum['Range'] == '1Y' ? timeOffset( 'year', datum['_max_date'], -1 ) : datum['Range'] == '3Y' ? timeOffset( 'year', datum['_max_date'], -3 ) : datum['Range'] == '5Y' ? timeOffset( 'year', datum['_max_date'], -5 ) : datum['Range'] == '10Y' ? timeOffset( 'year', datum['_max_date'], -10 ) : datum['Range'] == 'ALL' ? timeOffset( 'year', datum['_max_date'], -500 ) : null",
      "as": "_min_date"
    },
    {
      "filter": "datum['Date'] <= datum['_max_date'] && datum['Date'] >= datum['_min_date']"
    },
    {
      "window": [
        {
          "op": "rank",
          "as": "_rank"
        }
      ],
      "sort": [
        {
          "field": "__row__",
          "order": "ascending"
        }
      ]
    },
    {
      "calculate": "1 * datum['Close']",
      "as": "_current_close"
    },
    {
      "window": [
        {
          "op": "lag",
          "field": "_current_close",
          "as": "_previous_close"
        }
      ]
    },
    {
      "calculate": "datum['_current_close'] - datum['_previous_close']",
      "as": "_close_variance"
    },
    {
      "calculate": "datum['_close_variance'] / datum['_previous_close']",
      "as": "_close_variance_percent"
    },
    {
      "calculate": "1 * datum['Volume']",
      "as": "_current_volume"
    },
    {
      "window": [
        {
          "op": "lag",
          "field": "_current_volume",
          "as": "_previous_volume"
        }
      ]
    },
    {
      "calculate": "datum['_current_volume'] - datum['_previous_volume']",
      "as": "_volume_variance"
    },
    {
      "calculate": "datum['_volume_variance'] / datum['_previous_volume']",
      "as": "_volume_variance_percent"
    }
  ],
  "vconcat": [
    {
      "name": "UPPER_PRICE",
      "layer": [
        {
          "name": "PRICE_1",
          "width": 1100,
          "height": 540,
          "encoding": {
            "x": {
              "field": "Date",
              "type": "temporal",
              "axis": {
                "orient": "bottom",
                "grid": false,
                "titleFont": "Segoe UI",
                "titleFontSize": 16,
                "labelFont": "Segoe UI",
                "labelFontSize": 12
              }
            }
          },
          "layer": [
            {
              "name": "PRICE_11",
              "mark": {
                "type": "area",
                "line": {
                  "color": "darkblue"
                },
                "color": {
                  "x1": 1,
                  "y1": 1,
                  "x2": 1,
                  "y2": 0,
                  "gradient": "linear",
                  "stops": [
                    {
                      "offset": 0,
                      "color": "white"
                    },
                    {
                      "offset": 1,
                      "color": "darkblue"
                    }
                  ]
                }
              },
              "encoding": {
                "y": {
                  "field": "Close",
                  "type": "quantitative",
                  "axis": {
                    "orient": "left",
                    "grid": false,
                    "title": "Price",
                    "titleFont": "Segoe UI",
                    "titleFontSize": 16,
                    "labelFont": "Segoe UI",
                    "labelFontSize": 12
                  },
                  "scale": {
                    "domainMax": 500
                  }
                },
                "tooltip": [
                  {
                    "field": "Date",
                    "type": "temporal",
                    "formatType": "pbiFormat",
                    "format": "dd-MMM-yyyy",
                    "title": "Date"
                  },
                  {
                    "field": "_current_close",
                    "type": "quantitative",
                    "format": ".4f",
                    "title": "Price"
                  },
                  {
                    "field": "_previous_close",
                    "type": "quantitative",
                    "format": ".4f",
                    "title": "Previous"
                  },
                  {
                    "field": "_close_variance_percent",
                    "type": "quantitative",
                    "formatType": "pbiFormat",
                    "format": "+#,0.0%",
                    "title": "Variance"
                  }
                ]
              }
            }
          ]
        },
        {
          "name": "PRICE_TEXT_1",
          "transform": [
            {
              "window": [
                {
                  "op": "last_value",
                  "field": "Close",
                  "as": "_last_close_price"
                }
              ],
              "frame": [
                null,
                null
              ]
            },
            {
              "window": [
                {
                  "op": "first_value",
                  "field": "Close",
                  "as": "_first_close_price"
                }
              ],
              "frame": [
                null,
                null
              ]
            },
            {
              "filter": "datum['_rank'] == 1"
            },
            {
              "calculate": "datum['_last_close_price'] - datum['_first_close_price']",
              "as": "_variance"
            },
            {
              "calculate": "datum['_variance'] / datum['_first_close_price']",
              "as": "_variance_percent"
            }
          ],
          "layer": [
            {
              "name": "LAST_PRICE_1",
              "mark": {
                "type": "text",
                "align": "right",
                "baseline": "bottom",
                "fontSize": 48,
                "x": 160,
                "y": 60,
                "color": {
                  "expr": "datum['_variance'] >= 0 ? _positive_colour : _negative_colour"
                }
              },
              "encoding": {
                "text": {
                  "field": "_last_close_price",
                  "type": "quantitative",
                  "formatType": "pbiFormat",
                  "format": "#,0.00"
                }
              }
            },
            {
              "name": "FIRST_PRICE_1",
              "mark": {
                "type": "text",
                "align": "right",
                "baseline": "bottom",
                "fontSize": 24,
                "x": 260,
                "y": 56,
                "color": "black"
              },
              "encoding": {
                "text": {
                  "field": "_first_close_price",
                  "type": "quantitative",
                  "formatType": "pbiFormat",
                  "format": "#,0.00"
                }
              }
            },
            {
              "name": "LAST_PRICE_LABEL_1",
              "mark": {
                "type": "text",
                "align": "right",
                "baseline": "bottom",
                "fontSize": 12,
                "x": 160,
                "y": 70,
                "color": "black"
              },
              "encoding": {
                "text": {
                  "value": "Last Price"
                }
              }
            },
            {
              "name": "FIRST_PRICE_LABEL_1",
              "mark": {
                "type": "text",
                "align": "right",
                "baseline": "bottom",
                "fontSize": 12,
                "x": 260,
                "y": 70,
                "color": "black"
              },
              "encoding": {
                "text": {
                  "value": "First Price"
                }
              }
            },
            {
              "name": "VERTICAL_RULE_1",
              "mark": {
                "type": "rule",
                "strokeWidth": 4,
                "color": {
                  "expr": "datum['_variance'] >= 0 ? _positive_colour : _negative_colour"
                }
              },
              "encoding": {
                "x": {
                  "value": 330
                },
                "y": {
                  "value": 10
                },
                "y2": {
                  "value": 60
                }
              }
            },
            {
              "name": "VERTICAL_ARROW_1",
              "mark": {
                "type": "point",
                "filled": true,
                "size": 300,
                "x": 330,
                "y": {
                  "expr": "datum['_variance'] >= 0 ? 10 : 60"
                },
                "color": {
                  "expr": "datum['_variance'] >= 0 ? _positive_colour : _negative_colour"
                },
                "opacity": 1
              },
              "encoding": {
                "shape": {
                  "condition": [
                    {
                      "test": "datum['_variance'] < 0",
                      "value": "triangle-down"
                    }
                  ],
                  "value": "triangle-up"
                }
              }
            },
            {
              "name": "VARIANCE_PERCENT_1",
              "mark": {
                "type": "text",
                "align": "right",
                "baseline": "bottom",
                "fontSize": 36,
                "x": 460,
                "y": 60,
                "color": {
                  "expr": "datum['_variance'] >= 0 ? _positive_colour : _negative_colour"
                }
              },
              "encoding": {
                "text": {
                  "field": "_variance_percent",
                  "format": "+,.0%"
                }
              }
            },
            {
              "name": "VARIANCE_PERCENT_LABEL_1",
              "mark": {
                "type": "text",
                "align": "right",
                "baseline": "top",
                "fontSize": 12,
                "x": 460,
                "y": 60,
                "color": "black"
              },
              "encoding": {
                "text": {
                  "value": "Gain/Loss"
                }
              }
            }
          ]
        }
      ]
    },
    {
      "name": "LOWER_VOLUME",
      "layer": [
        {
          "name": "VOLUME_22",
          "width": 1100,
          "height": 540,
          "encoding": {
            "x": {
              "field": "Date",
              "type": "temporal",
              "axis": {
                "orient": "bottom",
                "grid": false,
                "titleFont": "Segoe UI",
                "titleFontSize": 16,
                "labelFont": "Segoe UI",
                "labelFontSize": 12
              }
            }
          },
          "layer": [
            {
              "name": "VOLUME2",
              "mark": {
                "type": "bar",
                "tooltip": true,
                "color": {
                  "expr": "datum['_current_volume'] > datum['_previous_volume'] ? _positive_colour : _negative_colour"
                }
              },
              "encoding": {
                "y": {
                  "field": "Volume",
                  "type": "quantitative",
                  "axis": {
                    "orient": "left",
                    "grid": false,
                    "titleFont": "Segoe UI",
                    "titleFontSize": 16,
                    "labelFont": "Segoe UI",
                    "labelFontSize": 12,
                    "format": ".2s"
                  },
                  "scale": {
                    "domainMax": 200000000
                  }
                },
                "tooltip": [
                  {
                    "field": "Date",
                    "type": "temporal",
                    "formatType": "pbiFormat",
                    "format": "dd-MMM-yyyy",
                    "title": "Date"
                  },
                  {
                    "field": "_current_volume",
                    "type": "quantitative",
                    "title": "Volume"
                  },
                  {
                    "field": "_previous_volume",
                    "type": "quantitative",
                    "formatType": "pbiFormat",
                    "format": "#,#",
                    "title": "Previous"
                  },
                  {
                    "field": "_volume_variance_percent",
                    "type": "quantitative",
                    "formatType": "pbiFormat",
                    "format": "+#,0.0%",
                    "title": "Variance"
                  }
                ]
              }
            },
            {
              "name": "VOLUME_TEXT_2",
              "transform": [
                {
                  "window": [
                    {
                      "op": "last_value",
                      "field": "Volume",
                      "as": "_last_close_volume"
                    }
                  ],
                  "frame": [
                    null,
                    null
                  ]
                },
                {
                  "window": [
                    {
                      "op": "first_value",
                      "field": "Volume",
                      "as": "_first_close_volume"
                    }
                  ],
                  "frame": [
                    null,
                    null
                  ]
                },
                {
                  "filter": "datum['_rank'] == 1"
                },
                {
                  "calculate": "datum['_last_close_volume'] - datum['_first_close_volume']",
                  "as": "_variance"
                },
                {
                  "calculate": "datum['_variance'] / datum['_first_close_volume']",
                  "as": "_variance_percent"
                }
              ],
              "layer": [
                {
                  "name": "LAST_VOLUME_2",
                  "mark": {
                    "type": "text",
                    "align": "left",
                    "baseline": "bottom",
                    "fontSize": 40,
                    "x": 200,
                    "y": 60,
                    "color": {
                      "expr": "datum['_variance'] >= 0 ? _positive_colour : _negative_colour"
                    }
                  },
                  "encoding": {
                    "text": {
                      "field": "_last_close_volume",
                      "type": "quantitative",
                      "formatType": "pbiFormat",
                      "format": "#,0"
                    }
                  }
                },
                {
                  "name": "FIRST_VOLUME_2",
                  "mark": {
                    "type": "text",
                    "align": "left",
                    "baseline": "bottom",
                    "fontSize": 24,
                    "x": 200,
                    "y": 110,
                    "color": "black"
                  },
                  "encoding": {
                    "text": {
                      "field": "_first_close_volume",
                      "type": "quantitative",
                      "formatType": "pbiFormat",
                      "format": "#,0"
                    }
                  }
                },
                {
                  "name": "LAST_VOLUME_LABEL_2",
                  "mark": {
                    "type": "text",
                    "align": "left",
                    "baseline": "bottom",
                    "fontSize": 12,
                    "x": 200,
                    "y": 70,
                    "color": "black"
                  },
                  "encoding": {
                    "text": {
                      "value": "Last Volume"
                    }
                  }
                },
                {
                  "name": "FIRST_VOLUME_LABEL_2",
                  "mark": {
                    "type": "text",
                    "align": "left",
                    "baseline": "bottom",
                    "fontSize": 12,
                    "x": 200,
                    "y": 120,
                    "color": "black"
                  },
                  "encoding": {
                    "text": {
                      "value": "First Volume"
                    }
                  }
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

The intent of this example is not to provide a finished visual, but rather to explore the use of the Deneb custom visual and the Vega-Lite language within Power BI and to serve as a starting point for further development.

Also included is the development PBIX using data for Microsoft’s stock price and volume from Yahoo Finance, downloaded 2024-07-12.

This example is provided as-is for information purposes only, and its use is solely at the discretion of the end user; no responsibility is assumed by the author.

Greg
Deneb Example - Period Stock Charts.pbix (4.1 MB)

1 Like

marking as solved