Deneb/Vega custom visual shows generic "sad face" icon instead of empty-state message — is this expected behavior for empty datasets?
14:41 04 Sep 2026

Deneb/Vega custom visual shows generic "sad face" icon instead of empty-state message — is this expected behavior for empty datasets?

Tags to use: powerbi, vega, vega-lite, deneb, data-visualization


Environment

  • Power BI Desktop (Mai 2026 build)

  • Deneb custom visual, version 1.9.1.0

  • Vega spec schema: https://vega.github.io/schema/vega/v5.json

  • Deneb's internal renderer reports: The input spec uses Vega v5, but the current version of Vega is v6.2.0 (informational warning only, not an error)

Summary

I have a Deneb visual (raw Vega, not Vega-Lite) rendering a Gantt-style milestone timeline. When a Power BI filter/slicer combination results in zero rows reaching one of my derived datasets, the visual shows Deneb's generic "☹" (sad face) placeholder instead of rendering an empty/blank chart area.

At first I assumed this was a JavaScript runtime crash, but after setting the Deneb Logs panel to Log level: Error, there are no error entries at all — the log is completely empty. Only [Warn] level messages appear (schema-validation warnings about the v5/v6.2.0 mismatch on my time scale, which I believe are false positives caused by using a signal-based domain instead of a data/field-based one — happy to be corrected).

Question

  1. Is the "☹" icon Deneb's standard placeholder for "zero marks rendered" (i.e., not actually an error), or does it always indicate a caught exception even when nothing shows in the Error-level log?

  2. Is there a supported way to distinguish, from the Vega spec itself, between "my data is legitimately empty" vs. "something upstream threw and got swallowed" — ideally by rendering a custom "No data for this selection" text mark instead of relying on Deneb's default icon?

  3. Is there a more idiomatic Vega pattern than what I'm doing below for guarding a time scale's domain against an empty upstream dataset?

What I already tried

  • Added a guard signal so the xTime scale never receives an undefined domain when the filtered dataset is empty:
{
  "name": "safeDomain",
  "update": "length(data('milestones')) > 0 ? extent(pluck(data('milestones'), 'milestoneDate')) : [datetime(year(now())-1,0,1), datetime(year(now())+1,11,31)]"
}
"scales": [
  {
    "name": "xTime",
    "type": "time",
    "domain": { "signal": "safeDomain" },
    "range": [{ "signal": "timelineStart" }, { "signal": "timelineEnd" }],
    "nice": true,
    "zero": false
  }
]

This fixed an actual crash (confirmed via the standalone Vega Editor: domain('xTime')[0]/[1] were undefined before the fix, causing an exception under "nice": true).

  • Found and removed a duplicate data source definition ({"name": "milestones"} with no source/transform, shadowing the real one further down the array) that was also contributing to blank renders.

  • After both fixes, the chart does render correctly when data is present. The "☹" now only appears when a filter selection legitimately yields 0 rows — but I can't yet tell programmatically/visually whether that's "correctly empty" or "another swallowed error", short of manually opening the Logs panel every time.

Minimal reproducible example

Here's a trimmed-down spec that reproduces the "☹ with empty Error log" behavior when the injected dataset has 0 rows (paste into the Vega Editor with an empty values: [] array for dataset):

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "width": 400,
  "height": 200,
  "signals": [
    {
      "name": "safeDomain",
      "update": "length(data('milestones')) > 0 ? extent(pluck(data('milestones'), 'milestoneDate')) : [datetime(2025,0,1), datetime(2027,11,31)]"
    }
  ],
  "data": [
    { "name": "dataset", "values": [] },
    {
      "name": "milestones",
      "source": "dataset",
      "transform": [
        { "type": "formula", "as": "milestoneDate", "expr": "toDate(datum['Datum Ende'])" },
        { "type": "filter", "expr": "isValid(datum.milestoneDate)" }
      ]
    }
  ],
  "scales": [
    {
      "name": "xTime",
      "type": "time",
      "domain": { "signal": "safeDomain" },
      "range": [10, 390],
      "nice": true
    }
  ],
  "axes": [{ "scale": "xTime", "orient": "bottom" }],
  "marks": [
    {
      "type": "symbol",
      "from": { "data": "milestones" },
      "encode": {
        "enter": {
          "x": { "scale": "xTime", "field": "milestoneDate" },
          "y": { "value": 100 },
          "shape": { "value": "diamond" }
        }
      }
    }
  ]
}

With values: [], this renders fine in the standalone Vega Editor (empty chart, valid axis 2025–2027, no error) — but the equivalent situation inside Deneb/Power BI shows the "☹" icon instead of the empty axis. I'd expect Deneb to just render what Vega itself renders (an empty-but-valid chart), so I'm not sure whether this is Deneb-specific behavior, a Power BI data-binding quirk, or something in my full production spec I haven't isolated yet.

What I'd like to know

  • Is this documented Deneb behavior (a deliberate "nothing to show" placeholder)?

  • Best-practice pattern for explicitly rendering a "No data for this selection" text mark instead, so end users can tell "empty by design" apart from "something broke" without opening dev tools?

Any pointers to Deneb's source/docs on this specific icon would also be appreciated.


Appendix: full production specs

In case the minimal example above doesn't reproduce it for you, here are the two complete, currently-working specs (post-fix) exactly as used in the report. Visual 1 is a thin header/axis bar; Visual 2 is the main 640px content chart that draws the milestone diamonds. Both pull from the same underlying Power BI table.

Visual 1 — header/axis spec (full)

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "width": {
    "signal": "pbiContainerWidth"
  },
  "height": 18,
  "autosize": {
    "type": "fit",
    "contains": "padding",
    "resize": true
  },
  "padding": {
    "left": 10,
    "right": 10,
    "top": 0,
    "bottom": 0
  },
  "signals": [
    {
      "name": "labelWidth",
      "value": 420
    },
    {
      "name": "timelineStart",
      "update": "labelWidth + 20"
    },
    {
      "name": "timelineEnd",
      "update": "width - 20"
    },
    {
      "name": "safeDomain",
      "update": "length(data('milestones')) > 0 ? extent(pluck(data('milestones'), 'milestoneDate')) : [datetime(year(now())-1,0,1), datetime(year(now())+1,11,31)]"
    },
    {
      "name": "visibleYears",
      "update": "(domain('xTime')[1] - domain('xTime')[0]) / 31557600000"
    }
  ],
  "data": [
    {
      "name": "dataset"
    },
    {
      "name": "milestones",
      "source": "dataset",
      "transform": [
        {
          "type": "filter",
          "expr": "isValid(datum['Datum Ende'])"
        },
        {
          "type": "filter",
          "expr": "!isValid(datum['Freier Filterkatalog']) || trim(toString(datum['Freier Filterkatalog'])) !== ''"
        },
        {
          "type": "formula",
          "as": "milestoneDate",
          "expr": "toDate(datum['Datum Ende'])"
        },
        {
          "type": "filter",
          "expr": "isValid(datum.milestoneDate)"
        }
      ]
    }
  ],
  "scales": [
    {
      "name": "xTime",
      "type": "time",
      "domain": {
        "signal": "safeDomain"
      },
      "range": [
        {
          "signal": "timelineStart"
        },
        {
          "signal": "timelineEnd"
        }
      ],
      "nice": true,
      "zero": false
    }
  ],
  "axes": [
    {
      "scale": "xTime",
      "orient": "bottom",
      "offset": 0,
      "tickCount": {
        "signal": "visibleYears <= 1.5 ? {interval: 'month', step: 3} : visibleYears <= 8 ? {interval: 'year', step: 1} : visibleYears <= 20 ? {interval: 'year', step: 2} : visibleYears <= 40 ? {interval: 'year', step: 5} : {interval: 'year', step: 10}"
      },
      "labelExpr": "visibleYears <= 1.5 ? ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'][month(datum.value)] + ' ' + year(datum.value) : timeFormat(datum.value, '%Y')",
      "labelFontSize": 10,
      "labelColor": "#555555",
      "labelPadding": 1,
      "labelOverlap": "greedy",
      "labelFlush": false,
      "tickSize": 3,
      "tickColor": "#A0A0A0",
      "domain": true,
      "domainColor": "#A0A0A0",
      "domainWidth": 1,
      "grid": false,
      "title": null
    }
  ],
  "marks": [
    {
      "type": "rule",
      "encode": {
        "update": {
          "x": {
            "signal": "timelineStart"
          },
          "x2": {
            "signal": "timelineStart"
          },
          "y": {
            "value": 0
          },
          "y2": {
            "signal": "height"
          },
          "stroke": {
            "value": "#D0D0D0"
          },
          "strokeWidth": {
            "value": 1
          }
        }
      }
    }
  ]
}

Visual 2 — content spec (full)

{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "width": {
    "signal": "pbiContainerWidth"
  },
  "height": 640,
  "autosize": {
    "type": "fit-x",
    "contains": "padding",
    "resize": true
  },
  "padding": {
    "left": 10,
    "right": 10,
    "top": 2,
    "bottom": 10
  },
  "signals": [
    {
      "name": "rowHeight",
      "value": 20
    },
    {
      "name": "topPadding",
      "value": 8
    },
    {
      "name": "labelWidth",
      "value": 420
    },
    {
      "name": "projectDescriptionLength",
      "value": 25
    },
    {
      "name": "timelineStart",
      "update": "labelWidth + 20"
    },
    {
      "name": "timelineEnd",
      "update": "width - 20"
    },
    {
      "name": "safeDomain",
      "update": "length(data('milestones')) > 0 ? extent(pluck(data('milestones'), 'milestoneDate')) : [datetime(year(now())-1,0,1), datetime(year(now())+1,11,31)]"
    },
    {
      "name": "dataStatusDate",
      "update": "data('dataStatus').length > 0 && isValid(data('dataStatus')[0].dataStatusDate) ? data('dataStatus')[0].dataStatusDate : datetime(year(now()), month(now()), date(now()))"
    },
    {
      "name": "contentHeight",
      "update": "data('contentExtent').length > 0 && isValid(data('contentExtent')[0].maxBlockEnd) ? topPadding + data('contentExtent')[0].maxBlockEnd * rowHeight + rowHeight : topPadding"
    }
  ],
  "data": [
    {
      "name": "dataset"
    },
    {
      "name": "dataStatus",
      "source": "dataset",
      "transform": [
        {
          "type": "aggregate",
          "fields": [
            "SourceDataTimestamp"
          ],
          "ops": [
            "max"
          ],
          "as": [
            "dataStatusRaw"
          ]
        },
        {
          "type": "formula",
          "as": "dataStatusDate",
          "expr": "toDate(datum.dataStatusRaw)"
        }
      ]
    },
    {
      "name": "projectLabels",
      "source": "dataset",
      "transform": [
        {
          "type": "aggregate",
          "groupby": [
            "Projektdefinition DB"
          ],
          "fields": [
            "Projektdefinition DB Bezeichnung"
          ],
          "ops": [
            "max"
          ],
          "as": [
            "Projektbezeichnung"
          ]
        },
        {
          "type": "formula",
          "as": "projectSort",
          "expr": "toString(datum['Projektdefinition DB'])"
        }
      ]
    },
    {
      "name": "modelsBase",
      "source": "dataset",
      "transform": [
        {
          "type": "aggregate",
          "groupby": [
            "Projektdefinition DB",
            "Vorgangsmodell"
          ]
        },
        {
          "type": "formula",
          "as": "projectSort",
          "expr": "toString(datum['Projektdefinition DB'])"
        },
        {
          "type": "formula",
          "as": "modelSort",
          "expr": "isValid(datum['Vorgangsmodell']) && trim(toString(datum['Vorgangsmodell'])) !== '' ? trim(toString(datum['Vorgangsmodell'])) : ''"
        },
        {
          "type": "formula",
          "as": "hasNoModel",
          "expr": "datum.modelSort === ''"
        },
        {
          "type": "formula",
          "as": "modelKey",
          "expr": "datum.projectSort + '||' + datum.modelSort"
        }
      ]
    },
    {
      "name": "projectModelCounts",
      "source": "modelsBase",
      "transform": [
        {
          "type": "filter",
          "expr": "datum.modelSort !== ''"
        },
        {
          "type": "aggregate",
          "groupby": [
            "projectSort"
          ],
          "ops": [
            "count"
          ],
          "as": [
            "modelCount"
          ]
        }
      ]
    },
    {
      "name": "catalogsBase",
      "source": "dataset",
      "transform": [
        {
          "type": "filter",
          "expr": "isValid(datum['Freier Filterkatalog']) && trim(toString(datum['Freier Filterkatalog'])) !== ''"
        },
        {
          "type": "filter",
          "expr": "!(trim(toString(datum['Freier Filterkatalog'])) === 'TK00' && (!isValid(datum['Vorgangsmodell']) || trim(toString(datum['Vorgangsmodell'])) === ''))"
        },
        {
          "type": "aggregate",
          "groupby": [
            "Projektdefinition DB",
            "Vorgangsmodell",
            "Freier Filterkatalog",
            "Freier Filterkatalog Bezeichnung"
          ]
        },
        {
          "type": "formula",
          "as": "projectSort",
          "expr": "toString(datum['Projektdefinition DB'])"
        },
        {
          "type": "formula",
          "as": "modelSort",
          "expr": "isValid(datum['Vorgangsmodell']) && trim(toString(datum['Vorgangsmodell'])) !== '' ? trim(toString(datum['Vorgangsmodell'])) : ''"
        },
        {
          "type": "formula",
          "as": "catalogSort",
          "expr": "trim(toString(datum['Freier Filterkatalog']))"
        },
        {
          "type": "formula",
          "as": "modelKey",
          "expr": "datum.projectSort + '||' + datum.modelSort"
        },
        {
          "type": "formula",
          "as": "catalogKey",
          "expr": "datum.projectSort + '||' + datum.modelSort + '||' + datum.catalogSort"
        },
        {
          "type": "formula",
          "as": "label",
          "expr": "datum.catalogSort + (isValid(datum['Freier Filterkatalog Bezeichnung']) && trim(toString(datum['Freier Filterkatalog Bezeichnung'])) !== '' ? ' - ' + trim(toString(datum['Freier Filterkatalog Bezeichnung'])) : '')"
        },
        {
          "type": "window",
          "groupby": [
            "modelKey"
          ],
          "sort": {
            "field": [
              "catalogSort"
            ],
            "order": [
              "ascending"
            ]
          },
          "ops": [
            "row_number"
          ],
          "as": [
            "catalogIndex"
          ]
        }
      ]
    },
    {
      "name": "catalogCounts",
      "source": "catalogsBase",
      "transform": [
        {
          "type": "aggregate",
          "groupby": [
            "modelKey"
          ],
          "ops": [
            "count"
          ],
          "as": [
            "catalogCount"
          ]
        }
      ]
    },
    {
      "name": "modelBlocks",
      "source": "modelsBase",
      "transform": [
        {
          "type": "lookup",
          "from": "catalogCounts",
          "key": "modelKey",
          "fields": [
            "modelKey"
          ],
          "values": [
            "catalogCount"
          ],
          "as": [
            "catalogCount"
          ]
        },
        {
          "type": "lookup",
          "from": "projectModelCounts",
          "key": "projectSort",
          "fields": [
            "projectSort"
          ],
          "values": [
            "modelCount"
          ],
          "as": [
            "modelCount"
          ]
        },
        {
          "type": "lookup",
          "from": "projectLabels",
          "key": "projectSort",
          "fields": [
            "projectSort"
          ],
          "values": [
            "Projektbezeichnung"
          ],
          "as": [
            "Projektbezeichnung"
          ]
        },
        {
          "type": "formula",
          "as": "catalogCountClean",
          "expr": "isValid(datum.catalogCount) ? datum.catalogCount : 0"
        },
        {
          "type": "formula",
          "as": "modelCountClean",
          "expr": "isValid(datum.modelCount) ? datum.modelCount : 0"
        },
        {
          "type": "formula",
          "as": "ProjektbezeichnungKurz",
          "expr": "isValid(datum.Projektbezeichnung) ? (length(toString(datum.Projektbezeichnung)) > projectDescriptionLength ? substring(toString(datum.Projektbezeichnung), 0, projectDescriptionLength) + '…' : toString(datum.Projektbezeichnung)) : ''"
        },
        {
          "type": "formula",
          "as": "headerLabel",
          "expr": "datum.projectSort + ' - ' + datum.ProjektbezeichnungKurz + (datum.hasNoModel ? '' : ' | ' + datum.modelSort)"
        },
        {
          "type": "formula",
          "as": "blockSize",
          "expr": "datum.catalogCountClean + 1"
        },
        {
          "type": "stack",
          "groupby": [],
          "sort": {
            "field": [
              "projectSort",
              "modelSort"
            ],
            "order": [
              "ascending",
              "ascending"
            ]
          },
          "field": "blockSize",
          "offset": "zero",
          "as": [
            "blockStart",
            "blockEnd"
          ]
        },
        {
          "type": "formula",
          "as": "headerRow",
          "expr": "datum.blockStart + 1"
        }
      ]
    },
    {
      "name": "contentExtent",
      "source": "modelBlocks",
      "transform": [
        {
          "type": "aggregate",
          "fields": [
            "blockEnd"
          ],
          "ops": [
            "max"
          ],
          "as": [
            "maxBlockEnd"
          ]
        }
      ]
    },
    {
      "name": "catalogs",
      "source": "catalogsBase",
      "transform": [
        {
          "type": "lookup",
          "from": "modelBlocks",
          "key": "modelKey",
          "fields": [
            "modelKey"
          ],
          "values": [
            "headerRow"
          ],
          "as": [
            "headerRow"
          ]
        },
        {
          "type": "formula",
          "as": "rowIndex",
          "expr": "datum.headerRow + datum.catalogIndex"
        }
      ]
    },
    {
      "name": "milestones",
      "source": "dataset",
      "transform": [
        {
          "type": "filter",
          "expr": "isValid(datum['Datum Ende']) && isValid(datum['Freier Filterkatalog']) && trim(toString(datum['Freier Filterkatalog'])) !== ''"
        },
        {
          "type": "filter",
          "expr": "!(trim(toString(datum['Freier Filterkatalog'])) === 'TK00' && (!isValid(datum['Vorgangsmodell']) || trim(toString(datum['Vorgangsmodell'])) === ''))"
        },
        {
          "type": "formula",
          "as": "projectSort",
          "expr": "toString(datum['Projektdefinition DB'])"
        },
        {
          "type": "formula",
          "as": "modelSort",
          "expr": "isValid(datum['Vorgangsmodell']) && trim(toString(datum['Vorgangsmodell'])) !== '' ? trim(toString(datum['Vorgangsmodell'])) : ''"
        },
        {
          "type": "formula",
          "as": "catalogSort",
          "expr": "trim(toString(datum['Freier Filterkatalog']))"
        },
        {
          "type": "formula",
          "as": "catalogKey",
          "expr": "datum.projectSort + '||' + datum.modelSort + '||' + datum.catalogSort"
        },
        {
          "type": "formula",
          "as": "milestoneDate",
          "expr": "toDate(datum['Datum Ende'])"
        },
        {
          "type": "filter",
          "expr": "isValid(datum.milestoneDate)"
        },
        {
          "type": "lookup",
          "from": "catalogs",
          "key": "catalogKey",
          "fields": [
            "catalogKey"
          ],
          "values": [
            "rowIndex"
          ],
          "as": [
            "rowIndex"
          ]
        },
        {
          "type": "filter",
          "expr": "isValid(datum.rowIndex)"
        },
        {
          "type": "formula",
          "as": "statusNormalized",
          "expr": "lower(trim(isValid(datum['Status Meilenstein']) ? toString(datum['Status Meilenstein']) : ''))"
        },
        {
          "type": "formula",
          "as": "milestoneColor",
          "expr": "datum.statusNormalized === 'erledigt' ? '#B3B3B3' : datum.statusNormalized === 'überfällig' ? '#FF0000' : datum.statusNormalized === 'fällig kurzfristig' ? '#FFFF00' : datum.statusNormalized === 'fällig mittelfristig' ? '#000000' : datum.statusNormalized === 'fällig langfristig' ? '#000000' : '#000000'"
        },
        {
          "type": "formula",
          "as": "milestoneLabel",
          "expr": "(isValid(datum['Katalogvorgang']) ? toString(datum['Katalogvorgang']) : '') + (isValid(datum['Vorgangsbezeichnung DB En']) && trim(toString(datum['Vorgangsbezeichnung DB En'])) !== '' ? ' - ' + toString(datum['Vorgangsbezeichnung DB En']) : '')"
        }
      ]
    }
  ],
  "scales": [
    {
      "name": "xTime",
      "type": "time",
      "domain": {
        "signal": "safeDomain"
      },
      "range": [
        {
          "signal": "timelineStart"
        },
        {
          "signal": "timelineEnd"
        }
      ],
      "nice": true,
      "zero": false
    }
  ],
  "axes": [],
  "marks": [
    {
      "type": "rule",
      "encode": {
        "update": {
          "x": {
            "signal": "timelineStart"
          },
          "x2": {
            "signal": "timelineStart"
          },
          "y": {
            "value": 0
          },
          "y2": {
            "signal": "contentHeight"
          },
          "stroke": {
            "value": "#D0D0D0"
          },
          "strokeWidth": {
            "value": 1
          }
        }
      }
    },
    {
      "type": "rule",
      "from": {
        "data": "modelBlocks"
      },
      "encode": {
        "enter": {
          "x": {
            "value": 0
          },
          "x2": {
            "signal": "width"
          },
          "y": {
            "signal": "topPadding + (datum.headerRow - 0.5) * rowHeight"
          },
          "stroke": {
            "value": "#E0E0E0"
          },
          "strokeWidth": {
            "value": 1
          }
        }
      }
    },
    {
      "type": "rule",
      "encode": {
        "update": {
          "x": {
            "scale": "xTime",
            "signal": "dataStatusDate"
          },
          "x2": {
            "scale": "xTime",
            "signal": "dataStatusDate"
          },
          "y": {
            "signal": "topPadding"
          },
          "y2": {
            "signal": "contentHeight"
          },
          "stroke": {
            "value": "#FF0000"
          },
          "strokeWidth": {
            "value": 1.5
          },
          "strokeDash": {
            "value": [
              5,
              4
            ]
          },
          "opacity": {
            "signal": "dataStatusDate >= domain('xTime')[0] && dataStatusDate <= domain('xTime')[1] ? 0.9 : 0"
          }
        }
      }
    },
    {
      "type": "text",
      "encode": {
        "update": {
          "x": {
            "scale": "xTime",
            "signal": "dataStatusDate",
            "offset": 5
          },
          "y": {
            "signal": "topPadding + 1"
          },
          "text": {
            "signal": "'Datenstand ' + timeFormat(dataStatusDate, '%d.%m.%Y')"
          },
          "fontSize": {
            "value": 12
          },
          "fontWeight": {
            "value": "bold"
          },
          "fill": {
            "value": "#FF0000"
          },
          "align": {
            "value": "left"
          },
          "baseline": {
            "value": "top"
          },
          "opacity": {
            "signal": "dataStatusDate >= domain('xTime')[0] && dataStatusDate <= domain('xTime')[1] ? 1 : 0"
          }
        }
      }
    },
    {
      "name": "warningSymbol",
      "type": "text",
      "from": {
        "data": "modelBlocks"
      },
      "encode": {
        "update": {
          "x": {
            "value": 8
          },
          "y": {
            "signal": "topPadding + datum.headerRow * rowHeight"
          },
          "text": {
            "value": "⚠"
          },
          "align": {
            "value": "left"
          },
          "baseline": {
            "value": "middle"
          },
          "fontSize": {
            "value": 14
          },
          "fill": {
            "value": "#8D6E63"
          },
          "opacity": {
            "signal": "datum.hasNoModel ? 1 : 0"
          }
        }
      }
    },
    {
      "type": "text",
      "from": {
        "data": "modelBlocks"
      },
      "encode": {
        "update": {
          "x": {
            "signal": "datum.hasNoModel ? 28 : 10"
          },
          "y": {
            "signal": "topPadding + datum.headerRow * rowHeight"
          },
          "text": {
            "field": "headerLabel"
          },
          "align": {
            "value": "left"
          },
          "baseline": {
            "value": "middle"
          },
          "fontSize": {
            "value": 12
          },
          "fontWeight": {
            "value": "bold"
          },
          "fill": {
            "signal": "datum.hasNoModel ? '#8D6E63' : datum.modelCountClean > 1 ? '#1565C0' : '#222222'"
          },
          "limit": {
            "signal": "labelWidth - (datum.hasNoModel ? 38 : 20)"
          }
        }
      }
    },
    {
      "type": "text",
      "from": {
        "data": "catalogs"
      },
      "encode": {
        "enter": {
          "x": {
            "value": 38
          },
          "y": {
            "signal": "topPadding + datum.rowIndex * rowHeight"
          },
          "text": {
            "field": "label"
          },
          "align": {
            "value": "left"
          },
          "baseline": {
            "value": "middle"
          },
          "fontSize": {
            "value": 11
          },
          "fill": {
            "value": "#666666"
          },
          "limit": {
            "signal": "labelWidth - 50"
          }
        }
      }
    },
    {
      "type": "symbol",
      "from": {
        "data": "milestones"
      },
      "encode": {
        "enter": {
          "x": {
            "scale": "xTime",
            "field": "milestoneDate"
          },
          "y": {
            "signal": "topPadding + datum.rowIndex * rowHeight"
          },
          "shape": {
            "value": "diamond"
          },
          "fill": {
            "field": "milestoneColor"
          },
          "stroke": {
            "value": "#222222"
          },
          "strokeWidth": {
            "value": 1
          },
          "tooltip": {
            "signal": "{'Projekt': datum['Projektdefinition DB'], 'Vorgangsmodell': isValid(datum['Vorgangsmodell']) ? datum['Vorgangsmodell'] : '', 'Terminkette': datum['Freier Filterkatalog'], 'Meilenstein': datum.milestoneLabel, 'Datum': timeFormat(datum.milestoneDate, '%d.%m.%Y'), 'Status': isValid(datum['Status Meilenstein']) ? datum['Status Meilenstein'] : ''}"
          }
        },
        "update": {
          "size": {
            "value": 110
          },
          "opacity": {
            "value": 1
          }
        },
        "hover": {
          "size": {
            "value": 190
          },
          "opacity": {
            "value": 0.75
          }
        }
      }
    }
  ]
}
powerbi visualization vega-lite vega deneb