Deneb
repository·main·Indexed 43 days ago
https://github.com/deneb-viz/denebA tool for creating declarative visualizations in Power BI using the Vega and Vega-Lite languages. It provides a powerful alternative to native Power BI visuals, featuring a specification editor powered by Monaco, a template catalog, and a specialized rendering lifecycle coordinator to manage Power BI host integration.
What's inside Deneb
- Deneb is a tool for creating declarative visualizations within Power BI by leveraging the Vega visualization language. It allows users to move beyond standard Power BI visuals by providing the flexibility of Vega and Vega-Lite specifications.
Overview of `@deneb-viz/editor` components
mainThe
@deneb-viz/editorpackage provides the advanced Deneb editing interface. It is built on top of@deneb-viz/app-coreand includes the following functional areas:- Specification Editor: Powered by Monaco.
- Command Bar: For executing editor commands.
- Preview and Debug Areas: For visualizing results and inspecting data.
- Settings Pane: For configuring editor behavior.
- Template Catalog: For accessing predefined templates.
- Export Pane: For exporting editor configurations.
Overview of @deneb-viz/json-processing
mainThe@deneb-viz/json-processingpackage serves as a centralized utility library for handling JSON and JSONC (JSON with comments) processing within the Deneb ecosystem. It leveragesnode-jsonc-parserto provide consistent JSON handling across different Deneb components.Implementation Plan for Overlay Scrollbars v2
mainThe Overlay Scrollbars v2 implementation aims to resolve two specific issues: (1) Vega SVG rendering 10px larger than its container on fitted specs, and (2) native scroll containers reserving gutters for scrollbars.
Architecture Overview
The solution uses a two-layer approach:
- Layer 1 (Vega-Lite Patching): Patches
autosize: { type: 'fit', contains: 'padding' }into Vega-Lite specs. This ensures the rendered SVG (including padding) fits the container exactly. - Layer 2 (UI Component Replacement): Replaces native
<div>scroll containers invisual-viewer.tsxandpreview-area.tsxwith<OverlayScrollbarsComponent>fromoverlayscrollbars-react. This provides true overlay behavior with zero gutter reservation and allows for user-styled scrollbars via a sharedgetScrollbarStyleVarshelper.
- Layer 1 (Vega-Lite Patching): Patches
Use @deneb-viz/eslint-config for internal linting
mainThe
@deneb-viz/eslint-configpackage provides shared ESLint configurations used across the Deneb monorepo. It includes a base configuration for all packages and a specialized configuration for managing dependency boundaries in application packages.Available configurations:
base.js: The shared flat configuration that every workspace package should extend.boundaries.js: A configuration used to enforce a layered dependency matrix. It is initialized viacreateBoundariesConfig({ entry, layers }). This is intended for application packages likeapp-coreandeditor. You should pass the specific folder layers that the package contains, and the dependency matrix will be filtered accordingly.
How the Deneb rendering lifecycle and coordinator work
mainDeneb manages the rendering lifecycle (started, finished, error) through a central
coordinator. To prevent race conditions between synchronous updates and asynchronous React/Vega painting, Deneb uses a pending-render binding mechanism.The Pending-Render Mechanism
- Binding: When a rendering update sets its dataset or compilation, the system calls
coordinator.bindPendingRender(id)synchronously within the same execution frame as theupdate()call. This binds the current update identity to the coordinator. - Asynchronous Painting: React/Vega performs the actual painting asynchronously. When the paint completes, it invokes injected callbacks (
onRenderingStarted,onRenderingFinished, oronRenderingError). - Attribution: These callbacks are no-arg adapters that route to the coordinator. The coordinator looks up the
pendingRenderIdat the moment of the call. If a newer update has already superseded the current one (e.g., via a newopen(B)call), the late-arriving callbacks from the old update (A) are treated as no-ops.
Lifecycle States
- Synchronous Paths: Non-rendering dispatch handlers (like
skip,fetch-more, orrecover) usecoordinator.closeCurrent()to close the current lifecycle immediately. - Asynchronous Paths: Rendering paths use
bindPendingRender(id)and rely on the eventual invocation ofonRendering*callbacks to signal completion or failure. - Error Handling: If an error occurs during the
update()dispatch, thecatchblock routes tocoordinator.failCurrent(e).
- Binding: When a rendering update sets its dataset or compilation, the system calls
Use the 'Treat as field parameter' toggle for individual fields
mainWhen
consolidateFieldParametersis enabled in the project settings, you can manually treat a regular field as a field parameter. This wraps the field's values in a single-element array and adds a companion__namesfield to ensure compatibility with specifications designed for field parameters.This state is managed via the
treatAsParameterflag within theSupportFieldFlagstype in@deneb-viz/data-core.export type SupportFieldFlags = { highlight: boolean; highlightStatus: boolean; highlightComparator: boolean; format: boolean; formatted: boolean; treatAsParameter?: boolean; };Understand why reader-mode visuals appear blank on spec errors
mainIn Deneb, when a specification fails to parse or compile, the visual is designed to render blank for report consumers (in reader or view mode). There is no in-canvas error message, overlay, or banner displayed to the end user.
Key Concepts
- Intentional Design: This is not a defect. The goal is to prevent developer-facing technical diagnostics (like raw compilation errors) from being shown to report consumers, who are not the intended audience for spec debugging.
- Error Escalation Path: If a consumer sees a blank visual, they are expected to notify the report author. The author can then use the Deneb editor to view the specific errors in the debug area (
log-viewer.tsx). - Error Handling Logic: While the visual remains blank, errors still propagate correctly as data (
compilation.result.errors), and the stale view/DOM is cleared during the error process (vega-embed.tsx).
How the Overlay Scrollbars v2 architecture works
mainThe Overlay Scrollbars v2 design addresses two distinct issues: SVG overflow and non-styled scrollbars. It uses a two-layer architecture to separate sizing logic from scrollbar rendering:
Layer 1 (Sizing): Handled by
patchVegaLiteResponsiveSizingin@deneb-viz/vega-runtime. This layer ensures that Vega-Lite specs usingwidth: 'container'andheight: 'container'actually fit their containers without the 10px overflow caused by default padding. This layer is verified first; if successful, fitted specs will show no scrollbars at all.Layer 2 (Rendering): Handled in
@deneb-viz/app-coreviavisual-viewer.tsxandpreview-area.tsx. This layer manages legitimate overflows using theoverlayscrollbarsandoverlayscrollbars-reactlibraries. It replaces the native browser scrollbars with styled overlay scrollbars that respect user-configured width and radius settings, providing true overlay behavior with zero gutter reservation.
Fix SVG overflow by injecting `autosize: { type: 'fit', contains: 'padding' }`
mainTo prevent the 10px SVG overflow issue (where the chart is
container + 2×paddingin size), Deneb must inject specificautosizesettings instead of zeroing out padding.When Deneb injects
width: 'container'andheight: 'container', Vega-Lite defaults toautosize.contains: 'content', which places padding outside the container dimensions. By settingautosize: { type: 'fit', contains: 'padding' }, the total rendered size (including padding) is forced to fit within the container. This approach preserves user-specified padding while ensuring the SVG does not trigger unwanted scrollbars.// The target configuration to inject: autosize: { type: 'fit', contains: 'padding' }Understand the app-core architecture layering rules
mainThe
packages/app-corepackage follows a strict, single-direction dependency model based onbulletproof-reactlayering. This ensures that features are self-contained and the application remains maintainable.Dependency Hierarchy
app/(Composition) $\rightarrow$features/(Domain Logic) $\rightarrow$shared/(components/+lib/)Layer May import from May NOT import from app/**features/**,components/**,lib/**,state/**,context/**,i18n/**,catalog/**— (Top of stack) features/X/**components/**,lib/**,state/**,context/**,i18n/**,catalog/**, sibling files withinfeatures/X/**features/Y/**(any other feature),app/**components/**lib/**,state/**,context/**,i18n/**,catalog/**features/**,app/**lib/**state/**(read-only via selectors),context/**,i18n/**,catalog/**features/**,components/**,app/**state/**,context/**,i18n/**,catalog/**each other, sibling files features/**,components/**,lib/**,app/**Exceptions
- Transitional API: The root barrel (
src/index.ts) re-exports some feature primitives. This is a temporary measure for Power BI compatibility. - State Types:
state/may import shape types from features, but only via the feature'stypes.tsfile (no UI or runtime code).
- Transitional API: The root barrel (
Best practice: Use renderId for view-bound hooks
mainA general rule for this codebase: When a
useMemo,useCallback, oruseEffectcalls into an externally-stateful service (such asVegaViewServices) rather than computing purely from React props and state, every channel that signals "the external state may now differ" must be included in the dependency array.In the context of view-bound hooks consuming
VegaViewServices,renderIdis the canonical signal that the bound view has been replaced.Rule:
renderIdis mandatory in the dependency arrays of any hook that reads from the currentView. Omitting it is a code smell that leads to stale data during spec re-runs or view replacements.