Deneb

repository·main·Indexed 43 days ago

https://github.com/deneb-viz/deneb

A 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.

Tokens
192.2K
Snippets
377
Records
749
Agent score
55%

What's inside Deneb

  1. Overview of Deneb

    main
    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.
  2. Overview of `@deneb-viz/editor` components

    main

    The @deneb-viz/editor package provides the advanced Deneb editing interface. It is built on top of @deneb-viz/app-core and 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.
  3. Implementation Plan for Overlay Scrollbars v2

    main

    The 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:

    1. 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.
    2. Layer 2 (UI Component Replacement): Replaces native <div> scroll containers in visual-viewer.tsx and preview-area.tsx with <OverlayScrollbarsComponent> from overlayscrollbars-react. This provides true overlay behavior with zero gutter reservation and allows for user-styled scrollbars via a shared getScrollbarStyleVars helper.
  4. Use @deneb-viz/eslint-config for internal linting

    main

    The @deneb-viz/eslint-config package 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 via createBoundariesConfig({ entry, layers }). This is intended for application packages like app-core and editor. You should pass the specific folder layers that the package contains, and the dependency matrix will be filtered accordingly.
  5. How the Deneb rendering lifecycle and coordinator work

    main

    Deneb 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

    1. Binding: When a rendering update sets its dataset or compilation, the system calls coordinator.bindPendingRender(id) synchronously within the same execution frame as the update() call. This binds the current update identity to the coordinator.
    2. Asynchronous Painting: React/Vega performs the actual painting asynchronously. When the paint completes, it invokes injected callbacks (onRenderingStarted, onRenderingFinished, or onRenderingError).
    3. Attribution: These callbacks are no-arg adapters that route to the coordinator. The coordinator looks up the pendingRenderId at the moment of the call. If a newer update has already superseded the current one (e.g., via a new open(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, or recover) use coordinator.closeCurrent() to close the current lifecycle immediately.
    • Asynchronous Paths: Rendering paths use bindPendingRender(id) and rely on the eventual invocation of onRendering* callbacks to signal completion or failure.
    • Error Handling: If an error occurs during the update() dispatch, the catch block routes to coordinator.failCurrent(e).
  6. Use the 'Treat as field parameter' toggle for individual fields

    main

    When consolidateFieldParameters is 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 __names field to ensure compatibility with specifications designed for field parameters.

    This state is managed via the treatAsParameter flag within the SupportFieldFlags type in @deneb-viz/data-core.

    export type SupportFieldFlags = {
        highlight: boolean;
        highlightStatus: boolean;
        highlightComparator: boolean;
        format: boolean;
        formatted: boolean;
        treatAsParameter?: boolean;
    };
  7. Understand why reader-mode visuals appear blank on spec errors

    main

    In 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).
  8. How the Overlay Scrollbars v2 architecture works

    main

    The 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:

    1. Layer 1 (Sizing): Handled by patchVegaLiteResponsiveSizing in @deneb-viz/vega-runtime. This layer ensures that Vega-Lite specs using width: 'container' and height: '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.

    2. Layer 2 (Rendering): Handled in @deneb-viz/app-core via visual-viewer.tsx and preview-area.tsx. This layer manages legitimate overflows using the overlayscrollbars and overlayscrollbars-react libraries. 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.

  9. Fix SVG overflow by injecting `autosize: { type: 'fit', contains: 'padding' }`

    main

    To prevent the 10px SVG overflow issue (where the chart is container + 2×padding in size), Deneb must inject specific autosize settings instead of zeroing out padding.

    When Deneb injects width: 'container' and height: 'container', Vega-Lite defaults to autosize.contains: 'content', which places padding outside the container dimensions. By setting autosize: { 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' }
  10. Understand the app-core architecture layering rules

    main

    The packages/app-core package follows a strict, single-direction dependency model based on bulletproof-react layering. This ensures that features are self-contained and the application remains maintainable.

    Dependency Hierarchy

    app/ (Composition) $\rightarrow$ features/ (Domain Logic) $\rightarrow$ shared/ (components/ + lib/)

    LayerMay import fromMay NOT import from
    app/**features/**, components/**, lib/**, state/**, context/**, i18n/**, catalog/**— (Top of stack)
    features/X/**components/**, lib/**, state/**, context/**, i18n/**, catalog/**, sibling files within features/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 filesfeatures/**, components/**, lib/**, app/**

    Exceptions

    1. Transitional API: The root barrel (src/index.ts) re-exports some feature primitives. This is a temporary measure for Power BI compatibility.
    2. State Types: state/ may import shape types from features, but only via the feature's types.ts file (no UI or runtime code).
  11. Best practice: Use renderId for view-bound hooks

    main

    A general rule for this codebase: When a useMemo, useCallback, or useEffect calls into an externally-stateful service (such as VegaViewServices) 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, renderId is the canonical signal that the bound view has been replaced.

    Rule: renderId is mandatory in the dependency arrays of any hook that reads from the current View. Omitting it is a code smell that leads to stale data during spec re-runs or view replacements.