npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@detain/dbrel-viz

v1.0.0

Published

Interactive database relationship visualization library with 20 swappable rendering engines (JointJS, Cytoscape, D3, Sigma, vis.js, GoJS, XYFlow, and more)

Readme

dbrel-viz

Interactive database relationship visualization — 20 rendering engines, one unified API.

npm version license node renderers build coverage PRs Welcome

Swap between JointJS, Cytoscape, D3, vis.js, Sigma, GoJS, XYFlow and 13 more — zero code changes.

Quick StartRenderersAPIExamplesCompanion Packages


Notedbrel-viz is purely a rendering frontend. It expects a JSON payload describing tables, rows, and computed relationships. Use one of our companion data packages (PHP or Node.js) to produce that payload, or generate it yourself.

Table of Contents


Why dbrel-viz?

Most graph-visualization tools lock you in. Pick D3 and you own the D3 learning curve forever. Pick Cytoscape and you're stuck if you hate its style system. dbrel-viz is the abstraction layer.

   ┌────────────────┐
   │  Your Data     │  (any source, any backend)
   └───────┬────────┘
           │ JSON payload
           ▼
   ┌────────────────────────────────────────────┐
   │        DbRel shell.js (shared core)        │
   │  layout • pivots • distance focus • sidebar│
   └────────────────────────────────────────────┘
           │  common renderer interface
           ▼
   ┌────────┬──────────┬──────┬──────────┬─────┐
   │JointJS │Cytoscape │D3.js │vis.js ...│ 20+ │
   └────────┴──────────┴──────┴──────────┴─────┘

The same data renders everywhere. Your users pick the engine that fits their brain. You never rewrite.

Features

  • 20 renderers, one API — JointJS, Cytoscape, Sigma.js, vis.js, D3, GoJS, force-graph, VivaGraph, Springy, AntV G6, C3, dc.js, NVD3, p5.js, Raphael, Vega, maxGraph, React Diagrams, XYFlow, Recharts
  • Live preview on hover — 1-second hover delay previews renderers in-place; click to confirm, move away to revert
  • Distance-based focus fading — click any node, and the rest of the graph fades based on BFS hop distance
  • Pivot system — re-center the view on any VPS host, switch, VLAN, server, asset or website master with a click
  • Grouped / separate display modes — one node per table, or one node per row
  • Shared smart layout — BFS + column bin-packing algorithm with golden-ratio aspect targeting
  • Link styling by relationship type — direct FKs solid, FIND_IN_SET dashed purple, cross-DB dotted orange
  • Auto color palettes per database — blue palette for primary, green for Kayako, orange for PowerDNS
  • Custom table icons — 90+ built-in table icon mappings; fully customizable
  • Breadcrumb pivot trail — visual path showing how you navigated from customer to current focal point
  • Live scripts & CSS loader — lazy-loads each renderer's CDN deps only when you switch to it
  • Zero build step — plain ES5, loads from src/ directly
  • Row detail modal — click any row to see all fields in a clean table
  • Sidebar with counts — per-table row counts, hover to highlight in renderer
  • Keyboard navigation — arrow keys + Enter to browse renderers
  • Filter by database and relationship type — toolbar chips toggle visibility
  • Zoom controls + fit-to-screen — every renderer implements the same zoom interface
  • D3 version juggling — shell automatically swaps D3 v3/v5/v7 as needed between renderers
  • Works inside AdminLTE 3, Bootstrap, or standalone<div id="db-rel-app"> is all you need

Screenshots

| JointJS (default) | Cytoscape | D3.js Force | | :-: | :-: | :-: | | JointJS screenshot | Cytoscape screenshot | D3 Force screenshot |

| vis.js | GoJS | XYFlow | | :-: | :-: | :-: | | vis.js screenshot | GoJS screenshot | XYFlow screenshot |

Quick Start

Minimal HTML page using dbrel-viz with a static JSON payload:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>dbrel-viz demo</title>
  <!-- jQuery + Bootstrap 4 (AdminLTE 3 compatible) -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>

  <!-- dbrel-viz -->
  <link rel="stylesheet" href="node_modules/@detain/dbrel-viz/src/core/styles.css">
  <script>
    // Override paths BEFORE loading the shell
    window.DbRel = { paths: {
      renderers: '/node_modules/@detain/dbrel-viz/src/renderers/',
      rendererPrefix: '', rendererSuffix: '.js'
    }};
  </script>
</head>
<body>
  <div id="db-rel-app">
    <!-- toolbar, sidebar, paper will be populated by shell.js -->
    <div id="db-rel-paper-wrap"><div id="db-rel-paper"></div></div>
    <input id="db-rel-custid" type="hidden" value="12345">
  </div>

  <script src="node_modules/@detain/dbrel-viz/src/core/shell.js"></script>
  <script src="node_modules/@detain/dbrel-viz/src/renderers/jointjs.js"></script>

  <script>
    // Load data from your own endpoint or just set it directly
    fetch('/api/db-relationships?custid=12345')
      .then(r => r.json())
      .then(data => {
        DbRel.data = data;
        DbRel.renderers['jointjs'].render();
        DbRel.updateSidebar();
      });
  </script>
</body>
</html>

That's it. Now switch to Cytoscape by clicking the library dropdown — the renderer swaps with zero code changes.

Installation

Via npm / yarn

npm install @detain/dbrel-viz
# or
yarn add @detain/dbrel-viz

The package ships its src/ folder directly — there is no build step. You can either:

  1. Serve src/ as static files (Express, nginx, etc.) and reference them from your HTML, or
  2. Bundle via webpack/vite/rollup — each file is a classic script wrapped in an IIFE

Via script tag (no npm)

<link rel="stylesheet"
      href="https://unpkg.com/@detain/dbrel-viz/src/core/styles.css">
<script src="https://unpkg.com/@detain/dbrel-viz/src/core/shell.js"></script>
<script src="https://unpkg.com/@detain/dbrel-viz/src/renderers/jointjs.js"></script>

Resolving file paths from Node

const dbrel = require('@detain/dbrel-viz');
console.log(dbrel.paths.shell);            // absolute path to shell.js
console.log(dbrel.paths.styles);           // absolute path to styles.css
console.log(dbrel.rendererPath('d3'));     // absolute path to the D3 renderer
console.log(dbrel.renderers);              // list of all renderer keys
console.log(dbrel.version);                // current package version

Handy for Express apps:

const express = require('express');
const dbrel = require('@detain/dbrel-viz');
const app = express();

app.use('/dbrel', express.static(require('path').dirname(dbrel.paths.shell) + '/..'));
// Now: /dbrel/core/shell.js, /dbrel/core/styles.css, /dbrel/renderers/*.js

Configuration

Override configuration before shell.js loads by setting window.DbRel early:

<script>
  window.DbRel = {
    paths: {
      renderers:      '/assets/js/',           // where renderer files live
      rendererPrefix: 'db_relationships_',     // prepended to renderer key
      rendererSuffix: '.js',                   // appended to renderer key
      libIcons:       '/assets/lib-icons/',    // renderer logo images
      tableIcons:     '/assets/table-icons/',  // per-table icon images
      ajaxUrl:        '/api/data',             // where to fetch data on load
      ajaxChoice:     'db_relationships_data'  // `choice=` query param
    }
  };
</script>
<script src="/assets/dbrel-viz/core/shell.js"></script>

| Path key | What it controls | | --- | --- | | renderers | Directory URL where renderer JS files are served from | | rendererPrefix | String prepended to each renderer key when building its URL | | rendererSuffix | String appended to each renderer key (usually .js) | | libIcons | Directory URL for the library logo icons used in the dropdown | | tableIcons | Directory URL for the per-table icon PNGs used in node headers | | ajaxUrl | Endpoint queried by DbRel.loadData(custid) | | ajaxChoice | Query-string choice= value sent with data requests |

The final URL a renderer loads from is:

<paths.renderers><paths.rendererPrefix><key><paths.rendererSuffix>

So cytoscape becomes /js/db_relationships_cytoscape.js with the defaults, or /assets/dbrel-viz/src/renderers/cytoscape.js if you point renderers at the source folder and set prefix/suffix to empty.

Data Format

The library consumes a single JSON payload, assigned to DbRel.data. Shape:

{
  "custid": 12345,
  "tables": {
    "my.accounts": {
      "rows":     [ { "account_id": 12345, "account_lid": "demo", ... } ],
      "columns":  ["account_id", "account_lid", ...],
      "total":    1,
      "truncated": false
    },
    "my.vps": {
      "rows":    [ { "vps_id": 99, "vps_hostname": "host.example.com", ... } ],
      "columns": ["vps_id", "vps_hostname", ...],
      "total":   3,
      "truncated": false
    }
  },
  "relationships": [
    {
      "source": "my.accounts",
      "target": "my.vps",
      "source_field": "account_id",
      "target_field": "vps_custid",
      "type": "direct",
      "cardinality": "1:N",
      "label": "Account → VPS",
      "matches": [ [0, [0, 1, 2]] ]
    }
  ],
  "metadata": {
    "databases": ["my", "kayako_v4", "pdns"],
    "table_count": 14,
    "total_rows": 42,
    "relationship_count": 9,
    "query_time_ms": 127.4,
    "custid": 12345,
    "pivot_table": null,
    "pivot_id": null
  },
  "prefixes":     { "accounts": "account_", "vps": "vps_" },
  "primaryKeys":  { "accounts": "account_id", "vps": "vps_id" },
  "hiddenFields": ["password", "api_token"]
}

| Key | Type | Description | | --- | --- | --- | | custid | number | Customer ID (echoed in metadata) | | tables["db.name"].rows | object[] | Row objects (keys are column names) | | tables["db.name"].columns | string[] | Ordered list of column names | | tables["db.name"].total | number | Total matching rows before any limit | | tables["db.name"].truncated | bool | Whether rows was cut short | | relationships[].source | string | "db.table" key of the source | | relationships[].target | string | "db.table" key of the target | | relationships[].source_field | string | Column in source holding the reference | | relationships[].target_field | string | Column in target being referenced | | relationships[].type | string | direct | find_in_set | cross_db | | relationships[].cardinality | string | 1:1 | 1:N | N:1 | N:M | | relationships[].label | string | Human-readable label shown in tooltip | | relationships[].matches | array | [[sourceRowIdx, [targetRowIdxs]], …] | | prefixes[table] | string | Column prefix stripped for display | | primaryKeys[table] | string | PK column used to label nodes | | hiddenFields | string[] | Columns never shown anywhere |

API Reference

Everything lives on the global DbRel namespace (created by core/shell.js).

State

| Property | Type | Description | | --- | --- | --- | | DbRel.data | object \| null | The current payload (see Data Format) | | DbRel.displayMode | "separate" \| "grouped" | One node per row vs. one node per table | | DbRel.showFullContent | bool | Whether to render full cell values instead of truncated | | DbRel.activeRendererKey | string \| null | Key of the currently active renderer | | DbRel.renderers | object | Registry of all registered renderer instances | | DbRel.pivot | object \| null | Current pivot info: { table, id, tableKey, idField, label } | | DbRel.paths | object | Configured paths (see Configuration) | | DbRel.RENDERERS | object | Manifest of all available renderers (name, icon, CDN URLs, category) | | DbRel.PIVOT_TABLES | object | Tables that can serve as a pivot focal point | | DbRel.DB_COLORS | object | Per-database color scheme | | DbRel.TABLE_PALETTES | object | Per-database color palettes for individual tables | | DbRel.LINK_STYLES | object | Stroke styles per relationship type | | DbRel.TABLE_ICONS | object | Per-table icon image map |

Data loading

DbRel.loadData(custid);                      // fetch via AJAX, auto-renders
DbRel.pivotTo(tableKey, rowIndex);           // re-center on a specific row
DbRel.pivotReset();                          // back to the account-centric view
DbRel.loadPivotDirect(table, id, fallbackCustid);  // direct-jump without custid

Rendering & display

DbRel.switchRenderer('cytoscape');           // swap to a different renderer
DbRel.registerRenderer(key, rendererObj);    // register a custom renderer
DbRel.updateSidebar();                       // refresh the sidebar panel
DbRel.resetTableColors();                    // clear the auto-color cache

Node helpers

DbRel.getNodeHeader(tableKey, rowIndex);     // e.g. "accounts 12345"
DbRel.getNodeLines(tableKey, rowIndex);      // array of "field: value" lines
DbRel.computeNodeSize(header, lines);        // { w, h } for layout
DbRel.getGroupedLines(tableKey);             // ASCII-art table for grouped mode
DbRel.computeGroupedNodeSize(tableName, lines);

Layout

// Shared BFS + column bin-packing layout, respecting aspect ratio targets
const positions = DbRel.computeLayout(containerWidth, containerHeight);
// Returns: { [nodeId]: { x, y, w, h } }

Distance-based focus

// BFS distance from a focused node to every other node
const distances = DbRel.computeNodeDistances(focusNodeId);
// { nodeId: 0|1|2|…|Infinity }

DbRel.distanceToOpacity(distance);           // 1.0 | 0.6 | 0.35 | 0.12

Color / display utilities

DbRel.getTableColor(tableKey);               // { header, bg, border }
DbRel.fmtVal(value);                         // smart-truncate for display
DbRel.pickDisplayColumns(columns, tableName);
DbRel.padRight(str, len);
DbRel.getPrimaryKey(tableName);
DbRel.shortenColName(col, tableName);
DbRel.getTableIconHtml(tableName);           // '<img class="…"> ' or ''
DbRel.getTableIconInfo(tableName);           // { type: 'img', src } or null

Tooltip & modal

DbRel.showTooltip(html, x, y);
DbRel.hideTooltip();
DbRel.getLinkTooltipHtml(relData);
DbRel.showRowModal(tableKey, rowIndex);

Pivot helpers

DbRel.getPivotConfig(tableName);             // { idField, label } | null
DbRel.getNodePivotInfo(tableKey, rowIndex);  // { table, id, tableKey, idField, label }

Filter helpers

DbRel.getDbFilters();                        // { my: true, kayako_v4: false, pdns: true }
DbRel.getTypeFilters();                      // { direct: true, find_in_set: true, cross_db: false }

Dynamic script / CSS loading

DbRel.loadScript(url);                       // returns a Promise; caches by URL
DbRel.loadCSS(url);                          // returns a Promise; caches by URL

Renderer Reference

Every renderer implements the same interface — the shell talks to any of them identically.

| Key | Library | Category | License | Unique Strength | | :-- | :-- | :-: | :-: | :-- | | jointjs | JointJS | graph | MPL-2.0 | SVG, orthogonal link routing, custom shapes | | cytoscape | Cytoscape.js | graph | MIT | Rich selector engine, built for biology workloads | | sigma | Sigma.js | graph | MIT | WebGL, handles huge graphs | | visjs | vis.js | graph | Apache-2.0 | Physics simulation, timeline friendly | | d3 | D3.js | graph | BSD-3 | Force layout, custom everything | | gojs | GoJS | graph | Commercial | Polished diagrams, flowchart-grade layouts | | forcegraph | force-graph | graph | MIT | Canvas force layout, buttery-smooth | | vivagraph | VivaGraph | graph | MIT | WebGL, layout algorithm library | | springy | Springy | graph | MIT | Tiny (~4KB), minimal spring layout | | g6 | AntV G6 | graph | MIT | Rich built-in behaviors, enterprise-focused | | c3 | C3.js | chart | MIT | D3 wrapper, clean chart defaults | | dcjs | dc.js | chart | Apache-2.0 | Dimensional crossfilter charts | | nvd3 | NVD3 | chart | Apache-2.0 | Reusable D3 v3 chart components | | p5 | p5.js | other | LGPL-2.1 | Creative-coding canvas, artistic layouts | | raphael | Raphael | other | MIT | Legacy VML/SVG, ultra-compatible | | vega | Vega | other | BSD-3 | Declarative JSON grammar, reproducible | | maxgraph | maxGraph | other | Apache-2.0 | mxGraph successor, diagram editor-grade | | reactdiagrams | React Diagrams | react | MIT | React-native node editor, pre-built iframe | | xyflow | XYFlow | react | MIT | React Flow successor, beautiful out of the box | | recharts | Recharts | chart | MIT | React composable charts |

Categories

  • graph — node-edge graph libraries (most renderers)
  • chart — chart-first libraries that re-purpose their bar/pie primitives into graphs
  • other — everything else (creative-coding, declarative, legacy)
  • react — requires React to be loaded; mounted via the shell's dynamic loader

Writing a Custom Renderer

Every renderer registers itself with DbRel.registerRenderer(key, obj). The object must implement this interface:

(function() {
    'use strict';
    var containerEl, myScene, zoomLevel = 100;

    DbRel.registerRenderer('myrenderer', {
        /** Called once, when the renderer is activated. */
        init: function(el) {
            containerEl = el;
            myScene = new MyLibrary(el);
        },

        /** Called every time data is loaded or display mode changes. */
        render: function() {
            myScene.clear();
            buildFromDbRelData(myScene);
        },

        /** Re-run the layout without rebuilding graph elements. */
        doLayout: function() { myScene.relayout(); },

        /** Zoom controls (percent: 10-400). */
        setZoom: function(pct) { zoomLevel = pct; myScene.setZoom(pct / 100); },
        getZoom: function() { return zoomLevel; },
        fitToScreen: function() { myScene.fit(); },

        /** Filter chips in the toolbar. */
        applyFilters: function(dbFilters, typeFilters) {
            // dbFilters = { my: true|false, kayako_v4: ..., pdns: ... }
            // typeFilters = { direct: ..., find_in_set: ..., cross_db: ... }
        },

        /** Click-to-focus. Dim everything not within 2 hops. */
        focusNode: function(nodeId) { /* ... */ },
        unfocusNode: function()     { /* ... */ },
        centerOnTable: function(tk) { /* ... */ },

        /** Sidebar hover highlight (optional). */
        highlightTable: function(tk)      { /* ... */ },
        clearHighlightTable: function()   { /* ... */ },

        /** Return { nodes, links } for the metadata panel. */
        getStats: function() {
            return { nodes: myScene.nodeCount(), links: myScene.edgeCount() };
        },

        /** Window resize (optional). */
        resize: function() { myScene.resize(); },

        /** Full teardown — the shell calls this before switching to another renderer. */
        destroy: function() {
            if (myScene) myScene.dispose();
            if (containerEl) containerEl.innerHTML = '';
            myScene = null; containerEl = null;
        }
    });
})();

Then add it to the manifest (edit DbRel.RENDERERS before the shell's init fires, or patch the shell):

DbRel.RENDERERS['myrenderer'] = {
    name: 'My Renderer',
    icon: '/icons/mine.svg',
    github: 'https://github.com/me/my-renderer',
    cat: 'graph',
    file: '/js/renderers/myrenderer.js',
    js: ['https://cdn.example.com/my-library.min.js'],
    css: []
};

That's it — it shows up in the library dropdown and works with all the shell's features (pivot, hover, focus fading, etc).

Examples

fetch('/data/customer-12345.json')
    .then(r => r.json())
    .then(data => {
        DbRel.data = data;
        DbRel.renderers[DbRel.activeRendererKey].render();
        DbRel.updateSidebar();
    });
// Try every renderer in rotation
const libs = ['jointjs', 'cytoscape', 'visjs', 'd3', 'sigma'];
let i = 0;
setInterval(() => {
    DbRel.switchRenderer(libs[i % libs.length]);
    i++;
}, 3000);
// Override BEFORE loading shell.js
window.DbRel = {
    paths: { tableIcons: '/my-icons/' },
    TABLE_ICONS: {
        accounts: { img: '/my-icons/person.png' },
        vps:      { img: '/my-icons/server.png' },
        domains:  { img: '/my-icons/globe.png' }
    }
};
// Jump straight to VPS host 42 without loading the customer first
DbRel.loadPivotDirect('vps_masters', 42, 0);
const express = require('express');
const path = require('path');
const dbrel = require('@detain/dbrel-viz');
const app = express();

// Serve the package's src/ at /dbrel
app.use('/dbrel', express.static(path.dirname(dbrel.paths.shell) + '/..'));

app.get('/', (req, res) => res.sendFile(__dirname + '/index.html'));
app.listen(3000);

Architecture

┌────────────────────────────────────────────────────────────────────┐
│                          Your application                          │
│                                                                    │
│   ┌────────────────────────────────────────────────────────────┐   │
│   │                   div#db-rel-app                           │   │
│   │  ┌──────────────┬───────────────────────────────────────┐  │   │
│   │  │              │  Toolbar (lib selector, filters, ...) │  │   │
│   │  │  Sidebar     ├───────────────────────────────────────┤  │   │
│   │  │              │                                       │  │   │
│   │  │  • tables    │       div#db-rel-paper-wrap           │  │   │
│   │  │  • legend    │       └─ div#db-rel-paper             │  │   │
│   │  │  • stats     │          └─ Active renderer's canvas  │  │   │
│   │  │              │                                       │  │   │
│   │  └──────────────┴───────────────────────────────────────┘  │   │
│   └────────────────────────────────────────────────────────────┘   │
└────────────────────────────────────────────────────────────────────┘
              ▲                                       ▲
              │ registers                             │ reads data
              │                                       │
   ┌──────────┴───────────┐               ┌───────────┴───────────┐
   │  Renderer (one of 20)│               │   DbRel.data  (JSON)  │
   │                      │               │                       │
   │  init()              │               │  tables[], rels[],    │
   │  render()            │               │  prefixes, PKs, meta  │
   │  doLayout()          │               │                       │
   │  setZoom() ...       │               └───────────────────────┘
   │  destroy()           │
   └──────────────────────┘

Rendering lifecycle

DbRel.switchRenderer('cytoscape')
   ├─▶ current renderer.destroy()
   ├─▶ reset #db-rel-paper (clear children/styles/classes)
   ├─▶ DbRel.loadCSS(manifest.css[])
   ├─▶ DbRel.loadScript(manifest.js[])  (sequential for dep ordering)
   ├─▶ if first time: DbRel.loadScript(manifest.file)
   ├─▶ new renderer.init(paperEl)
   ├─▶ new renderer.render()
   └─▶ DbRel.updateSidebar()

Layout algorithm (shared across renderers)

  1. Build BFS adjacency from the relationships
  2. Root = the *.accounts table (or first table if absent)
  3. Assign each table to a BFS layer
  4. Sort layers by connectivity (most-connected first)
  5. Pack blocks into columns with bin-packing (keeping within target aspect)
  6. Honor containerWidth / containerHeight — fall back to 16:9 × 0.85

Companion Packages

dbrel-viz is a rendering frontend. Pair it with a data producer:

| Package | Language | Purpose | | :-- | :-- | :-- | | @detain/dbrel-viz | Browser JS | This package — the frontend | | detain/dbrel-data-php | PHP ≥ 7.4 | Collects rows via mysqli, computes matches, emits the JSON | | @detain/dbrel-data-js | Node ≥ 14 | Same output, Node + mysql2/promise |

Data flow end-to-end:

┌──────────┐     ┌───────────────────────────────┐     ┌──────────────┐
│  MySQL   │────▶│  dbrel-data-php               │────▶│              │
│          │     │  (or dbrel-data-js)           │     │  dbrel-viz   │
│  accounts│     │                               │     │  (browser)   │
│  vps     │     │  • Loads schema JSON          │     │              │
│  domains │     │  • Collects rows per table    │JSON │  • 20 libs   │
│  ...     │     │  • Computes relationship      │────▶│  • Pivot     │
└──────────┘     │    matches                    │     │  • Focus     │
                 │  • Emits the payload          │     │              │
                 └───────────────────────────────┘     └──────────────┘

Browser Support

| Browser | Version | | :-- | :-- | | Chrome | Latest | | Firefox | Latest | | Edge | Latest | | Safari | 13+ | | IE 11 | Shell works (ES5); some renderers require polyfills |

Requirements

  • jQuery 3+
  • Bootstrap 4 (for modals and dropdowns)
  • Font Awesome 5 (for toolbar icons, optional)

Contributing

Contributions are welcome!

git clone https://github.com/detain/dbrel-viz.git
cd dbrel-viz
npm install
npm test

Ideas we'd love help with

  • Additional renderers (e.g. mermaid, nvd3-network, chartjs-graph)
  • TypeScript definitions for the DbRel namespace
  • Per-renderer screenshot generation
  • Storybook with example payloads

PR guidelines

  1. One feature or fix per PR
  2. Keep the public shell API stable — new functionality goes on renderer interfaces
  3. Add a Jest test for any change to core/shell.js
  4. Run npm test before pushing
  5. Lowercase, descriptive commit messages (add cytoscape dim on focus, fix zoom in grouped mode)

Code style

  • Plain ES5 in the shell (must work without a build step)
  • Modern ES in renderers is fine if the target library requires it
  • No frameworks in core/ — jQuery for DOM, Bootstrap for modals

License

MIT © 2025 Joe Huss / InterServer


⬆ back to top

Made with care by InterServer — because one graph library is never enough.