Merge branch 'develop' into release/10.2.0

pull/4337/head
Knut Sveidqvist 2023-05-15 09:50:02 +02:00
commit 132ce2fb4b
24 changed files with 2341 additions and 7 deletions

View File

@ -0,0 +1,163 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
describe('Quadrant Chart', () => {
it('should render if only chart type is provided', () => {
imgSnapshotTest(
`
quadrantChart
`,
{}
);
cy.get('svg');
});
it('should render a complete quadrant chart', () => {
imgSnapshotTest(
`
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
`,
{}
);
cy.get('svg');
});
it('should render without points', () => {
imgSnapshotTest(
`
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
`,
{}
);
cy.get('svg');
});
it('should able to render y-axix on right side', () => {
imgSnapshotTest(
`
%%{init: {"quadrantChart": {"yAxisPosition": "right"}}}%%
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
`,
{}
);
cy.get('svg');
});
it('should able to render x-axix on bottom', () => {
imgSnapshotTest(
`
%%{init: {"quadrantChart": {"xAxisPosition": "bottom"}}}%%
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
`,
{}
);
cy.get('svg');
});
it('should able to render x-axix on bottom and y-axis on right', () => {
imgSnapshotTest(
`
%%{init: {"quadrantChart": {"xAxisPosition": "bottom", "yAxisPosition": "right"}}}%%
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
`,
{}
);
cy.get('svg');
});
it('should render without title', () => {
imgSnapshotTest(
`
quadrantChart
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
`,
{}
);
cy.get('svg');
});
it('should use all the config', () => {
imgSnapshotTest(
`
%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600, "titlePadding": 20, "titleFontSize": 10, "quadrantPadding": 20, "quadrantTextTopPadding": 40, "quadrantLabelFontSize": 20, "quadrantInternalBorderStrokeWidth": 3, "quadrantExternalBorderStrokeWidth": 5, "xAxisLabelPadding": 20, "xAxisLabelFontSize": 20, "yAxisLabelPadding": 20, "yAxisLabelFontSize": 20, "pointTextPadding": 20, "pointLabelFontSize": 20, "pointRadius": 10 }}}%%
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
`,
{}
);
cy.get('svg');
});
it('should use all the theme variable', () => {
imgSnapshotTest(
`
%%{init: {"themeVariables": {"quadrant1Fill": "#b4dcff","quadrant2Fill": "#fef0ff", "quadrant3Fill": "#fffaf0", "quadrant4Fill": "#f0fff2", "quadrant1TextFill": "#ff0000", "quadrant2TextFill": "#2d00df", "quadrant3TextFill": "#00ffda", "quadrant4TextFill": "#e68300", "quadrantPointFill": "#0149ff", "quadrantPointTextFill": "#dc00ff", "quadrantXAxisTextFill": "#ffb500", "quadrantYAxisTextFill": "#fae604", "quadrantInternalBorderStrokeFill": "#3636f2", "quadrantExternalBorderStrokeFill": "#ff1010", "quadrantTitleFill": "#00ea19"} }}%%
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
`,
{}
);
cy.get('svg');
});
});

View File

@ -54,6 +54,9 @@
<li>
<h2><a href="./pie.html">Pie</a></h2>
</li>
<li>
<h2><a href="./quadrantchart.html">Quadrant charts</a></h2>
</li>
<li>
<h2><a href="./requirements.html">Requirements</a></h2>
</li>

55
demos/quadrantchart.html Normal file
View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Mermaid Quick Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
<style>
div.mermaid {
/* font-family: 'trebuchet ms', verdana, arial; */
font-family: 'Courier New', Courier, monospace !important;
}
</style>
</head>
<body>
<h1>Quadrant chart demos</h1>
<pre class="mermaid">
%%{init: {"quadrantChart": {"quadrantPadding": 10}, "theme": "forest", "themeVariables": {"quadrant1TextFill": "blue"}} }%%
quadrantChart
x-axis Urgent --> Not Urgent
y-axis Not Important --> important
quadrant-1 Plan
quadrant-2 Do
quadrant-3 Deligate
quadrant-4 Delete
</pre>
<pre class="mermaid">
%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%%
quadrantChart
title Analytics and Business Intelligence Platforms
x-axis "Completeness of Vision ❤" -->
y-axis Ability to Execute
quadrant-1 Leaders
quadrant-2 Challengers
quadrant-3 Niche
quadrant-4 Visionaries
Microsoft: [0.75, 0.75]
Salesforce: [0.55, 0.60]
IBM: [0.51, 0.40]
Incorta: [0.20, 0.30]
</pre>
<hr />
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
theme: 'default',
logLevel: 3,
securityLevel: 'loose',
});
</script>
</body>
</html>

View File

@ -14,7 +14,7 @@
#### Defined in
[defaultConfig.ts:2115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2115)
[defaultConfig.ts:2293](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2293)
---

View File

@ -235,6 +235,42 @@ journey
Sit down: 5: Me
```
### [Quadrant Chart](../syntax/quadrantChart.md)
```mermaid-example
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```
```mermaid
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```
## Installation
**In depth guides and examples can be found at [Getting Started](./n00b-gettingStarted.md) and [Usage](../config/usage.md).**

View File

@ -0,0 +1,171 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/quadrantChart.md](../../packages/mermaid/src/docs/syntax/quadrantChart.md).
# Quadrant Chart
> A quadrant chart is a visual representation of data that is divided into four quadrants. It is used to plot data points on a two-dimensional grid, with one variable represented on the x-axis and another variable represented on the y-axis. The quadrants are determined by dividing the chart into four equal parts based on a set of criteria that is specific to the data being analyzed. Quadrant charts are often used to identify patterns and trends in data, and to prioritize actions based on the position of data points within the chart. They are commonly used in business, marketing, and risk management, among other fields.
## Example
```mermaid-example
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```
```mermaid
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```
## Syntax
> **Note**
> In place of `<text>` you can use text like `this is a sample text` or inside **double quotes** like `"This type of text may contain unicode like ❤"`.
> **Note**
> If there is no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
> If there are points **x-axis** labels will rendered from left of the respective quadrant also they will be displayed in bottom of the chart, and **y-axis** lables will be rendered in bottom of the respective quadrant, the quadrant text will render at top of the respective quadrant.
> **Note**
> For points x and y value min value is 0 and max value is 1.
### Title
The title is a short description of the chart and it will always render on top of the chart.
#### Example
quadrantChart
title This is a sample example
### x-axis
The x-axis determine what text would be displayed in the x-axis. In x-axis there is two part **left** and **right** you can pass **both** or you can pass only **left**. The statement should start with `x-axis` then the `left axis text` followed by the delimiter `-->` then `right axis text`.
#### Example
1. `x-axis <text> --> <text>` both the left and right axis text will be rendered.
2. `x-axis <text>` only the left axis text will be rendered.
### y-axis
The y-axis determine what text would be displayed in the y-axis. In y-axis there is two part **top** and **bottom** you can pass **both** or you can pass only **bottom**. The statement should start with `y-axis` then the `bottom axis text` followed by the delimiter `-->` then `top axis text`.
#### Example
1. `y-axis <text> --> <text>` both the bottom and top axis text will be rendered.
2. `y-axis <text>` only the bottom axis text will be rendered.
### Quadrants text
The `quadrant-[1,2,3,4]` determine what text would be displayed inside the quadrants.
#### Example
1. `quadrant-1 <text>` determine what text will be rendered inside the top right quadrant.
2. `quadrant-2 <text>` determine what text will be rendered inside the top left quadrant.
3. `quadrant-3 <text>` determine what text will be rendered inside the bottom left quadrant.
4. `quadrant-4 <text>` determine what text will be rendered inside the bottom right quadrant.
### Points
Points are used to plot a circle inside the quadrantChart. The syntax is `<text>: [x, y]` here x and y value is in the range 0 - 1.
#### Example
1. `Point 1: [0.75, 0.80]` here the Point 1 will be drawn in the top right quadrant.
2. `Point 2: [0.35, 0.24]` here the Point 2 will be drawn in the bottom left quadrant.
## Chart Configurations
| Parameter | Description | Default value |
| --------------------------------- | ------------------------------------------------------------------------------------------------- | :-----------: |
| chartWidth | Width of the chart | 500 |
| chartHeight | Height of the chart | 500 |
| titlePadding | Top and Bottom padding of the title | 10 |
| titleFontSize | Title font size | 20 |
| quadrantPadding | Padding outside all the quadrants | 5 |
| quadrantTextTopPadding | Quadrant text top padding when text is drawn on top ( not data points are there) | 5 |
| quadrantLabelFontSize | Quadrant text font size | 16 |
| quadrantInternalBorderStrokeWidth | Border stroke width inside the quadrants | 1 |
| quadrantExternalBorderStrokeWidth | Quadrant external border stroke width | 2 |
| xAxisLabelPadding | Top and bottom padding of x-axis text | 5 |
| xAxisLabelFontSize | X-axis texts font size | 16 |
| xAxisPosition | Position of x-axis (top , bottom) if there are points the x-axis will alway be rendered in bottom | 'top' |
| yAxisLabelPadding | Left and Right padding of y-axis text | 5 |
| yAxisLabelFontSize | Y-axis texts font size | 16 |
| yAxisPosition | Position of y-axis (left , right) | 'left' |
| pointTextPadding | Padding between point and the below text | 5 |
| pointLabelFontSize | Point text font size | 12 |
| pointRadius | Radius of the point to be drawn | 5 |
## Chart Theme Variables
| Parameter | Description |
| -------------------------------- | --------------------------------------- |
| quadrant1Fill | Fill color of the top right quadrant |
| quadrant2Fill | Fill color of the top left quadrant |
| quadrant3Fill | Fill color of the bottom left quadrant |
| quadrant4Fill | Fill color of the bottom right quadrant |
| quadrant1TextFill | Text color of the top right quadrant |
| quadrant2TextFill | Text color of the top left quadrant |
| quadrant3TextFill | Text color of the bottom left quadrant |
| quadrant4TextFill | Text color of the bottom right quadrant |
| quadrantPointFill | Points fill color |
| quadrantPointTextFill | Points text color |
| quadrantXAxisTextFill | X-axis text color |
| quadrantYAxisTextFill | Y-axis text color |
| quadrantInternalBorderStrokeFill | Quadrants inner border color |
| quadrantExternalBorderStrokeFill | Quadrants outer border color |
| quadrantTitleFill | Title color |
## Example on config and theme
```mermaid-example
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
quadrantChart
x-axis Urgent --> Not Urgent
y-axis Not Important --> important
quadrant-1 Plan
quadrant-2 Do
quadrant-3 Deligate
quadrant-4 Delete
```
```mermaid
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
quadrantChart
x-axis Urgent --> Not Urgent
y-axis Not Important --> important
quadrant-1 Plan
quadrant-2 Do
quadrant-3 Deligate
quadrant-4 Delete
```

View File

@ -27,6 +27,7 @@ export interface MermaidConfig {
state?: StateDiagramConfig;
er?: ErDiagramConfig;
pie?: PieDiagramConfig;
quadrantChart?: QuadrantChartConfig;
requirement?: RequirementDiagramConfig;
mindmap?: MindmapDiagramConfig;
gitGraph?: GitGraphDiagramConfig;
@ -226,6 +227,27 @@ export interface PieDiagramConfig extends BaseDiagramConfig {
textPosition?: number;
}
export interface QuadrantChartConfig extends BaseDiagramConfig {
chartWidth: number;
chartHeight: number;
titleFontSize: number;
titlePadding: number;
quadrantPadding: number;
xAxisLabelPadding: number;
yAxisLabelPadding: number;
xAxisLabelFontSize: number;
yAxisLabelFontSize: number;
quadrantLabelFontSize: number;
quadrantTextTopPadding: number;
pointTextPadding: number;
pointLabelFontSize: number;
pointRadius: number;
xAxisPosition: 'top' | 'bottom';
yAxisPosition: 'left' | 'right';
quadrantInternalBorderStrokeWidth: number;
quadrantExternalBorderStrokeWidth: number;
}
export interface ErDiagramConfig extends BaseDiagramConfig {
titleTopMargin?: number;
diagramPadding?: number;

View File

@ -1280,6 +1280,184 @@ const config: Partial<MermaidConfig> = {
textPosition: 0.75,
},
quadrantChart: {
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
* | chartWidth | Width of the chart | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 500
*/
chartWidth: 500,
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
* | chartHeight | Height of the chart | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 500
*/
chartHeight: 500,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
* | titlePadding | Chart title top and bottom padding | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 10
*/
titlePadding: 10,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
* | titleFontSize | Chart title font size | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 20
*/
titleFontSize: 20,
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
* | quadrantPadding | Padding around the quadrant square | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 5
*/
quadrantPadding: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ---------------------- | -------------------------------------------------------------------------- | ------- | -------- | ------------------- |
* | quadrantTextTopPadding | quadrant title padding from top if the quadrant is rendered on top | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 5
*/
quadrantTextTopPadding: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
* | quadrantLabelFontSize | quadrant title font size | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 16
*/
quadrantLabelFontSize: 16,
/**
* | Parameter | Description | Type | Required | Values |
* | --------------------------------- | ------------------------------------------------------------- | ------- | -------- | ------------------- |
* | quadrantInternalBorderStrokeWidth | stroke width of edges of the box that are inside the quadrant | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 1
*/
quadrantInternalBorderStrokeWidth: 1,
/**
* | Parameter | Description | Type | Required | Values |
* | --------------------------------- | -------------------------------------------------------------- | ------- | -------- | ------------------- |
* | quadrantExternalBorderStrokeWidth | stroke width of edges of the box that are outside the quadrant | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 2
*/
quadrantExternalBorderStrokeWidth: 2,
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
* | xAxisLabelPadding | Padding around x-axis labels | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 5
*/
xAxisLabelPadding: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
* | xAxisLabelFontSize | x-axis label font size | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 16
*/
xAxisLabelFontSize: 16,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------- | ------------------------------- | ------- | -------- | ------------------- |
* | xAxisPosition | position of x-axis labels | string | Optional | 'top' or 'bottom' |
*
* **Notes:**
* Default value: top
*/
xAxisPosition: 'top',
/**
* | Parameter | Description | Type | Required | Values |
* | --------------- | ---------------------------------- | ------- | -------- | ------------------- |
* | yAxisLabelPadding | Padding around y-axis labels | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 5
*/
yAxisLabelPadding: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------------ | ---------------------------------- | ------- | -------- | ------------------- |
* | yAxisLabelFontSize | y-axis label font size | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 16
*/
yAxisLabelFontSize: 16,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------- | ------------------------------- | ------- | -------- | ------------------- |
* | yAxisPosition | position of y-axis labels | string | Optional | 'left' or 'right' |
*
* **Notes:**
* Default value: left
*/
yAxisPosition: 'left',
/**
* | Parameter | Description | Type | Required | Values |
* | ---------------------- | -------------------------------------- | ------- | -------- | ------------------- |
* | pointTextPadding | padding between point and point label | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 5
*/
pointTextPadding: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ---------------------- | ---------------------- | ------- | -------- | ------------------- |
* | pointTextPadding | point title font size | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 12
*/
pointLabelFontSize: 12,
/**
* | Parameter | Description | Type | Required | Values |
* | ------------- | ------------------------------- | ------- | -------- | ------------------- |
* | pointRadius | radius of the point to be drawn | number | Optional | Any positive number |
*
* **Notes:**
* Default value: 5
*/
pointRadius: 5,
/**
* | Parameter | Description | Type | Required | Values |
* | ----------- | ----------- | ------- | -------- | ----------- |
* | useMaxWidth | See Notes | boolean | Required | true, false |
*
* **Notes:**
*
* When this flag is set to true, the diagram width is locked to 100% and scaled based on
* available space. If set to false, the diagram reserves its absolute width.
*
* Default value: true
*/
useMaxWidth: true,
},
/** The object containing configurations specific for req diagrams */
requirement: {
useWidth: undefined,

View File

@ -6,6 +6,7 @@ import git from '../diagrams/git/gitGraphDetector.js';
import gantt from '../diagrams/gantt/ganttDetector.js';
import info from '../diagrams/info/infoDetector.js';
import pie from '../diagrams/pie/pieDetector.js';
import quadrantChart from '../diagrams/quadrant-chart/quadrantDetector.js';
import requirement from '../diagrams/requirement/requirementDetector.js';
import sequence from '../diagrams/sequence/sequenceDetector.js';
import classDiagram from '../diagrams/class/classDetector.js';
@ -77,6 +78,7 @@ export const addDiagrams = () => {
git,
stateV2,
state,
journey
journey,
quadrantChart
);
};

View File

@ -0,0 +1,185 @@
%lex
%options case-insensitive
%x string
%x string
%x md_string
%x title
%x open_directive
%x type_directive
%x arg_directive
%x close_directive
%x acc_title
%x acc_descr
%x acc_descr_multiline
%x point_start
%x point_x
%x point_y
%%
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[\n\r]+ return 'NEWLINE';
\%\%[^\n]* /* do nothing */
title { this.begin("title");return 'title'; }
<title>(?!\n|;|#)*[^\n]* { this.popState(); return "title_value"; }
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
<acc_descr>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; }
accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
<acc_descr_multiline>[\}] { this.popState(); }
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
" "*"x-axis"" "* return 'X-AXIS';
" "*"y-axis"" "* return 'Y-AXIS';
" "*\-\-+\>" "* return 'AXIS-TEXT-DELIMITER'
" "*"quadrant-1"" "* return 'QUADRANT_1';
" "*"quadrant-2"" "* return 'QUADRANT_2';
" "*"quadrant-3"" "* return 'QUADRANT_3';
" "*"quadrant-4"" "* return 'QUADRANT_4';
["][`] { this.begin("md_string");}
<md_string>[^`"]+ { return "MD_STR";}
<md_string>[`]["] { this.popState();}
["] this.begin("string");
<string>["] this.popState();
<string>[^"]* return "STR";
\s*\:\s*\[\s* {this.begin("point_start"); return 'point_start';}
<point_start>(1)|(0(.\d+)?) {this.begin('point_x'); return 'point_x';}
<point_start>\s*\]" "* {this.popState();}
<point_x>\s*\,\s* {this.popState(); this.begin('point_y');}
<point_y>(1)|(0(.\d+)?) {this.popState(); return 'point_y';}
" "*"quadrantChart"" "* return 'QUADRANT';
[A-Za-z]+ return 'ALPHA';
":" return 'COLON';
\+ return 'PLUS';
"," return 'COMMA';
"=" return 'EQUALS';
\= return 'EQUALS';
"*" return 'MULT';
\# return 'BRKT';
[\_] return 'UNDERSCORE';
"." return 'DOT';
"&" return 'AMP';
\- return 'MINUS';
[0-9]+ return 'NUM';
\s return 'SPACE';
";" return 'SEMI';
[!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION';
<<EOF>> return 'EOF';
/lex
%start start
%% /* language grammar */
start
: eol start
| SPACE start
| directive start
| QUADRANT document
;
document
: /* empty */
| document line
;
line
: statement eol
;
statement
:
| SPACE statement
| axisDetails
| quadrantDetails
| points
| title title_value { $$=$2.trim();yy.setDiagramTitle($$); }
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);}
| directive
;
points
: text point_start point_x point_y {yy.addPoint($1, $3, $4);}
;
axisDetails
: X-AXIS text AXIS-TEXT-DELIMITER text {yy.setXAxisLeftText($2); yy.setXAxisRightText($4);}
| X-AXIS text AXIS-TEXT-DELIMITER {$2.text += $3; yy.setXAxisLeftText($2);}
| X-AXIS text {yy.setXAxisLeftText($2);}
| Y-AXIS text AXIS-TEXT-DELIMITER text {yy.setYAxisBottomText($2); yy.setYAxisTopText($4);}
| Y-AXIS text AXIS-TEXT-DELIMITER {$2.text += $3; yy.setYAxisBottomText($2);}
| Y-AXIS text {yy.setYAxisBottomText($2);}
;
quadrantDetails
: QUADRANT_1 text {yy.setQuadrant1Text($2)}
| QUADRANT_2 text {yy.setQuadrant2Text($2)}
| QUADRANT_3 text {yy.setQuadrant3Text($2)}
| QUADRANT_4 text {yy.setQuadrant4Text($2)}
;
directive
: openDirective typeDirective closeDirective
| openDirective typeDirective ':' argDirective closeDirective
;
eol
: NEWLINE
| SEMI
| EOF
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'quadrantChart'); }
;
text: alphaNumToken
{ $$={text:$1, type: 'text'};}
| text textNoTagsToken
{ $$={text:$1.text+''+$2, type: $1.type};}
| STR
{ $$={text: $1, type: 'text'};}
| MD_STR
{ $$={text: $1, type: 'markdown'};}
;
alphaNum
: alphaNumToken
{$$=$1;}
| alphaNum alphaNumToken
{$$=$1+''+$2;}
;
alphaNumToken : PUNCTUATION | AMP | NUM| ALPHA | COMMA | PLUS | EQUALS | MULT | DOT | BRKT| UNDERSCORE ;
textNoTagsToken: alphaNumToken | SPACE | MINUS;
%%

View File

@ -0,0 +1,298 @@
// @ts-ignore: TODO Fix ts errors
import { parser } from './quadrant.jison';
import { Mock, vi } from 'vitest';
const parserFnConstructor = (str: string) => {
return () => {
parser.parse(str);
};
};
const mockDB: Record<string, Mock<any, any>> = {
setQuadrant1Text: vi.fn(),
setQuadrant2Text: vi.fn(),
setQuadrant3Text: vi.fn(),
setQuadrant4Text: vi.fn(),
setXAxisLeftText: vi.fn(),
setXAxisRightText: vi.fn(),
setYAxisTopText: vi.fn(),
setYAxisBottomText: vi.fn(),
setDiagramTitle: vi.fn(),
parseDirective: vi.fn(),
addPoint: vi.fn(),
};
function clearMocks() {
for (const key in mockDB) {
mockDB[key].mockRestore();
}
}
describe('Testing quadrantChart jison file', () => {
beforeEach(() => {
parser.yy = mockDB;
clearMocks();
});
it('should throw error if quadrantChart text is not there', () => {
const str = 'quadrant-1 do';
expect(parserFnConstructor(str)).toThrow();
});
it('should not throw error if only quadrantChart is there', () => {
const str = 'quadrantChart';
expect(parserFnConstructor(str)).not.toThrow();
});
it('should be able to parse directive', () => {
const str =
'%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%% \n quadrantChart';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']);
expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']);
expect(mockDB.parseDirective.mock.calls[2]).toEqual([
'{"quadrantChart": {"chartWidth": 600, "chartHeight": 600} }',
'arg_directive',
]);
expect(mockDB.parseDirective.mock.calls[3]).toEqual([
'}%%',
'close_directive',
'quadrantChart',
]);
});
it('should be able to parse xAxis text', () => {
let str = 'quadrantChart\nx-axis urgent --> not urgent';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'urgent', type: 'text' });
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({ text: 'not urgent', type: 'text' });
clearMocks();
str = 'quadrantChart\n x-AxIs Urgent --> Not Urgent \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'Urgent', type: 'text' });
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({ text: 'Not Urgent ', type: 'text' });
clearMocks();
str =
'quadrantChart\n x-AxIs "Urgent(* +=[❤" --> "Not Urgent (* +=[❤"\n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'Urgent(* +=[❤', type: 'text' });
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({
text: 'Not Urgent (* +=[❤',
type: 'text',
});
clearMocks();
str = 'quadrantChart\n x-AxIs "Urgent(* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({ text: 'Urgent(* +=[❤', type: 'text' });
expect(mockDB.setXAxisRightText).not.toHaveBeenCalled();
clearMocks();
str = 'quadrantChart\n x-AxIs "Urgent(* +=[❤" --> ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
text: 'Urgent(* +=[❤ --> ',
type: 'text',
});
expect(mockDB.setXAxisRightText).not.toHaveBeenCalled();
});
it('should be able to parse yAxis text', () => {
let str = 'quadrantChart\ny-axis urgent --> not urgent';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({ text: 'urgent', type: 'text' });
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({ text: 'not urgent', type: 'text' });
clearMocks();
str = 'quadrantChart\n y-AxIs Urgent --> Not Urgent \n';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({ text: 'Urgent', type: 'text' });
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({ text: 'Not Urgent ', type: 'text' });
clearMocks();
str =
'quadrantChart\n Y-AxIs "Urgent(* +=[❤" --> "Not Urgent (* +=[❤"\n ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({ text: 'Urgent(* +=[❤', type: 'text' });
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({
text: 'Not Urgent (* +=[❤',
type: 'text',
});
clearMocks();
str = 'quadrantChart\n y-AxIs "Urgent(* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({ text: 'Urgent(* +=[❤', type: 'text' });
expect(mockDB.setYAxisTopText).not.toHaveBeenCalled();
clearMocks();
str = 'quadrantChart\n y-AxIs "Urgent(* +=[❤" --> ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({
text: 'Urgent(* +=[❤ --> ',
type: 'text',
});
expect(mockDB.setYAxisTopText).not.toHaveBeenCalled();
});
it('should be able to parse quadrant1 text', () => {
let str = 'quadrantChart\nquadrant-1 Plan';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Plan', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-1 Plan ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Plan ', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-1 "Plan(* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Plan(* +=[❤', type: 'text' });
});
it('should be able to parse quadrant2 text', () => {
let str = 'quadrantChart\nquadrant-2 do';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'do', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-2 Do ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'Do ', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-2 "Do(* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'Do(* +=[❤', type: 'text' });
});
it('should be able to parse quadrant3 text', () => {
let str = 'quadrantChart\nquadrant-3 deligate';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'deligate', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-3 Deligate ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'Deligate ', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-3 "Deligate(* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'Deligate(* +=[❤', type: 'text' });
});
it('should be able to parse quadrant4 text', () => {
let str = 'quadrantChart\nquadrant-4 delete';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'delete', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-4 Delete ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'Delete ', type: 'text' });
clearMocks();
str = 'QuadRantChart \n QuaDrant-4 "Delete(* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'Delete(* +=[❤', type: 'text' });
});
it('should be able to parse title', () => {
let str = 'quadrantChart\ntitle this is title';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('this is title');
clearMocks();
str = 'QuadRantChart \n TiTle this Is title ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('this Is title');
clearMocks();
str = 'QuadRantChart \n title "this is title (* +=[❤"';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.setDiagramTitle).toHaveBeenCalledWith('"this is title (* +=[❤"');
});
it('should be able to parse points', () => {
let str = 'quadrantChart\npoint1: [0.1, 0.4]';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'point1', type: 'text' }, '0.1', '0.4');
clearMocks();
str = 'QuadRantChart \n Point1 : [0.1, 0.4] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'Point1', type: 'text' }, '0.1', '0.4');
clearMocks();
str = 'QuadRantChart \n "Point1 : (* +=[❤": [1, 0] ';
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Point1 : (* +=[❤', type: 'text' },
'1',
'0'
);
clearMocks();
str = 'QuadRantChart \n Point1 : [1.2, 0.4] ';
expect(parserFnConstructor(str)).toThrow();
});
it('should be able to parse the whole chart', () => {
const str = `%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%%
quadrantChart
title Analytics and Business Intelligence Platforms
x-axis "Completeness of Vision ❤" --> "x-axis-2"
y-axis Ability to Execute --> "y-axis-2"
quadrant-1 Leaders
quadrant-2 Challengers
quadrant-3 Niche
quadrant-4 Visionaries
Microsoft: [0.75, 0.75]
Salesforce: [0.55, 0.60]
IBM: [0.51, 0.40]
Incorta: [0.20, 0.30]`;
expect(parserFnConstructor(str)).not.toThrow();
expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']);
expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']);
expect(mockDB.parseDirective.mock.calls[2]).toEqual([
'{"quadrantChart": {"chartWidth": 600, "chartHeight": 600} }',
'arg_directive',
]);
expect(mockDB.parseDirective.mock.calls[3]).toEqual([
'}%%',
'close_directive',
'quadrantChart',
]);
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
text: 'Completeness of Vision ❤',
type: 'text',
});
expect(mockDB.setXAxisRightText).toHaveBeenCalledWith({ text: 'x-axis-2', type: 'text' });
expect(mockDB.setYAxisTopText).toHaveBeenCalledWith({ text: 'y-axis-2', type: 'text' });
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({
text: 'Ability to Execute',
type: 'text',
});
expect(mockDB.setQuadrant1Text).toHaveBeenCalledWith({ text: 'Leaders', type: 'text' });
expect(mockDB.setQuadrant2Text).toHaveBeenCalledWith({ text: 'Challengers', type: 'text' });
expect(mockDB.setQuadrant3Text).toHaveBeenCalledWith({ text: 'Niche', type: 'text' });
expect(mockDB.setQuadrant4Text).toHaveBeenCalledWith({ text: 'Visionaries', type: 'text' });
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Microsoft', type: 'text' },
'0.75',
'0.75'
);
expect(mockDB.addPoint).toHaveBeenCalledWith(
{ text: 'Salesforce', type: 'text' },
'0.55',
'0.60'
);
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'IBM', type: 'text' }, '0.51', '0.40');
expect(mockDB.addPoint).toHaveBeenCalledWith({ text: 'Incorta', type: 'text' }, '0.20', '0.30');
});
});

View File

@ -0,0 +1,601 @@
// @ts-ignore: TODO Fix ts errors
import { scaleLinear } from 'd3';
import { log } from '../../logger.js';
import { QuadrantChartConfig } from '../../config.type.js';
import defaultConfig from '../../defaultConfig.js';
import { getThemeVariables } from '../../themes/theme-default.js';
const defaultThemeVariables = getThemeVariables();
export type TextVerticalPos = 'left' | 'center' | 'right';
export type TextHorizontalPos = 'top' | 'middle' | 'bottom';
export interface Point {
x: number;
y: number;
}
export interface QuadrantPointInputType extends Point {
text: string;
}
export interface QuadrantTextType extends Point {
text: string;
fill: string;
verticalPos: TextVerticalPos;
horizontalPos: TextHorizontalPos;
fontSize: number;
rotation: number;
}
export interface QuadrantPointType extends Point {
fill: string;
radius: number;
text: QuadrantTextType;
}
export interface QuadrantQuadrantsType extends Point {
text: QuadrantTextType;
width: number;
height: number;
fill: string;
}
export interface QuadrantLineType {
strokeWidth: number;
strokeFill: string;
x1: number;
y1: number;
x2: number;
y2: number;
}
export interface QuadrantBuildType {
points: QuadrantPointType[];
quadrants: QuadrantQuadrantsType[];
axisLabels: QuadrantTextType[];
title?: QuadrantTextType;
borderLines?: QuadrantLineType[];
}
export interface quadrantBuilderData {
titleText: string;
quadrant1Text: string;
quadrant2Text: string;
quadrant3Text: string;
quadrant4Text: string;
xAxisLeftText: string;
xAxisRightText: string;
yAxisBottomText: string;
yAxisTopText: string;
points: QuadrantPointInputType[];
}
export interface QuadrantBuilderConfig extends QuadrantChartConfig {
showXAxis: boolean;
showYAxis: boolean;
showTitle: boolean;
}
export interface QuadrantBuilderThemeConfig {
quadrantTitleFill: string;
quadrant1Fill: string;
quadrant2Fill: string;
quadrant3Fill: string;
quadrant4Fill: string;
quadrant1TextFill: string;
quadrant2TextFill: string;
quadrant3TextFill: string;
quadrant4TextFill: string;
quadrantPointFill: string;
quadrantPointTextFill: string;
quadrantXAxisTextFill: string;
quadrantYAxisTextFill: string;
quadrantInternalBorderStrokeFill: string;
quadrantExternalBorderStrokeFill: string;
}
interface CalculateSpaceData {
xAxisSpace: {
top: number;
bottom: number;
};
yAxisSpace: {
left: number;
right: number;
};
titleSpace: {
top: number;
};
quadrantSpace: {
quadrantLeft: number;
quadrantTop: number;
quadrantWidth: number;
quadrantHalfWidth: number;
quadrantHeight: number;
quadrantHalfHeight: number;
};
}
export class QuadrantBuilder {
private config: QuadrantBuilderConfig;
private themeConfig: QuadrantBuilderThemeConfig;
private data: quadrantBuilderData;
constructor() {
this.config = this.getDefaultConfig();
this.themeConfig = this.getDefaultThemeConfig();
this.data = this.getDefaultData();
}
getDefaultData(): quadrantBuilderData {
return {
titleText: '',
quadrant1Text: '',
quadrant2Text: '',
quadrant3Text: '',
quadrant4Text: '',
xAxisLeftText: '',
xAxisRightText: '',
yAxisBottomText: '',
yAxisTopText: '',
points: [],
};
}
getDefaultConfig(): QuadrantBuilderConfig {
return {
showXAxis: true,
showYAxis: true,
showTitle: true,
chartHeight: defaultConfig.quadrantChart?.chartWidth || 500,
chartWidth: defaultConfig.quadrantChart?.chartHeight || 500,
titlePadding: defaultConfig.quadrantChart?.titlePadding || 10,
titleFontSize: defaultConfig.quadrantChart?.titleFontSize || 20,
quadrantPadding: defaultConfig.quadrantChart?.quadrantPadding || 5,
xAxisLabelPadding: defaultConfig.quadrantChart?.xAxisLabelPadding || 5,
yAxisLabelPadding: defaultConfig.quadrantChart?.yAxisLabelPadding || 5,
xAxisLabelFontSize: defaultConfig.quadrantChart?.xAxisLabelFontSize || 16,
yAxisLabelFontSize: defaultConfig.quadrantChart?.yAxisLabelFontSize || 16,
quadrantLabelFontSize: defaultConfig.quadrantChart?.quadrantLabelFontSize || 16,
quadrantTextTopPadding: defaultConfig.quadrantChart?.quadrantTextTopPadding || 5,
pointTextPadding: defaultConfig.quadrantChart?.pointTextPadding || 5,
pointLabelFontSize: defaultConfig.quadrantChart?.pointLabelFontSize || 12,
pointRadius: defaultConfig.quadrantChart?.pointRadius || 5,
xAxisPosition: defaultConfig.quadrantChart?.xAxisPosition || 'top',
yAxisPosition: defaultConfig.quadrantChart?.yAxisPosition || 'left',
quadrantInternalBorderStrokeWidth:
defaultConfig.quadrantChart?.quadrantInternalBorderStrokeWidth || 1,
quadrantExternalBorderStrokeWidth:
defaultConfig.quadrantChart?.quadrantExternalBorderStrokeWidth || 2,
};
}
getDefaultThemeConfig(): QuadrantBuilderThemeConfig {
return {
quadrant1Fill: defaultThemeVariables.quadrant1Fill,
quadrant2Fill: defaultThemeVariables.quadrant2Fill,
quadrant3Fill: defaultThemeVariables.quadrant3Fill,
quadrant4Fill: defaultThemeVariables.quadrant4Fill,
quadrant1TextFill: defaultThemeVariables.quadrant1TextFill,
quadrant2TextFill: defaultThemeVariables.quadrant2TextFill,
quadrant3TextFill: defaultThemeVariables.quadrant3TextFill,
quadrant4TextFill: defaultThemeVariables.quadrant4TextFill,
quadrantPointFill: defaultThemeVariables.quadrantPointFill,
quadrantPointTextFill: defaultThemeVariables.quadrantPointTextFill,
quadrantXAxisTextFill: defaultThemeVariables.quadrantXAxisTextFill,
quadrantYAxisTextFill: defaultThemeVariables.quadrantYAxisTextFill,
quadrantTitleFill: defaultThemeVariables.quadrantTitleFill,
quadrantInternalBorderStrokeFill: defaultThemeVariables.quadrantInternalBorderStrokeFill,
quadrantExternalBorderStrokeFill: defaultThemeVariables.quadrantExternalBorderStrokeFill,
};
}
clear() {
this.config = this.getDefaultConfig();
this.themeConfig = this.getDefaultThemeConfig();
this.data = this.getDefaultData();
log.info('clear called');
}
setData(data: Partial<quadrantBuilderData>) {
this.data = { ...this.data, ...data };
}
addPoints(points: QuadrantPointInputType[]) {
this.data.points = [...points, ...this.data.points];
}
setConfig(config: Partial<QuadrantBuilderConfig>) {
log.trace('setConfig called with: ', config);
this.config = { ...this.config, ...config };
}
setThemeConfig(themeConfig: Partial<QuadrantBuilderThemeConfig>) {
log.trace('setThemeConfig called with: ', themeConfig);
this.themeConfig = { ...this.themeConfig, ...themeConfig };
}
calculateSpace(
xAxisPosition: typeof this.config.xAxisPosition,
showXAxis: boolean,
showYAxis: boolean,
showTitle: boolean
): CalculateSpaceData {
const xAxisSpaceCalculation =
this.config.xAxisLabelPadding * 2 + this.config.xAxisLabelFontSize;
const xAxisSpace = {
top: xAxisPosition === 'top' && showXAxis ? xAxisSpaceCalculation : 0,
bottom: xAxisPosition === 'bottom' && showXAxis ? xAxisSpaceCalculation : 0,
};
const yAxisSpaceCalculation =
this.config.yAxisLabelPadding * 2 + this.config.yAxisLabelFontSize;
const yAxisSpace = {
left: this.config.yAxisPosition === 'left' && showYAxis ? yAxisSpaceCalculation : 0,
right: this.config.yAxisPosition === 'right' && showYAxis ? yAxisSpaceCalculation : 0,
};
const titleSpaceCalculation = this.config.titleFontSize + this.config.titlePadding * 2;
const titleSpace = {
top: showTitle ? titleSpaceCalculation : 0,
};
const quadrantLeft = this.config.quadrantPadding + yAxisSpace.left;
const quadrantTop = this.config.quadrantPadding + xAxisSpace.top + titleSpace.top;
const quadrantWidth =
this.config.chartWidth - this.config.quadrantPadding * 2 - yAxisSpace.left - yAxisSpace.right;
const quadrantHeight =
this.config.chartHeight -
this.config.quadrantPadding * 2 -
xAxisSpace.top -
xAxisSpace.bottom -
titleSpace.top;
const quadrantHalfWidth = quadrantWidth / 2;
const quadrantHalfHeight = quadrantHeight / 2;
const quadrantSpace = {
quadrantLeft,
quadrantTop,
quadrantWidth,
quadrantHalfWidth,
quadrantHeight,
quadrantHalfHeight,
};
return {
xAxisSpace,
yAxisSpace,
titleSpace,
quadrantSpace,
};
}
getAxisLabels(
xAxisPosition: typeof this.config.xAxisPosition,
showXAxis: boolean,
showYAxis: boolean,
spaceData: CalculateSpaceData
): QuadrantTextType[] {
const { quadrantSpace, titleSpace } = spaceData;
const {
quadrantHalfHeight,
quadrantHeight,
quadrantLeft,
quadrantHalfWidth,
quadrantTop,
quadrantWidth,
} = quadrantSpace;
const drawAxisLabelInMiddle = this.data.points.length === 0;
const axisLabels: QuadrantTextType[] = [];
if (this.data.xAxisLeftText && showXAxis) {
axisLabels.push({
text: this.data.xAxisLeftText,
fill: this.themeConfig.quadrantXAxisTextFill,
x: quadrantLeft + (drawAxisLabelInMiddle ? quadrantHalfWidth / 2 : 0),
y:
xAxisPosition === 'top'
? this.config.xAxisLabelPadding + titleSpace.top
: this.config.xAxisLabelPadding +
quadrantTop +
quadrantHeight +
this.config.quadrantPadding,
fontSize: this.config.xAxisLabelFontSize,
verticalPos: drawAxisLabelInMiddle ? 'center' : 'left',
horizontalPos: 'top',
rotation: 0,
});
}
if (this.data.xAxisRightText && showXAxis) {
axisLabels.push({
text: this.data.xAxisRightText,
fill: this.themeConfig.quadrantXAxisTextFill,
x: quadrantLeft + quadrantHalfWidth + (drawAxisLabelInMiddle ? quadrantHalfWidth / 2 : 0),
y:
xAxisPosition === 'top'
? this.config.xAxisLabelPadding + titleSpace.top
: this.config.xAxisLabelPadding +
quadrantTop +
quadrantHeight +
this.config.quadrantPadding,
fontSize: this.config.xAxisLabelFontSize,
verticalPos: drawAxisLabelInMiddle ? 'center' : 'left',
horizontalPos: 'top',
rotation: 0,
});
}
if (this.data.yAxisBottomText && showYAxis) {
axisLabels.push({
text: this.data.yAxisBottomText,
fill: this.themeConfig.quadrantYAxisTextFill,
x:
this.config.yAxisPosition === 'left'
? this.config.yAxisLabelPadding
: this.config.yAxisLabelPadding +
quadrantLeft +
quadrantWidth +
this.config.quadrantPadding,
y: quadrantTop + quadrantHeight - (drawAxisLabelInMiddle ? quadrantHalfHeight / 2 : 0),
fontSize: this.config.yAxisLabelFontSize,
verticalPos: drawAxisLabelInMiddle ? 'center' : 'left',
horizontalPos: 'top',
rotation: -90,
});
}
if (this.data.yAxisTopText && showYAxis) {
axisLabels.push({
text: this.data.yAxisTopText,
fill: this.themeConfig.quadrantYAxisTextFill,
x:
this.config.yAxisPosition === 'left'
? this.config.yAxisLabelPadding
: this.config.yAxisLabelPadding +
quadrantLeft +
quadrantWidth +
this.config.quadrantPadding,
y: quadrantTop + quadrantHalfHeight - (drawAxisLabelInMiddle ? quadrantHalfHeight / 2 : 0),
fontSize: this.config.yAxisLabelFontSize,
verticalPos: drawAxisLabelInMiddle ? 'center' : 'left',
horizontalPos: 'top',
rotation: -90,
});
}
return axisLabels;
}
getQuadrants(spaceData: CalculateSpaceData): QuadrantQuadrantsType[] {
const { quadrantSpace } = spaceData;
const { quadrantHalfHeight, quadrantLeft, quadrantHalfWidth, quadrantTop } = quadrantSpace;
const quadrants: QuadrantQuadrantsType[] = [
{
text: {
text: this.data.quadrant1Text,
fill: this.themeConfig.quadrant1TextFill,
x: 0,
y: 0,
fontSize: this.config.quadrantLabelFontSize,
verticalPos: 'center',
horizontalPos: 'middle',
rotation: 0,
},
x: quadrantLeft + quadrantHalfWidth,
y: quadrantTop,
width: quadrantHalfWidth,
height: quadrantHalfHeight,
fill: this.themeConfig.quadrant1Fill,
},
{
text: {
text: this.data.quadrant2Text,
fill: this.themeConfig.quadrant2TextFill,
x: 0,
y: 0,
fontSize: this.config.quadrantLabelFontSize,
verticalPos: 'center',
horizontalPos: 'middle',
rotation: 0,
},
x: quadrantLeft,
y: quadrantTop,
width: quadrantHalfWidth,
height: quadrantHalfHeight,
fill: this.themeConfig.quadrant2Fill,
},
{
text: {
text: this.data.quadrant3Text,
fill: this.themeConfig.quadrant3TextFill,
x: 0,
y: 0,
fontSize: this.config.quadrantLabelFontSize,
verticalPos: 'center',
horizontalPos: 'middle',
rotation: 0,
},
x: quadrantLeft,
y: quadrantTop + quadrantHalfHeight,
width: quadrantHalfWidth,
height: quadrantHalfHeight,
fill: this.themeConfig.quadrant3Fill,
},
{
text: {
text: this.data.quadrant4Text,
fill: this.themeConfig.quadrant4TextFill,
x: 0,
y: 0,
fontSize: this.config.quadrantLabelFontSize,
verticalPos: 'center',
horizontalPos: 'middle',
rotation: 0,
},
x: quadrantLeft + quadrantHalfWidth,
y: quadrantTop + quadrantHalfHeight,
width: quadrantHalfWidth,
height: quadrantHalfHeight,
fill: this.themeConfig.quadrant4Fill,
},
];
for (const quadrant of quadrants) {
quadrant.text.x = quadrant.x + quadrant.width / 2;
// place the text in the center of the box
if (this.data.points.length === 0) {
quadrant.text.y = quadrant.y + quadrant.height / 2;
quadrant.text.horizontalPos = 'middle';
// place the text top of the quadrant square
} else {
quadrant.text.y = quadrant.y + this.config.quadrantTextTopPadding;
quadrant.text.horizontalPos = 'top';
}
}
return quadrants;
}
getQuadrantPoints(spaceData: CalculateSpaceData): QuadrantPointType[] {
const { quadrantSpace } = spaceData;
const { quadrantHeight, quadrantLeft, quadrantTop, quadrantWidth } = quadrantSpace;
const xAxis = scaleLinear()
.domain([0, 1])
.range([quadrantLeft, quadrantWidth + quadrantLeft]);
const yAxis = scaleLinear()
.domain([0, 1])
.range([quadrantHeight + quadrantTop, quadrantTop]);
const points: QuadrantPointType[] = this.data.points.map((point) => {
const props: QuadrantPointType = {
x: xAxis(point.x),
y: yAxis(point.y),
fill: this.themeConfig.quadrantPointFill,
radius: this.config.pointRadius,
text: {
text: point.text,
fill: this.themeConfig.quadrantPointTextFill,
x: xAxis(point.x),
y: yAxis(point.y) + this.config.pointTextPadding,
verticalPos: 'center',
horizontalPos: 'top',
fontSize: this.config.pointLabelFontSize,
rotation: 0,
},
};
return props;
});
return points;
}
getBorders(spaceData: CalculateSpaceData): QuadrantLineType[] {
const halfExternalBorderWidth = this.config.quadrantExternalBorderStrokeWidth / 2;
const { quadrantSpace } = spaceData;
const {
quadrantHalfHeight,
quadrantHeight,
quadrantLeft,
quadrantHalfWidth,
quadrantTop,
quadrantWidth,
} = quadrantSpace;
const borderLines: QuadrantLineType[] = [
// top border
{
strokeFill: this.themeConfig.quadrantExternalBorderStrokeFill,
strokeWidth: this.config.quadrantExternalBorderStrokeWidth,
x1: quadrantLeft - halfExternalBorderWidth,
y1: quadrantTop,
x2: quadrantLeft + quadrantWidth + halfExternalBorderWidth,
y2: quadrantTop,
},
// right border
{
strokeFill: this.themeConfig.quadrantExternalBorderStrokeFill,
strokeWidth: this.config.quadrantExternalBorderStrokeWidth,
x1: quadrantLeft + quadrantWidth,
y1: quadrantTop + halfExternalBorderWidth,
x2: quadrantLeft + quadrantWidth,
y2: quadrantTop + quadrantHeight - halfExternalBorderWidth,
},
// bottom border
{
strokeFill: this.themeConfig.quadrantExternalBorderStrokeFill,
strokeWidth: this.config.quadrantExternalBorderStrokeWidth,
x1: quadrantLeft - halfExternalBorderWidth,
y1: quadrantTop + quadrantHeight,
x2: quadrantLeft + quadrantWidth + halfExternalBorderWidth,
y2: quadrantTop + quadrantHeight,
},
// left border
{
strokeFill: this.themeConfig.quadrantExternalBorderStrokeFill,
strokeWidth: this.config.quadrantExternalBorderStrokeWidth,
x1: quadrantLeft,
y1: quadrantTop + halfExternalBorderWidth,
x2: quadrantLeft,
y2: quadrantTop + quadrantHeight - halfExternalBorderWidth,
},
// vertical inner border
{
strokeFill: this.themeConfig.quadrantInternalBorderStrokeFill,
strokeWidth: this.config.quadrantInternalBorderStrokeWidth,
x1: quadrantLeft + quadrantHalfWidth,
y1: quadrantTop + halfExternalBorderWidth,
x2: quadrantLeft + quadrantHalfWidth,
y2: quadrantTop + quadrantHeight - halfExternalBorderWidth,
},
// horizontal inner border
{
strokeFill: this.themeConfig.quadrantInternalBorderStrokeFill,
strokeWidth: this.config.quadrantInternalBorderStrokeWidth,
x1: quadrantLeft + halfExternalBorderWidth,
y1: quadrantTop + quadrantHalfHeight,
x2: quadrantLeft + quadrantWidth - halfExternalBorderWidth,
y2: quadrantTop + quadrantHalfHeight,
},
];
return borderLines;
}
getTitle(showTitle: boolean): QuadrantTextType | undefined {
if (showTitle) {
return {
text: this.data.titleText,
fill: this.themeConfig.quadrantTitleFill,
fontSize: this.config.titleFontSize,
horizontalPos: 'top',
verticalPos: 'center',
rotation: 0,
y: this.config.titlePadding,
x: this.config.chartWidth / 2,
};
}
return;
}
build(): QuadrantBuildType {
const showXAxis =
this.config.showXAxis && !!(this.data.xAxisLeftText || this.data.xAxisRightText);
const showYAxis =
this.config.showYAxis && !!(this.data.yAxisTopText || this.data.yAxisBottomText);
const showTitle = this.config.showTitle && !!this.data.titleText;
const xAxisPosition = this.data.points.length > 0 ? 'bottom' : this.config.xAxisPosition;
const calculatedSpace = this.calculateSpace(xAxisPosition, showXAxis, showYAxis, showTitle);
return {
points: this.getQuadrantPoints(calculatedSpace),
quadrants: this.getQuadrants(calculatedSpace),
axisLabels: this.getAxisLabels(xAxisPosition, showXAxis, showYAxis, calculatedSpace),
borderLines: this.getBorders(calculatedSpace),
title: this.getTitle(showTitle),
};
}
}

View File

@ -0,0 +1,128 @@
import { log } from '../../logger.js';
import mermaidAPI from '../../mermaidAPI.js';
import * as configApi from '../../config.js';
import { sanitizeText } from '../common/common.js';
import {
setAccTitle,
getAccTitle,
setDiagramTitle,
getDiagramTitle,
getAccDescription,
setAccDescription,
clear as commonClear,
} from '../../commonDb.js';
import { QuadrantBuilder } from './quadrantBuilder.js';
const config = configApi.getConfig();
function textSanitizer(text: string) {
return sanitizeText(text.trim(), config);
}
type LexTextObj = { text: string; type: 'text' | 'markdown' };
const quadrantBuilder = new QuadrantBuilder();
function setQuadrant1Text(textObj: LexTextObj) {
quadrantBuilder.setData({ quadrant1Text: textSanitizer(textObj.text) });
}
function setQuadrant2Text(textObj: LexTextObj) {
quadrantBuilder.setData({ quadrant2Text: textSanitizer(textObj.text) });
}
function setQuadrant3Text(textObj: LexTextObj) {
quadrantBuilder.setData({ quadrant3Text: textSanitizer(textObj.text) });
}
function setQuadrant4Text(textObj: LexTextObj) {
quadrantBuilder.setData({ quadrant4Text: textSanitizer(textObj.text) });
}
function setXAxisLeftText(textObj: LexTextObj) {
quadrantBuilder.setData({ xAxisLeftText: textSanitizer(textObj.text) });
}
function setXAxisRightText(textObj: LexTextObj) {
quadrantBuilder.setData({ xAxisRightText: textSanitizer(textObj.text) });
}
function setYAxisTopText(textObj: LexTextObj) {
quadrantBuilder.setData({ yAxisTopText: textSanitizer(textObj.text) });
}
function setYAxisBottomText(textObj: LexTextObj) {
quadrantBuilder.setData({ yAxisBottomText: textSanitizer(textObj.text) });
}
function addPoint(textObj: LexTextObj, x: number, y: number) {
quadrantBuilder.addPoints([{ x, y, text: textSanitizer(textObj.text) }]);
}
function setWidth(width: number) {
quadrantBuilder.setConfig({ chartWidth: width });
}
function setHeight(height: number) {
quadrantBuilder.setConfig({ chartHeight: height });
}
function getQuadrantData() {
const config = configApi.getConfig();
const { themeVariables, quadrantChart: quadrantChartConfig } = config;
if (quadrantChartConfig) {
quadrantBuilder.setConfig(quadrantChartConfig);
}
quadrantBuilder.setThemeConfig({
quadrant1Fill: themeVariables.quadrant1Fill,
quadrant2Fill: themeVariables.quadrant2Fill,
quadrant3Fill: themeVariables.quadrant3Fill,
quadrant4Fill: themeVariables.quadrant4Fill,
quadrant1TextFill: themeVariables.quadrant1TextFill,
quadrant2TextFill: themeVariables.quadrant2TextFill,
quadrant3TextFill: themeVariables.quadrant3TextFill,
quadrant4TextFill: themeVariables.quadrant4TextFill,
quadrantPointFill: themeVariables.quadrantPointFill,
quadrantPointTextFill: themeVariables.quadrantPointTextFill,
quadrantXAxisTextFill: themeVariables.quadrantXAxisTextFill,
quadrantYAxisTextFill: themeVariables.quadrantYAxisTextFill,
quadrantExternalBorderStrokeFill: themeVariables.quadrantExternalBorderStrokeFill,
quadrantInternalBorderStrokeFill: themeVariables.quadrantInternalBorderStrokeFill,
quadrantTitleFill: themeVariables.quadrantTitleFill,
});
quadrantBuilder.setData({ titleText: getDiagramTitle() });
return quadrantBuilder.build();
}
export const parseDirective = function (statement: string, context: string, type: string) {
// @ts-ignore: TODO Fix ts errors
mermaidAPI.parseDirective(this, statement, context, type);
};
const clear = function () {
quadrantBuilder.clear();
commonClear();
};
export default {
setWidth,
setHeight,
setQuadrant1Text,
setQuadrant2Text,
setQuadrant3Text,
setQuadrant4Text,
setXAxisLeftText,
setXAxisRightText,
setYAxisTopText,
setYAxisBottomText,
addPoint,
getQuadrantData,
parseDirective,
clear,
setAccTitle,
getAccTitle,
setDiagramTitle,
getDiagramTitle,
getAccDescription,
setAccDescription,
};

View File

@ -0,0 +1,20 @@
import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types.js';
const id = 'quadrantChart';
const detector: DiagramDetector = (txt) => {
return txt.match(/^\s*quadrantChart/) !== null;
};
const loader = async () => {
const { diagram } = await import('./quadrantDiagram.js');
return { id, diagram };
};
const plugin: ExternalDiagramDefinition = {
id,
detector,
loader,
};
export default plugin;

View File

@ -0,0 +1,12 @@
import { DiagramDefinition } from '../../diagram-api/types.js';
// @ts-ignore: TODO Fix ts errors
import parser from './parser/quadrant.jison';
import db from './quadrantDb.js';
import renderer from './quadrantRenderer.js';
export const diagram: DiagramDefinition = {
parser,
db,
renderer,
styles: () => '',
};

View File

@ -0,0 +1,173 @@
// @ts-ignore: TODO Fix ts errors
import { select } from 'd3';
import * as configApi from '../../config.js';
import { log } from '../../logger.js';
import { configureSvgSize } from '../../setupGraphViewbox.js';
import { Diagram } from '../../Diagram.js';
import {
QuadrantBuildType,
QuadrantLineType,
QuadrantPointType,
QuadrantQuadrantsType,
QuadrantTextType,
TextHorizontalPos,
TextVerticalPos,
} from './quadrantBuilder.js';
export const draw = (txt: string, id: string, _version: string, diagObj: Diagram) => {
function getDominantBaseLine(horizontalPos: TextHorizontalPos) {
return horizontalPos === 'top' ? 'hanging' : 'middle';
}
function getTextAnchor(verticalPos: TextVerticalPos) {
return verticalPos === 'left' ? 'start' : 'middle';
}
function getTransformation(data: { x: number; y: number; rotation: number }) {
return `translate(${data.x}, ${data.y}) rotate(${data.rotation || 0})`;
}
const conf = configApi.getConfig();
log.debug('Rendering quadrant chart\n' + txt);
const securityLevel = conf.securityLevel;
// Handle root and Document for when rendering in sandbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
const svg = root.select(`[id="${id}"]`);
const group = svg.append('g').attr('class', 'main');
const width = conf.quadrantChart?.chartWidth || 500;
const height = conf.quadrantChart?.chartHeight || 500;
configureSvgSize(svg, height, width, conf.quadrantChart?.useMaxWidth || true);
svg.attr('viewBox', '0 0 ' + width + ' ' + height);
// @ts-ignore: TODO Fix ts errors
diagObj.db.setHeight(height);
// @ts-ignore: TODO Fix ts errors
diagObj.db.setWidth(width);
// @ts-ignore: TODO Fix ts errors
const quadrantData: QuadrantBuildType = diagObj.db.getQuadrantData();
const quadrantsGroup = group.append('g').attr('class', 'quadrants');
const borderGroup = group.append('g').attr('class', 'border');
const dataPointGroup = group.append('g').attr('class', 'data-points');
const labelGroup = group.append('g').attr('class', 'labels');
const titleGroup = group.append('g').attr('class', 'title');
if (quadrantData.title) {
titleGroup
.append('text')
.attr('x', 0)
.attr('y', 0)
.attr('fill', quadrantData.title.fill)
.attr('font-size', quadrantData.title.fontSize)
.attr('dominant-baseline', getDominantBaseLine(quadrantData.title.horizontalPos))
.attr('text-anchor', getTextAnchor(quadrantData.title.verticalPos))
.attr('transform', getTransformation(quadrantData.title))
.text(quadrantData.title.text);
}
if (quadrantData.borderLines) {
borderGroup
.selectAll('line')
.data(quadrantData.borderLines)
.enter()
.append('line')
.attr('x1', (data: QuadrantLineType) => data.x1)
.attr('y1', (data: QuadrantLineType) => data.y1)
.attr('x2', (data: QuadrantLineType) => data.x2)
.attr('y2', (data: QuadrantLineType) => data.y2)
.style('stroke', (data: QuadrantLineType) => data.strokeFill)
.style('stroke-width', (data: QuadrantLineType) => data.strokeWidth);
}
const quadrants = quadrantsGroup
.selectAll('g.quadrant')
.data(quadrantData.quadrants)
.enter()
.append('g')
.attr('class', 'quadrant');
quadrants
.append('rect')
.attr('x', (data: QuadrantQuadrantsType) => data.x)
.attr('y', (data: QuadrantQuadrantsType) => data.y)
.attr('width', (data: QuadrantQuadrantsType) => data.width)
.attr('height', (data: QuadrantQuadrantsType) => data.height)
.attr('fill', (data: QuadrantQuadrantsType) => data.fill);
quadrants
.append('text')
.attr('x', 0)
.attr('y', 0)
.attr('fill', (data: QuadrantQuadrantsType) => data.text.fill)
.attr('font-size', (data: QuadrantQuadrantsType) => data.text.fontSize)
.attr('dominant-baseline', (data: QuadrantQuadrantsType) =>
getDominantBaseLine(data.text.horizontalPos)
)
.attr('text-anchor', (data: QuadrantQuadrantsType) => getTextAnchor(data.text.verticalPos))
.attr('transform', (data: QuadrantQuadrantsType) => getTransformation(data.text))
.text((data: QuadrantQuadrantsType) => data.text.text);
const labels = labelGroup
.selectAll('g.label')
.data(quadrantData.axisLabels)
.enter()
.append('g')
.attr('class', 'label');
labels
.append('text')
.attr('x', 0)
.attr('y', 0)
.text((data: QuadrantTextType) => data.text)
.attr('fill', (data: QuadrantTextType) => data.fill)
.attr('font-size', (data: QuadrantTextType) => data.fontSize)
.attr('dominant-baseline', (data: QuadrantTextType) => getDominantBaseLine(data.horizontalPos))
.attr('text-anchor', (data: QuadrantTextType) => getTextAnchor(data.verticalPos))
.attr('transform', (data: QuadrantTextType) => getTransformation(data));
const dataPoints = dataPointGroup
.selectAll('g.data-point')
.data(quadrantData.points)
.enter()
.append('g')
.attr('class', 'data-point');
dataPoints
.append('circle')
.attr('cx', (data: QuadrantPointType) => data.x)
.attr('cy', (data: QuadrantPointType) => data.y)
.attr('r', (data: QuadrantPointType) => data.radius)
.attr('fill', (data: QuadrantPointType) => data.fill);
dataPoints
.append('text')
.attr('x', 0)
.attr('y', 0)
.text((data: QuadrantPointType) => data.text.text)
.attr('fill', (data: QuadrantPointType) => data.text.fill)
.attr('font-size', (data: QuadrantPointType) => data.text.fontSize)
.attr('dominant-baseline', (data: QuadrantPointType) =>
getDominantBaseLine(data.text.horizontalPos)
)
.attr('text-anchor', (data: QuadrantPointType) => getTextAnchor(data.text.verticalPos))
.attr('transform', (data: QuadrantPointType) => getTransformation(data.text));
};
export default {
draw,
};

View File

@ -115,6 +115,7 @@ function sidebarSyntax() {
{ text: 'User Journey', link: '/syntax/userJourney' },
{ text: 'Gantt', link: '/syntax/gantt' },
{ text: 'Pie Chart', link: '/syntax/pie' },
{ text: 'Quadrant Chart', link: '/syntax/quadrantChart' },
{ text: 'Requirement Diagram', link: '/syntax/requirementDiagram' },
{ text: 'Gitgraph (Git) Diagram 🔥', link: '/syntax/gitgraph' },
{ text: 'C4C Diagram (Context) Diagram 🦺⚠️', link: '/syntax/c4c' },

View File

@ -98,3 +98,22 @@ journey
Go downstairs: 5: Me
Sit down: 5: Me
```
### [Quadrant Chart](../syntax/quadrantChart.md)
```mermaid-example
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```

View File

@ -0,0 +1,142 @@
# Quadrant Chart
> A quadrant chart is a visual representation of data that is divided into four quadrants. It is used to plot data points on a two-dimensional grid, with one variable represented on the x-axis and another variable represented on the y-axis. The quadrants are determined by dividing the chart into four equal parts based on a set of criteria that is specific to the data being analyzed. Quadrant charts are often used to identify patterns and trends in data, and to prioritize actions based on the position of data points within the chart. They are commonly used in business, marketing, and risk management, among other fields.
## Example
```mermaid-example
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```
## Syntax
```note
In place of `<text>` you can use text like `this is a sample text` or inside **double quotes** like `"This type of text may contain unicode like ❤"`.
```
```note
If there is no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
If there are points **x-axis** labels will rendered from left of the respective quadrant also they will be displayed in bottom of the chart, and **y-axis** lables will be rendered in bottom of the respective quadrant, the quadrant text will render at top of the respective quadrant.
```
```note
For points x and y value min value is 0 and max value is 1.
```
### Title
The title is a short description of the chart and it will always render on top of the chart.
#### Example
```
quadrantChart
title This is a sample example
```
### x-axis
The x-axis determine what text would be displayed in the x-axis. In x-axis there is two part **left** and **right** you can pass **both** or you can pass only **left**. The statement should start with `x-axis` then the `left axis text` followed by the delimiter `-->` then `right axis text`.
#### Example
1. `x-axis <text> --> <text>` both the left and right axis text will be rendered.
2. `x-axis <text>` only the left axis text will be rendered.
### y-axis
The y-axis determine what text would be displayed in the y-axis. In y-axis there is two part **top** and **bottom** you can pass **both** or you can pass only **bottom**. The statement should start with `y-axis` then the `bottom axis text` followed by the delimiter `-->` then `top axis text`.
#### Example
1. `y-axis <text> --> <text>` both the bottom and top axis text will be rendered.
2. `y-axis <text>` only the bottom axis text will be rendered.
### Quadrants text
The `quadrant-[1,2,3,4]` determine what text would be displayed inside the quadrants.
#### Example
1. `quadrant-1 <text>` determine what text will be rendered inside the top right quadrant.
2. `quadrant-2 <text>` determine what text will be rendered inside the top left quadrant.
3. `quadrant-3 <text>` determine what text will be rendered inside the bottom left quadrant.
4. `quadrant-4 <text>` determine what text will be rendered inside the bottom right quadrant.
### Points
Points are used to plot a circle inside the quadrantChart. The syntax is `<text>: [x, y]` here x and y value is in the range 0 - 1.
#### Example
1. `Point 1: [0.75, 0.80]` here the Point 1 will be drawn in the top right quadrant.
2. `Point 2: [0.35, 0.24]` here the Point 2 will be drawn in the bottom left quadrant.
## Chart Configurations
| Parameter | Description | Default value |
| --------------------------------- | ------------------------------------------------------------------------------------------------- | :-----------: |
| chartWidth | Width of the chart | 500 |
| chartHeight | Height of the chart | 500 |
| titlePadding | Top and Bottom padding of the title | 10 |
| titleFontSize | Title font size | 20 |
| quadrantPadding | Padding outside all the quadrants | 5 |
| quadrantTextTopPadding | Quadrant text top padding when text is drawn on top ( not data points are there) | 5 |
| quadrantLabelFontSize | Quadrant text font size | 16 |
| quadrantInternalBorderStrokeWidth | Border stroke width inside the quadrants | 1 |
| quadrantExternalBorderStrokeWidth | Quadrant external border stroke width | 2 |
| xAxisLabelPadding | Top and bottom padding of x-axis text | 5 |
| xAxisLabelFontSize | X-axis texts font size | 16 |
| xAxisPosition | Position of x-axis (top , bottom) if there are points the x-axis will alway be rendered in bottom | 'top' |
| yAxisLabelPadding | Left and Right padding of y-axis text | 5 |
| yAxisLabelFontSize | Y-axis texts font size | 16 |
| yAxisPosition | Position of y-axis (left , right) | 'left' |
| pointTextPadding | Padding between point and the below text | 5 |
| pointLabelFontSize | Point text font size | 12 |
| pointRadius | Radius of the point to be drawn | 5 |
## Chart Theme Variables
| Parameter | Description |
| -------------------------------- | --------------------------------------- |
| quadrant1Fill | Fill color of the top right quadrant |
| quadrant2Fill | Fill color of the top left quadrant |
| quadrant3Fill | Fill color of the bottom left quadrant |
| quadrant4Fill | Fill color of the bottom right quadrant |
| quadrant1TextFill | Text color of the top right quadrant |
| quadrant2TextFill | Text color of the top left quadrant |
| quadrant3TextFill | Text color of the bottom left quadrant |
| quadrant4TextFill | Text color of the bottom right quadrant |
| quadrantPointFill | Points fill color |
| quadrantPointTextFill | Points text color |
| quadrantXAxisTextFill | X-axis text color |
| quadrantYAxisTextFill | Y-axis text color |
| quadrantInternalBorderStrokeFill | Quadrants inner border color |
| quadrantExternalBorderStrokeFill | Quadrants outer border color |
| quadrantTitleFill | Title color |
## Example on config and theme
```mermaid-example
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
quadrantChart
x-axis Urgent --> Not Urgent
y-axis Not Important --> important
quadrant-1 Plan
quadrant-2 Do
quadrant-3 Deligate
quadrant-4 Delete
```

View File

@ -1,4 +1,4 @@
import { darken, lighten, adjust, invert } from 'khroma';
import { darken, lighten, adjust, invert, isDark } from 'khroma';
import { mkBorder } from './theme-helpers.js';
import {
oldAttributeBackgroundColorEven,
@ -220,6 +220,31 @@ class Theme {
this.pieOuterStrokeColor = this.pieOuterStrokeColor || 'black';
this.pieOpacity = this.pieOpacity || '0.7';
/* quadrant-graph */
this.quadrant1Fill = this.quadrant1Fill || this.primaryColor;
this.quadrant2Fill = this.quadrant2Fill || adjust(this.primaryColor, { r: 5, g: 5, b: 5 });
this.quadrant3Fill = this.quadrant3Fill || adjust(this.primaryColor, { r: 10, g: 10, b: 10 });
this.quadrant4Fill = this.quadrant4Fill || adjust(this.primaryColor, { r: 15, g: 15, b: 15 });
this.quadrant1TextFill = this.quadrant1TextFill || this.primaryTextColor;
this.quadrant2TextFill =
this.quadrant2TextFill || adjust(this.primaryTextColor, { r: -5, g: -5, b: -5 });
this.quadrant3TextFill =
this.quadrant3TextFill || adjust(this.primaryTextColor, { r: -10, g: -10, b: -10 });
this.quadrant4TextFill =
this.quadrant4TextFill || adjust(this.primaryTextColor, { r: -15, g: -15, b: -15 });
this.quadrantPointFill =
this.quadrantPointFill || isDark(this.quadrant1Fill)
? lighten(this.quadrant1Fill)
: darken(this.quadrant1Fill);
this.quadrantPointTextFill = this.quadrantPointTextFill || this.primaryTextColor;
this.quadrantXAxisTextFill = this.quadrantXAxisTextFill || this.primaryTextColor;
this.quadrantYAxisTextFill = this.quadrantYAxisTextFill || this.primaryTextColor;
this.quadrantInternalBorderStrokeFill =
this.quadrantInternalBorderStrokeFill || this.primaryBorderColor;
this.quadrantExternalBorderStrokeFill =
this.quadrantExternalBorderStrokeFill || this.primaryBorderColor;
this.quadrantTitleFill = this.quadrantTitleFill || this.primaryTextColor;
/* requirement-diagram */
this.requirementBackground = this.requirementBackground || this.primaryColor;
this.requirementBorderColor = this.requirementBorderColor || this.primaryBorderColor;

View File

@ -1,4 +1,4 @@
import { invert, lighten, darken, rgba, adjust } from 'khroma';
import { invert, lighten, darken, rgba, adjust, isDark } from 'khroma';
import { mkBorder } from './theme-helpers.js';
class Theme {
@ -226,6 +226,31 @@ class Theme {
this.pieOuterStrokeColor = this.pieOuterStrokeColor || 'black';
this.pieOpacity = this.pieOpacity || '0.7';
/* quadrant-graph */
this.quadrant1Fill = this.quadrant1Fill || this.primaryColor;
this.quadrant2Fill = this.quadrant2Fill || adjust(this.primaryColor, { r: 5, g: 5, b: 5 });
this.quadrant3Fill = this.quadrant3Fill || adjust(this.primaryColor, { r: 10, g: 10, b: 10 });
this.quadrant4Fill = this.quadrant4Fill || adjust(this.primaryColor, { r: 15, g: 15, b: 15 });
this.quadrant1TextFill = this.quadrant1TextFill || this.primaryTextColor;
this.quadrant2TextFill =
this.quadrant2TextFill || adjust(this.primaryTextColor, { r: -5, g: -5, b: -5 });
this.quadrant3TextFill =
this.quadrant3TextFill || adjust(this.primaryTextColor, { r: -10, g: -10, b: -10 });
this.quadrant4TextFill =
this.quadrant4TextFill || adjust(this.primaryTextColor, { r: -15, g: -15, b: -15 });
this.quadrantPointFill =
this.quadrantPointFill || isDark(this.quadrant1Fill)
? lighten(this.quadrant1Fill)
: darken(this.quadrant1Fill);
this.quadrantPointTextFill = this.quadrantPointTextFill || this.primaryTextColor;
this.quadrantXAxisTextFill = this.quadrantXAxisTextFill || this.primaryTextColor;
this.quadrantYAxisTextFill = this.quadrantYAxisTextFill || this.primaryTextColor;
this.quadrantInternalBorderStrokeFill =
this.quadrantInternalBorderStrokeFill || this.primaryBorderColor;
this.quadrantExternalBorderStrokeFill =
this.quadrantExternalBorderStrokeFill || this.primaryBorderColor;
this.quadrantTitleFill = this.quadrantTitleFill || this.primaryTextColor;
/* class */
this.classText = this.primaryTextColor;

View File

@ -1,4 +1,4 @@
import { invert, lighten, rgba, adjust, darken } from 'khroma';
import { invert, lighten, rgba, adjust, darken, isDark } from 'khroma';
import { mkBorder } from './theme-helpers.js';
import {
oldAttributeBackgroundColorEven,
@ -247,6 +247,31 @@ class Theme {
this.pieOuterStrokeColor = this.pieOuterStrokeColor || 'black';
this.pieOpacity = this.pieOpacity || '0.7';
/* quadrant-graph */
this.quadrant1Fill = this.quadrant1Fill || this.primaryColor;
this.quadrant2Fill = this.quadrant2Fill || adjust(this.primaryColor, { r: 5, g: 5, b: 5 });
this.quadrant3Fill = this.quadrant3Fill || adjust(this.primaryColor, { r: 10, g: 10, b: 10 });
this.quadrant4Fill = this.quadrant4Fill || adjust(this.primaryColor, { r: 15, g: 15, b: 15 });
this.quadrant1TextFill = this.quadrant1TextFill || this.primaryTextColor;
this.quadrant2TextFill =
this.quadrant2TextFill || adjust(this.primaryTextColor, { r: -5, g: -5, b: -5 });
this.quadrant3TextFill =
this.quadrant3TextFill || adjust(this.primaryTextColor, { r: -10, g: -10, b: -10 });
this.quadrant4TextFill =
this.quadrant4TextFill || adjust(this.primaryTextColor, { r: -15, g: -15, b: -15 });
this.quadrantPointFill =
this.quadrantPointFill || isDark(this.quadrant1Fill)
? lighten(this.quadrant1Fill)
: darken(this.quadrant1Fill);
this.quadrantPointTextFill = this.quadrantPointTextFill || this.primaryTextColor;
this.quadrantXAxisTextFill = this.quadrantXAxisTextFill || this.primaryTextColor;
this.quadrantYAxisTextFill = this.quadrantYAxisTextFill || this.primaryTextColor;
this.quadrantInternalBorderStrokeFill =
this.quadrantInternalBorderStrokeFill || this.primaryBorderColor;
this.quadrantExternalBorderStrokeFill =
this.quadrantExternalBorderStrokeFill || this.primaryBorderColor;
this.quadrantTitleFill = this.quadrantTitleFill || this.primaryTextColor;
/* requirement-diagram */
this.requirementBackground = this.requirementBackground || this.primaryColor;
this.requirementBorderColor = this.requirementBorderColor || this.primaryBorderColor;

View File

@ -1,4 +1,4 @@
import { darken, lighten, adjust, invert } from 'khroma';
import { darken, lighten, adjust, invert, isDark } from 'khroma';
import { mkBorder } from './theme-helpers.js';
import {
oldAttributeBackgroundColorEven,
@ -215,6 +215,31 @@ class Theme {
this.pieOuterStrokeColor = this.pieOuterStrokeColor || 'black';
this.pieOpacity = this.pieOpacity || '0.7';
/* quadrant-graph */
this.quadrant1Fill = this.quadrant1Fill || this.primaryColor;
this.quadrant2Fill = this.quadrant2Fill || adjust(this.primaryColor, { r: 5, g: 5, b: 5 });
this.quadrant3Fill = this.quadrant3Fill || adjust(this.primaryColor, { r: 10, g: 10, b: 10 });
this.quadrant4Fill = this.quadrant4Fill || adjust(this.primaryColor, { r: 15, g: 15, b: 15 });
this.quadrant1TextFill = this.quadrant1TextFill || this.primaryTextColor;
this.quadrant2TextFill =
this.quadrant2TextFill || adjust(this.primaryTextColor, { r: -5, g: -5, b: -5 });
this.quadrant3TextFill =
this.quadrant3TextFill || adjust(this.primaryTextColor, { r: -10, g: -10, b: -10 });
this.quadrant4TextFill =
this.quadrant4TextFill || adjust(this.primaryTextColor, { r: -15, g: -15, b: -15 });
this.quadrantPointFill =
this.quadrantPointFill || isDark(this.quadrant1Fill)
? lighten(this.quadrant1Fill)
: darken(this.quadrant1Fill);
this.quadrantPointTextFill = this.quadrantPointTextFill || this.primaryTextColor;
this.quadrantXAxisTextFill = this.quadrantXAxisTextFill || this.primaryTextColor;
this.quadrantYAxisTextFill = this.quadrantYAxisTextFill || this.primaryTextColor;
this.quadrantInternalBorderStrokeFill =
this.quadrantInternalBorderStrokeFill || this.primaryBorderColor;
this.quadrantExternalBorderStrokeFill =
this.quadrantExternalBorderStrokeFill || this.primaryBorderColor;
this.quadrantTitleFill = this.quadrantTitleFill || this.primaryTextColor;
/* requirement-diagram */
this.requirementBackground = this.requirementBackground || this.primaryColor;
this.requirementBorderColor = this.requirementBorderColor || this.primaryBorderColor;

View File

@ -1,4 +1,4 @@
import { invert, darken, lighten, adjust } from 'khroma';
import { invert, darken, lighten, adjust, isDark } from 'khroma';
import { mkBorder } from './theme-helpers.js';
import {
oldAttributeBackgroundColorEven,
@ -246,6 +246,31 @@ class Theme {
this.pieOuterStrokeColor = this.pieOuterStrokeColor || 'black';
this.pieOpacity = this.pieOpacity || '0.7';
/* quadrant-graph */
this.quadrant1Fill = this.quadrant1Fill || this.primaryColor;
this.quadrant2Fill = this.quadrant2Fill || adjust(this.primaryColor, { r: 5, g: 5, b: 5 });
this.quadrant3Fill = this.quadrant3Fill || adjust(this.primaryColor, { r: 10, g: 10, b: 10 });
this.quadrant4Fill = this.quadrant4Fill || adjust(this.primaryColor, { r: 15, g: 15, b: 15 });
this.quadrant1TextFill = this.quadrant1TextFill || this.primaryTextColor;
this.quadrant2TextFill =
this.quadrant2TextFill || adjust(this.primaryTextColor, { r: -5, g: -5, b: -5 });
this.quadrant3TextFill =
this.quadrant3TextFill || adjust(this.primaryTextColor, { r: -10, g: -10, b: -10 });
this.quadrant4TextFill =
this.quadrant4TextFill || adjust(this.primaryTextColor, { r: -15, g: -15, b: -15 });
this.quadrantPointFill =
this.quadrantPointFill || isDark(this.quadrant1Fill)
? lighten(this.quadrant1Fill)
: darken(this.quadrant1Fill);
this.quadrantPointTextFill = this.quadrantPointTextFill || this.primaryTextColor;
this.quadrantXAxisTextFill = this.quadrantXAxisTextFill || this.primaryTextColor;
this.quadrantYAxisTextFill = this.quadrantYAxisTextFill || this.primaryTextColor;
this.quadrantInternalBorderStrokeFill =
this.quadrantInternalBorderStrokeFill || this.primaryBorderColor;
this.quadrantExternalBorderStrokeFill =
this.quadrantExternalBorderStrokeFill || this.primaryBorderColor;
this.quadrantTitleFill = this.quadrantTitleFill || this.primaryTextColor;
/* requirement-diagram */
this.requirementBackground = this.requirementBackground || this.primaryColor;
this.requirementBorderColor = this.requirementBorderColor || this.primaryBorderColor;