Quadrants
Configure the two-axis Quadrants visualization
Overview
The Quadrants visualization plots entities on a horizontal and vertical metric axis. Optional bubble sizing encodes a third metric. Entities that share the same coordinates are grouped into a cluster badge; clicking the badge expands the cluster so individual entities can be selected. Register it in the settings.visualization array:
import type { Config, Visualizations } from "@envisioning/app";
const Quadrants: Visualizations["quadrants"] = {
type: "quadrants",
label: { en: "Quadrants" },
entityPageId: "technology",
horizontal: [{ joinKey: "trl" }],
vertical: [{ joinKey: "doi" }],
bubble: [{ joinKey: "cost" }],
};
const settings: Config["settings"] = {
visualization: [Quadrants],
// ...
};Multiple visualizations can coexist in the same project (for example, Radar and Quadrants):
visualization: [Radar, Quadrants],Core Properties
| Property | Type | Required | Description |
|---|---|---|---|
type | "quadrants" | Yes | Visualization type |
label | I18n | Yes | Display label |
entityPageId | string | Yes | Target page for entity clicks |
horizontal | array | Yes | Horizontal (X) axis metric(s) |
vertical | array | Yes | Vertical (Y) axis metric(s) |
bubble | array | No | Bubble size metric(s) |
settings | object | No | Visual appearance settings |
showDownloadButton | boolean | No | Show download button (default: true) |
titlePath | string | No | Override entity title path from schema |
toolbar | array | No | Custom toolbar items |
toolbarOptions | array | No | Custom toolbar option menus |
Metric Dimensions
horizontal, vertical, and bubble accept the same metric configuration shape used by the Radar:
{
horizontal: [
{
label: { en: "TRL" },
joinKey: "trl",
scale: { type: "linear" },
},
],
}Dimension Properties
| Property | Type | Description |
|---|---|---|
label | I18n | Optional override label |
joinKey | string | Schema join key for the metric |
scale | object | D3 scale configuration |
showOption | boolean | Show in toolbar menu (default: true) |
forceDomain | [number, number] | Override metric domain |
Scale Types
scale: { type: "linear" } // default
scale: { type: "symlog", constant: 1 }
scale: { type: "pow", exponent: 2 }
scale: { type: "sqrt" }Each dimension requires a metric join. A non-metric join key causes a runtime error:
horizontal schema is not a metric. Check horizontal metric for quadrants visualization.
vertical schema is not a metric. Check vertical metric for quadrants visualization.
bubble schema is not a metric. Check bubble metric for quadrants visualization.Horizontal (X Axis)
Controls where entities appear along the horizontal axis. The active metric is selected from the toolbar.
horizontal: [
{
label: { en: "TRL" },
joinKey: "trl",
},
{
label: { en: "Cost" },
joinKey: "cost",
},
],Vertical (Y Axis)
Controls where entities appear along the vertical axis. The active metric is selected from the toolbar.
vertical: [
{
label: { en: "Diffusion of Innovation" },
joinKey: "doi",
},
{
label: { en: "TRL" },
joinKey: "trl",
},
],Changing the horizontal or vertical metric recenters the chart zoom.
Bubble (Size)
Controls entity dot and bubble radius. The active metric is selected from the toolbar. When no bubble option is configured, all entities use dotRadius.
bubble: [
{
label: { en: "TRL" },
joinKey: "trl",
},
{
label: { en: "Cost" },
joinKey: "cost",
},
],The metric value is mapped from the schema domain to circle area between dotRadius + dotPadding and maxBubbleRadius (those settings are radii at the domain endpoints; radius is derived via √area so area is proportional to the value).
Cluster size
Add a virtual bubble option with joinKey: "clusterSize" (no schema join). When selected, cluster badge radius and entity bubble radius scale by the number of entities at the same coordinates:
- Domain:
[1, maxClusterSize]wheremaxClusterSizeis the largest overlap group on the chart. - Range:
[minClusterRadius, maxBubbleRadius]as radii at domain endpoints; cluster count maps to area between those bounds. - Solo entities (not in a multi-item cluster) use count
1and stay at their metric position. - The cluster badge scales with group size; after opening a cluster, each exploded entity bubble uses count
1(minimum radius) so individuals are smaller than the badge.
{
label: { en: "Cluster size" },
joinKey: "clusterSize",
},Metric bubble options keep a fixed cluster badge radius (minClusterRadius) regardless of group size.
Overlapping Entities (Clusters)
Entities with identical horizontal and vertical metric values are rendered as a single cluster circle showing the count. Clicking the cluster opens an explosion layout: entities are placed on concentric rings around a close control so each can be clicked individually. Exploded entities use a fixed hit target of dotRadius + dotPadding (independent of bubble size). Ring spacing between hit areas uses maxBubbleRadius when a metric bubble option is active, or minClusterRadius when the bubble option is clusterSize; entities on each ring are equally spaced. Click the fog overlay or the close button to collapse the cluster.
| Setting | Default | Description |
|---|---|---|
minClusterRadius | 2 × dotRadius | Minimum cluster badge radius; fixed badge size for metric bubble options; lower bound of the cluster-size scale |
clusterFontSize | 18 | Font size for the count label |
Chart Results
The toolbar Chart Results menu controls how discrete metricResults labels are displayed. This is session state (not config); the default is vertical axis results.
| Mode | Description |
|---|---|
| Vertical axis results | Show metricResults labels along the vertical axis |
| Horizontal axis results | Show metricResults labels along the horizontal axis |
| Intersection results | Show quadrant labels from intersectionSettings |
| None | Hide all chart result labels |
Intersection results only appears when at least one quadrant label (topLeftLabel, topRightLabel, bottomLeftLabel, bottomRightLabel) resolves to non-empty text.
Axis result labels are only rendered for metrics with metricType other than "CONTINUOUS".
Visual Settings
settings: {
color: "{collection.color.hex}", // entity fill; default: "accent"
dotRadius: 5,
dotPadding: 3,
maxBubbleRadius: 30,
bubbleOpacity: 0.3,
chartWidth: 1500,
chartHeight: 1000,
chartPadding: 20,
showGrid: false,
resultSettings: { /* axis result labels */ },
verticalAxisSettings: { /* Y axis ruler */ },
horizontalAxisSettings: { /* X axis ruler */ },
intersectionSettings: { /* quadrant labels */ },
},Chart Layout
| Setting | Default | Description |
|---|---|---|
color | "accent" | Fill color (descriptor or template) |
dotRadius | 5 | Base dot radius |
dotPadding | 3 | Added to dotRadius for minimum bubble radius |
maxBubbleRadius | 30 | Maximum bubble radius at the top of the metric domain (area-encoded) |
bubbleOpacity | 0.3 | Opacity of sized bubbles |
chartWidth | 1500 | Plot width in SVG user units |
chartHeight | 1000 | Plot height in SVG user units |
chartPadding | 20 | Inset from chart edges |
showGrid | false | Grid lines aligned with axis ticks |
Axis Settings (verticalAxisSettings / horizontalAxisSettings)
Both axes share the same shape. Defaults apply independently per axis.
verticalAxisSettings: {
originPosition: "center",
showTitle: true,
showTicks: true,
showLine: true,
tickStep: 1,
showTicksLabels: "values",
titlePosition: "outside",
padding: 20,
tickLabelSize: 14,
titleSize: 18,
},| Property | Default | Description |
|---|---|---|
originPosition | "center" | Ruler line position: center, min, max, origin (uses metricOrigin), outside |
showTitle | true | Show metric title |
showTicks | true | Show tick marks |
showLine | true | Show axis line |
tickStep | 1 | Step between tick values |
showTicksLabels | "values" | false, "values", or "labels" (uses metricResults with numeric fallback) |
titlePosition | "outside" | "inside" or "outside" relative to plot center |
padding | 20 | Space between axis line and title or tick labels |
tickLabelSize | 14 | Tick label font size |
titleSize | 18 | Metric title font size |
Result Settings (resultSettings)
Styles labels drawn from metricResults along the active axis (vertical or horizontal chart-results mode).
resultSettings: {
fontSize: 36,
fontWeight: "normal",
textColor: "disabled",
textOpacity: 1,
textTranslate: 0,
},| Property | Default | Description |
|---|---|---|
fontSize | 36 | Label font size (number or CSS string) |
fontWeight | "normal" | normal, light, or bold |
textColor | "disabled" | Theme color token |
textOpacity | 1 | Label opacity |
textTranslate | 0 | Offset from axis (positive moves up on vertical axis) |
Intersection Settings (intersectionSettings)
Labels for the four quadrants when Intersection results is selected.
intersectionSettings: {
topLeftLabel: { en: "Explore" },
topRightLabel: { en: "Adopt" },
bottomLeftLabel: { en: "Assess" },
bottomRightLabel: { en: "Scale" },
labelPosition: "center",
labelPadding: 24,
labelFontSize: 36,
labelOpacity: 1,
labelFontWeight: "normal",
labelColor: "disabled",
},| Property | Default | Description |
|---|---|---|
topLeftLabel | — | Top-left quadrant label |
topRightLabel | — | Top-right quadrant label |
bottomLeftLabel | — | Bottom-left quadrant label |
bottomRightLabel | — | Bottom-right quadrant label |
labelPosition | "center" | center, corner, or origin |
labelPadding | 24 | Inset for corner and origin placement |
labelFontSize | 36 | Font size |
labelOpacity | 1 | Label opacity |
labelFontWeight | "normal" | normal, light, or bold |
labelColor | "disabled" | Theme color token |
Toolbar
When horizontal, vertical, or bubble arrays are provided, the visualization renders a toolbar with:
- Vertical scale — single-select over
verticaloptions - Horizontal scale — single-select over
horizontaloptions - Bubble — single-select over
bubbleoptions (when configured) - Chart Results — single-select for axis or intersection label display
Options with showOption: false are hidden from the menu.
Session preferences (selected indices) are stored per visualization index and default to the first horizontal option, second vertical option, first bubble option, and vertical chart results.
Zoom and Navigation
The chart supports pan and zoom. Selecting an entity via URL or click zooms to that entity. Changing axis metrics recenters the view. Entity selection uses slug-first matching (same as Radar and cards).
Complete Example
import type { Visualizations } from "@envisioning/app";
const metricOptions = [
{ label: { en: "TRL" }, joinKey: "trl" },
{ label: { en: "Diffusion of Innovation" }, joinKey: "doi" },
{ label: { en: "Cost" }, joinKey: "cost" },
] as const;
export const Quadrants: Visualizations["quadrants"] = {
type: "quadrants",
label: { en: "Quadrants", pt: "Quadrantes" },
entityPageId: "technology",
showDownloadButton: true,
settings: {
color: "{collection.color.hex}",
dotRadius: 6,
maxBubbleRadius: 40,
bubbleOpacity: 0.5,
chartPadding: 150,
resultSettings: {
fontSize: 48,
fontWeight: "bold",
textColor: "border",
textTranslate: 30,
},
intersectionSettings: {
topLeftLabel: { en: "Explore", pt: "Explorar" },
topRightLabel: { en: "Adopt", pt: "Adotar" },
bottomLeftLabel: { en: "Assess", pt: "Avaliar" },
bottomRightLabel: { en: "Scale", pt: "Escalar" },
labelPosition: "center",
labelFontSize: 36,
labelPadding: 120,
},
verticalAxisSettings: {
originPosition: "outside",
showTicksLabels: "values",
titlePosition: "outside",
padding: 30,
tickLabelSize: 18,
titleSize: 24,
},
horizontalAxisSettings: {
originPosition: "outside",
showTicksLabels: "values",
titlePosition: "outside",
padding: 30,
tickLabelSize: 18,
titleSize: 24,
},
},
horizontal: [...metricOptions],
vertical: [...metricOptions],
bubble: [...metricOptions],
};Validation
The configuration validates that:
entityPageIdexists in the pages array- All
joinKeyvalues inhorizontal,vertical, andbubbleexist in the schema joins
If validation fails:
Page "technology" not found in pages. Check visualization: quadrants
Join key "trl" not found in the schema. Check: quadrants - horizontal, joinKey: trl