Deneb Examples - Multiple Deneb Visuals with Consistent Styles

Deneb/Vega-Lite can be configured with “global” settings to use consistent styles across multiple visuals (or multiple reports) in Power BI. The example presented herein uses a made-up (synthetic) dataset of 4 years of financial results and presents 5 different Deneb visuals, all using common style settings. (Granted, there is no “sharing” per se of configurations between Deneb visuals at this time [at least, that I’ve found], but local (“faux-global”) settings can be achieved with a little cut-and-paste). This presents organizations with an easy-to-implement addition to Power BI themes for consistent styling across multiple Deneb visuals.

The dataset item includes budget and actual sales values by subcategory, category, and year, and the Deneb visuals include category performance, annual trends, a financial statement, subcategory performance, and subcategory and item budget performance. A common configuration is used for all Deneb visuals, with blue for income and orange for expenses and a different blue for budget performance.

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

  • use of a common “transform” block to extend the dataset with in-visual calculations for:
    • level (category, subcategory, item; L1, L2, L3)
    • the aggregation of category, subcategory, and overall budgets and actuals
    • the item percent of subcategory for budgets and actuals
    • the level variance (actual - budget) and percent variance
  • use of a common “config” block with named styles for all non-positional formatting, including
    • visual title fonts (face, size, weight, style, colour)
    • category colours (bar, column, line)
    • data label background colour and fonts (face, size, weight, style, line break character)
    • table level (category\subcategory\item) background colours and fonts (face, size, weight, style, colour)
    • budget performance colour,
    • axis and legend fonts (face, size, colour)

1 - Category Performance:

  • use of a “transform” block with:
    • a “filter” transform to retain only the L1 records
    • a “stack” transform to replace the standard side-by-side display with an on-top-of-one-another display
  • use of a “bar” mark for category 1 (income)
  • use of a “bar” mark for category 2 (expense)
  • use of a “layer” block with:
    • a “transform” block to extend the dataset with in-visual calculation of the data label (category, value, percent, and including separators)
    • a “rect” mark with rounded corners for the data label background
    • a “text” mark for the multi-line data label

2 - Trends:

  • use of a “layer” block with:
    • a “bar” mark with 50% band width for the expense values
    • a “line” mark for the income values
    • a “circle” mark to highlight the income values

3 - Financial Statement:

  • use of a “layer” block with:
    • a “text” mark for the column headers (with block-specific, hard-coded data)
      • x “axis” uses specific “values” to give the appearance of vertical grid lines
    • 3x “bar” marks for the full-width L1, L2, and L3 backgrounds
      • L3 background is set to 5% band height and offset by 10 pixels to give the appearance of horizontal grid lines
    • 5x “text” marks for the category\subcategory\item, budget, actual, variance, and percent variance

4 - Subcategory Performance:

  • use of shared “x” encoding outside the layer block to implement the horizontal layout
  • use of a “layer” block with:
    • a “bar” mark for the stacked income subcategories
      • a “transform” block to filter for the income category
      • variable opacity depending on subcategory rank (R1-80%, R2=60%, etc.)
    • a “bar” mark for expense subcategories
      • a “transform” block to filter for the expense category
      • variable opacity depending on subcategory rank (R1-80%, R2=60%, etc.)
    • a “transform” block to extend the dataset with in-visual calculation of the data label (subcategory, value, percent, and including separators)
    • a “rect” mark with rounded corners for the data label background
    • a “text” mark for the multi-line data label

5 - Budget Performance:

  • use of a “vconcat” block to vertically-concatenate the title, charts, and legend
    • title block uses a non-visible “rect” mark (transparent)
    • chart block with shared “y” encoding uses an “hconcat” block to horizontally-concatenate the subcategory and item bar charts
      • use of a “layer” block for subcategory with:
        • a “text” mark for a “faux-y-axis” with variable xOffset, font size, and font weight
        • a “bar” mark with 100% opacity for subcategories with positive variances (at or above 100% of budget)
        • a “bar” mark with 40% opacity for subcategories with negative variances (below 100% of budget)
        • a “tick” mark for the budget percent of category
        • a “rect” mark for the background rectangle for positive variances
        • a “text” mark for the positive variances
      • use of a “rule” mark with hard-coded y values as a divider
      • use of a “layer” block for item with:
        • a “text” mark for a “faux-y-axis” with variable xOffset, font size, and font weight
        • a “bar” mark with 100% opacity for items with positive variances (at or above 100% of budget)
        • a “bar” mark with 40% opacity for items with negative variances (below 100% of budget)
        • a “tick” mark for the budget percent of subcategory
        • a “rect” mark for the background rectangle for positive variances
        • a “text” mark for the positive variances
    • legend block using a “layer” block (with block-specific, hard-coded data):
      • 2x “rect” marks for over budget, under budget symbols (width 20, height 10)
        • over budget - full colour; under budget - 40% opacity
      • 1x “rect” mark for budget symbol (width 2, height 20)
      • 3x “text” marks for legend labels
Deneb/Vega-Lite Config JSON Code - Global
{
  "view": {"stroke": null},
  "style": {
    "group-title": {
      "font": "Segoe UI",
      "fontSize": 16,
      "fontWeight": "bold",
      "fontStyle": "normal",
      "color": "#000000"
    },  
    "_category_1_colour": {
      "color": "#03AFB9"
    },
    "_category_2_colour": {
      "color": "#F76962"
    },
    "_label_background_colour": {
      "color": "#E3E3E3"
    },	
    "_label_text": {
      "font": "Segoe UI",
      "fontSize": 12,
      "fontWeight": "normal",
      "fontStyle": "normal",
      "color": "black",
      "lineBreak": "|"
    },
    "_level_1_background_colour": {
      "color": "#C9C9C9"
    },
    "_level_2_background_colour": {
      "color": "#E3E3E3"
    },
    "_level_3_background_colour": {
      "color": "#E3E3E3"
    },
    "_table_text": {
      "font": "Segoe UI",
      "fontSize": 12,
      "fontWeight": "normal",
      "fontStyle": "normal",
      "color": "#000000"
    },
    "_actual_colour": {
      "color": "#4598D3"
    },
    "_budget_colour": {
      "color": "#000000"
    },
    "_x_axis_font": {
      "labelAngle": 0,
      "labelFont": "Segoe UI",
      "labelFontSize": 12,
      "labelColor": "#000000",
      "titleFont": "Segoe UI",
      "titleFontSize": 14,
	  "titleColor": "#000000"
    },
    "_y_axis_font": {
      "labelAngle": 0,
      "labelFont": "Segoe UI",
      "labelFontSize": 12,
      "labelColor": "#000000",
      "titleFont": "Segoe UI",
      "titleFontSize": 14,
	  "titleColor": "#000000"
    },
    "_legend_text": {
      "align": "left",
      "font": "Segoe UI",
      "fontSize": 12,
      "color": "#000000"
    }
  }
}
Deneb/Vega-Lite Transform JSON Code - Global
  "transform": [
    {
      "calculate": "datum['Category ID'] + '-' + datum['Category']",
      "as": "_category_id_name"
    },
    {
      "calculate": "datum['Category ID'] * 100 * 100 + datum['Subcategory ID'] * 100 + datum['Item ID']",
      "as": "_id"
    },
    {
      "calculate": "datum['Item ID'] == 1 ? datum['Category'] : datum['Item ID'] == 2 ? datum['Subcategory'] : datum['Item']",
      "as": "_item"
    },
    {
      "calculate": "datum['Item ID'] == 1 ? 1 : datum['Item ID'] == 2 ? 2 : 3",
      "as": "_level"
    },
    {
      "joinaggregate": [
        {
          "op": "sum",
          "field": "Budget",
          "as": "_subcategory_budget"
        }
      ],
      "groupby": ["Subcategory ID"]
    },
    {
      "joinaggregate": [
        {
          "op": "sum",
          "field": "Actual",
          "as": "_subcategory_actual"
        }
      ],
      "groupby": ["Subcategory ID"]
    },
    {
      "joinaggregate": [
        {
          "op": "sum",
          "field": "Budget",
          "as": "_category_budget"
        }
      ],
      "groupby": ["Category ID"]
    },
    {
      "joinaggregate": [
        {
          "op": "sum",
          "field": "Actual",
          "as": "_category_actual"
        }
      ],
      "groupby": ["Category ID"]
    },
    {
      "joinaggregate": [
        {
          "op": "sum",
          "field": "Budget",
          "as": "_overall_budget"
        }
      ]
    },
    {
      "joinaggregate": [
        {
          "op": "sum",
          "field": "Actual",
          "as": "_overall_actual"
        }
      ]
    },
    {
      "calculate": "datum['Item ID'] == 2 ? datum['_subcategory_budget'] : datum['Budget']",
      "as": "Budget"
    },
    {
      "calculate": "datum['Item ID'] == 2 ? datum['_subcategory_actual'] : datum['Actual']",
      "as": "Actual"
    },
    {
      "calculate": "datum['Item ID'] == 1 ? datum['_category_budget'] : datum['Budget']",
      "as": "Budget"
    },
    {
      "calculate": "datum['Item ID'] == 1 ? datum['_category_actual'] : datum['Actual']",
      "as": "Actual"
    },
    {
      "calculate": "datum['Actual'] / datum['_subcategory_actual']",
      "as": "_actual_percent_of_subcategory"
    },
    {
      "calculate": "datum['Budget'] / datum['_subcategory_actual']",
      "as": "_budget_percent_of_subcategory"
    },
    {
      "calculate": "datum['Actual'] - datum['Budget']",
      "as": "_variance"
    },
    {
      "calculate": "datum['_variance'] / datum['Budget']",
      "as": "_percent_variance"
    }
  ]
Deneb/Vega-Lite Specification JSON Code - Category Performance
{
  "title": {
    "anchor": "middle",
    "align": "center",
    "offset": 12,
    "text": "Category Performance (Actuals)"
  },
  "data": {"name": "dataset"},
  "transform": [...],
  "hconcat": [
    {
      "name": "NORMALIZED_BAR_CHART",
      "transform": [
        {
          "filter": "datum['_level'] == 1"
        },
        {
          "stack": "_category_actual",
          "as": ["_lower", "_upper"]
        }
      ],
      "height": 100,
      "width": 530,
      "layer": [
        {
          "name": "STACKED_BAR_CATEGORY_1",
          "transform": [
            {
              "filter": "datum['Category ID'] == 1"
            }
          ],
          "mark": {
            "type": "bar",
            "style": "_category_1_colour"
          },
          "encoding": {
            "x": {
              "field": "_lower",
              "type": "quantitative",
              "axis": null
            },
            "x2": {"field": "_upper"}
          }
        },
        {
          "name": "STACKED_BAR_CATEGORY_2",
          "transform": [
            {
              "filter": "datum['Category ID'] == 2"
            }
          ],
          "mark": {
            "type": "bar",
            "style": "_category_2_colour"
          },
          "encoding": {
            "x": {
              "field": "_lower",
              "type": "quantitative",
              "axis": null
            },
            "x2": {"field": "_upper"}
          }
        },
        {
          "name": "STACKED_BAR_LABELS",
          "transform": [
            {
              "stack": "_category_actual",
              "as": ["_lower", "_upper"]
            },
            {
              "calculate": "datum['_category_actual'] / datum['_overall_actual']",
              "as": "_category_percent"
            },
            {
              "calculate": "datum['Category'] + '|' + format( datum['_category_actual'], ',.0f' ) + '|' + format( datum['_category_percent'], '.0%' )",
              "as": "_block_label"
            }
          ],
          "layer": [
            {
              "name": "LABEL_BACKGROUND",
              "mark": {
                "type": "rect",
                "cornerRadius": 4,
                "xOffset": -50,
                "yOffset": 50,
                "height": 40,
                "width": 90,
                "style": "_label_background_colour"
              },
              "encoding": {
                "x": {
                  "field": "_upper",
                  "type": "quantitative",
                  "axis": {
                    "title": null
                  }
                }
              }
            },
            {
              "name": "LABEL_FOREGROUND",
              "mark": {
                "type": "text",
                "xOffset": -50,
                "yOffset": -14,
                "style": "_label_text"
              },
              "encoding": {
                "text": {
                  "field": "_block_label",
                  "type": "nominal"
                },
                "x": {
                  "field": "_upper",
                  "type": "quantitative",
                  "axis": {
                    "title": null
                  }
                }
              }
            }
          ]
        }
      ]
    }
  ]
}
Deneb/Vega-Lite Specification JSON Code - Trends
{
  "title": {
    "anchor": "middle",
    "align": "center",
    "orient": "top",
    "offset": 4,
    "text": "Trend (Actuals)"
  },
  "data": {"name": "dataset"},
  "transform": [...],
  "encoding": {
    "x": {
      "field": "Year",
      "type": "ordinal",
      "axis": {
        "title": null,
        "style": "_x_axis_font"
      }
    }
  },
  "layer": [
    {
      "name": "EXPENSE_COLUMN",
      "transform": [
        {
          "filter": "datum['Category ID'] == 2 & datum['Item ID'] == 1"
        }
      ],
      "mark": {
        "type": "bar",
        "width": {"band": 0.5},
        "style": "_category_2_colour"
      },
      "encoding": {
        "y": {
          "field": "_category_actual",
          "type": "quantitative",
          "axis": {
            "title": null,
            "style": "_y_axis_font"
          },
          "scale": {
            "domain": [0, 50000]
          }
        }
      }
    },
    {
      "name": "INCOME_LINE",
      "transform": [
        {
          "filter": "datum['Category ID'] == 1 & datum['Item ID'] == 1"
        }
      ],
      "layer": [
        {
          "name": "LINE",
          "mark": {
            "type": "line",
            "style": "_category_1_colour"
          },
          "encoding": {
            "y": {
              "field": "Actual",
              "type": "quantitative",
              "axis": {
                "title": null,
                "tickCount": 5
              },
              "scale": {
                "domain": [0, 50000]
              }
            }
          }
        },
        {
          "name": "MARKERS",
          "mark": {
            "type": "circle",
            "size": 100,
            "opacity": 1,
            "style": "_category_1_colour"
          },
          "encoding": {
            "y": {
              "field": "Actual",
              "type": "quantitative"
            }
          }
        }
      ]
    }
  ]
}
Deneb/Vega-Lite Specification JSON Code - Financial Statement
{
  "data": {"name": "dataset"},
  "transform": [...],
  "hconcat": [
    {
      "name": "STATEMENT",
      "title": {
        "anchor": "middle",
        "align": "center",
        "orient": "top",
        "offset": 8,
        "text": "Financial Statement"
      },
      "width": 700,
      "encoding": {
        "y": {
          "field": "_id",
          "type": "ordinal",
          "axis": null
        }
      },
      "layer": [
        {
          "name": "COLUMN_HEADERS",
          "data": {
            "values": [
              {
                "_id": 1,
                "_x": 34000,
                "_title": "Category\\Subcategory\\Item"
              },
              {
                "_id": 2,
                "_x": 80000,
                "_title": "Budget"
              },
              {
                "_id": 3,
                "_x": 100000,
                "_title": "Actual"
              },
              {
                "_id": 4,
                "_x": 120000,
                "_title": "Variance"
              },
              {
                "_id": 5,
                "_x": 140000,
                "_title": "% Variance"
              }
            ]
          },
          "mark": {
            "type": "text",
            "align": {
              "expr": "indexof( [1], datum['_id'] ) > 0 ? 'left' : 'right'"
            },
            "color": "#969696",
            "fontSize": 14,
            "fontStyle": "italic",
            "fontWeight": "normal"
          },
          "encoding": {
            "y": {"value": -10},
            "x": {
              "field": "_x",
              "type": "quantitative",
              "axis": {
                "title": null,
                "domain": false,
                "ticks": false,
                "labels": false,
                "values": [
                  0,
                  60400,
                  80400,
                  100400,
                  120400,
                  140000
                ]
              }
            },
            "text": {
              "field": "_title",
              "type": "nominal"
            }
          }
        },
        {
          "name": "BAR_BACKGROUND_LEVEL_1",
          "transform": [
            {
              "filter": "datum['_level'] == 1"
            }
          ],
          "mark": {
            "type": "bar",
            "style": "_level_1_background_colour"
          },
          "encoding": {
            "x": {"value": 700}
          }
        },
        {
          "name": "BAR_BACKGROUND_LEVEL_2",
          "transform": [
            {
              "filter": "datum['_level'] == 2"
            }
          ],
          "mark": {
            "type": "bar",
            "style": "_level_2_background_colour"
          },
          "encoding": {
            "x": {"value": 700}
          }
        },
        {
          "name": "BAR_BACKGROUND_LEVEL_3",
          "transform": [
            {
              "filter": "datum['_level'] == 3"
            }
          ],
          "mark": {
            "type": "bar",
            "height": {"band": 0.05},
            "yOffset": 10,
            "style": "_level_3_background_colour"
          },
          "encoding": {
            "x": {"value": 700}
          }
        },
        {
          "name": "TEXT_ITEM",
          "mark": {
            "type": "text",
            "align": "left",
            "fontSize": {
              "expr": "datum['_level'] == 1 ? 16 : datum['_level'] == 2 ? 14 : 12"
            },
            "fontWeight": {
              "expr": "datum['_level'] == 1 ? 900 : datum['_level'] == 2 ? 700 : 400"
            },
            "xOffset": {
              "expr": "datum['_level'] == 2 ? 10 : datum['_level'] == 3 ? 18 : 2"
            },
            "style": "_table_text"
          },
          "encoding": {
            "text": {
              "field": "_item",
              "type": "nominal"
            },
            "x": {"datum": 0}
          }
        },
        {
          "name": "TEXT_BUDGET",
          "mark": {
            "type": "text",
            "align": "right",
            "fontWeight": {
              "expr": "datum['_level'] == 1 ? 900 : datum['_level'] == 2 ? 700 : 400"
            },
            "xOffset": 400,
            "style": "_table_text"
          },
          "encoding": {
            "text": {
              "field": "Budget",
              "type": "quantitative",
              "format": ","
            },
            "x": {"datum": 0}
          }
        },
        {
          "name": "TEXT_ACTUAL",
          "mark": {
            "type": "text",
            "align": "right",
            "fontWeight": {
              "expr": "datum['_level'] == 1 ? 900 : datum['_level'] == 2 ? 700 : 400"
            },
            "xOffset": 500,
            "style": "_table_text"
          },
          "encoding": {
            "text": {
              "field": "Actual",
              "type": "quantitative",
              "format": ","
            },
            "x": {"datum": 0}
          }
        },
        {
          "name": "TEXT_VARIANCE",
          "mark": {
            "type": "text",
            "align": "right",
            "fontWeight": {
              "expr": "datum['_level'] == 1 ? 900 : datum['_level'] == 2 ? 700 : 400"
            },
            "xOffset": 600,
            "style": "_table_text"
          },
          "encoding": {
            "text": {
              "field": "_variance",
              "type": "quantitative",
              "format": ","
            },
            "x": {"datum": 0}
          }
        },
        {
          "name": "TEXT_PERCENT_VARIANCE",
          "mark": {
            "type": "text",
            "align": "right",
            "fontWeight": {
              "expr": "datum['_level'] == 1 ? 900 : datum['_level'] == 2 ? 700 : 400"
            },
            "xOffset": 700,
            "style": "_table_text"
          },
          "encoding": {
            "text": {
              "field": "_percent_variance",
              "type": "quantitative",
              "format": ".0%"
            },
            "x": {"datum": 0}
          }
        }
      ]
    }
  ]
}

The intent of this example is not to provide a finished visual, but rather to serve as a starting point for further multi-custom visual or multi-report development.

Also included is the development sample PBIX using made-up data.

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 Examples - Consistent Styles - V10.pbix (1.5 MB)

2 Likes

mark as solved

1 Like