Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cards/local/reports/riskMatrixWidget.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"displayName": "Risk matrix widget",
"description": "A likelihood by impact risk matrix, banded green, amber and red",
"description": "A likelihood by impact risk matrix, banded green, amber and red, or coloured by the risk levels of your own method",
"category": "General",
"name": "base/reports/riskMatrixWidget"
}
69 changes: 64 additions & 5 deletions .cards/local/reports/riskMatrixWidget/index.adoc.hbs
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
{{#each cells}}{{#if (ne (add (max this.likelihood ../likelihood.length) (max this.impact ../impact.length)) (add ../likelihood.length ../impact.length))}}
WARNING: A cell at likelihood {{this.likelihood}}, impact {{this.impact}} falls outside this matrix, which has {{../likelihood.length}} likelihood levels and {{../impact.length}} impact levels. Its count of {{this.count}} is not shown.

{{/if}}{{/each}}{{#vegaLite}}
{{/if}}{{/each}}{{#if levelGrid}}{{#if (ne levelGrid.length likelihood.length)}}
WARNING: The level grid has {{levelGrid.length}} rows, but this matrix has {{likelihood.length}} likelihood levels. Cells without a level are drawn in grey.

{{/if}}{{#each levelGrid}}{{#if (ne this.length @root.impact.length)}}
WARNING: Row {{add @index 1}} of the level grid has {{this.length}} entries, but this matrix has {{@root.impact.length}} impact levels. Cells without a level are drawn in grey.

{{/if}}{{#each this}}{{#if (ne (max this @root.levels.length) @root.levels.length)}}
WARNING: Row {{add @../index 1}} of the level grid refers to level {{this}}, but only {{@root.levels.length}} levels are defined. That cell is drawn in grey.

{{/if}}{{/each}}{{/each}}{{/if}}{{#vegaLite}}
"spec": {
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"width": {"step": 58},
Expand All @@ -18,11 +27,31 @@ WARNING: A cell at likelihood {{this.likelihood}}, impact {{this.impact}} falls
"data": {
"values": [
{{#each likelihood as |lLabel lIdx|}}{{#each ../impact as |iLabel iIdx|}}
{"l": {{lIdx}}, "i": {{iIdx}}, "lLabel": "{{{jsonEscape lLabel}}}", "iLabel": "{{{jsonEscape iLabel}}}", "key": "{{lIdx}}-{{iIdx}}"}{{#unless @last}},{{/unless}}
{"l": {{lIdx}}, "i": {{iIdx}}, "lLabel": "{{{jsonEscape lLabel}}}", "iLabel": "{{{jsonEscape iLabel}}}", "key": "{{lIdx}}-{{iIdx}}"{{#with (lookup (lookup @root.levelGrid lIdx) iIdx)}}, "level": {{this}}{{/with}} }{{#unless @last}},{{/unless}}
{{/each}}{{#unless @last}},{{/unless}}{{/each}}
]
},
"transform": [
{{#if levels}}
{
"lookup": "level",
"from": {
"data": {
"values": [
{{#each levels}}
{"level": {{add @index 1}}, "levelName": "{{{jsonEscape this.name}}}", "colour": {{this.colour}} }{{#unless @last}},{{/unless}}
{{/each}}
]
},
"key": "level",
"fields": ["levelName", "colour"]
}
},
{
"calculate": "datum.colour === 5 ? '#ffffff' : '#1a1a1a'",
"as": "ink"
}
{{else}}
{
"joinaggregate": [
{"op": "max", "field": "l", "as": "lMax"},
Expand All @@ -32,7 +61,12 @@ WARNING: A cell at likelihood {{this.likelihood}}, impact {{this.impact}} falls
{
"calculate": "(datum.l + datum.i) / (datum.lMax + datum.iMax) <= 1 / 3 ? '#0ca30c' : (datum.l + datum.i) / (datum.lMax + datum.iMax) <= 2 / 3 ? '#fab219' : '#d03b3b'",
"as": "band"
},
{
"calculate": "'#1a1a1a'",
"as": "ink"
}
{{/if}}
],
"layer": [
{
Expand Down Expand Up @@ -80,7 +114,32 @@ WARNING: A cell at likelihood {{this.likelihood}}, impact {{this.impact}} falls
"titleColor": "currentColor"
}
},
{{#if levels}}
"color": {
"condition": {
"test": "datum.levelName != null",
"field": "levelName",
"type": "nominal",
"scale": {
"domain": [{{#each levels}}{{#with (lookup @root.levels (subtract (subtract @root.levels.length 1) @index))}}"{{{jsonEscape this.name}}}"{{/with}}{{#unless @last}}, {{/unless}}{{/each}}],
"range": [{{#each levels}}{{#with (lookup (lookup @root.levels (subtract (subtract @root.levels.length 1) @index)) "colour")}}"{{#if (eq this 1)}}#0ca30c{{/if}}{{#if (eq this 2)}}#b5e68a{{/if}}{{#if (eq this 3)}}#fab219{{/if}}{{#if (eq this 4)}}#d03b3b{{/if}}{{#if (eq this 5)}}#7a1414{{/if}}"{{/with}}{{#unless @last}}, {{/unless}}{{/each}}]
},
"legend": {
"orient": "right",
"title": null,
"symbolType": "square",
"symbolSize": 180,
"symbolStrokeWidth": 0,
"labelFontSize": 12,
"labelColor": "currentColor",
"labelLimit": 0
}
},
"value": "#9a9a9a"
}
{{else}}
"color": {"field": "band", "type": "nominal", "scale": null, "legend": null}
{{/if}}
}
},
{
Expand All @@ -104,8 +163,7 @@ WARNING: A cell at likelihood {{this.likelihood}}, impact {{this.impact}} falls
"mark": {
"type": "text",
"fontSize": 15,
"fontWeight": "bold",
"color": "#1a1a1a"
"fontWeight": "bold"
},
"encoding": {
"y": {
Expand Down Expand Up @@ -145,7 +203,8 @@ WARNING: A cell at likelihood {{this.likelihood}}, impact {{this.impact}} falls
"titleColor": "currentColor"
}
},
"text": {"field": "count", "type": "quantitative"}
"text": {"field": "count", "type": "quantitative"},
"fill": {"field": "ink", "type": "nominal", "scale": null, "legend": null}
}
}{{#if legend}},
{
Expand Down
39 changes: 39 additions & 0 deletions .cards/local/reports/riskMatrixWidget/parameterSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,41 @@
"description": "Axis title for the impact scale (optional, defaults to 'Impact')",
"type": "string"
},
"levels": {
"description": "Risk levels of your risk method, lowest first (optional, give together with 'levelGrid'). Each level names a step of a fixed five-step colour scale, so the matrix colours follow your method instead of the position of each cell. The levels are listed in a legend beside the matrix.",
"type": "array",
"minItems": 2,
"maxItems": 5,
"items": {
"additionalProperties": false,
"type": "object",
"properties": {
"name": {
"description": "Name of this risk level, shown in the legend",
"type": "string"
},
"colour": {
"description": "Step of the colour scale: 1 green, 2 light green, 3 amber, 4 red, 5 dark red",
"type": "integer",
"minimum": 1,
"maximum": 5
}
},
"required": ["name", "colour"]
}
},
"levelGrid": {
"description": "The risk level of every cell (optional, give together with 'levels'). One row per likelihood level, lowest first; each row has one entry per impact level, lowest first. Each entry is a 1-based position in 'levels'.",
"type": "array",
"minItems": 3,
"maxItems": 5,
"items": {
"type": "array",
"minItems": 3,
"maxItems": 5,
"items": {"type": "integer", "minimum": 1, "maximum": 5}
}
},
"cells": {
"description": "How many risks sit in each cell. Likelihood and impact are 1-based positions in the scales above. Cells you leave out are drawn in their risk colour with no count.",
"type": "array",
Expand Down Expand Up @@ -67,6 +102,10 @@
}
},
"required": ["name", "title", "likelihood", "impact"],
"dependencies": {
"levels": ["levelGrid"],
"levelGrid": ["levels"]
},
"title": "Report",
"type": "object"
}
95 changes: 93 additions & 2 deletions cardRoot/base_h8a3eqto/c/base_4c0lf8u0/c/base_kxwagxhg/index.adoc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
= Risk matrix widget

Cell colour is the risk level, derived from the position in the two scales, using the
fixed status palette. The number in a cell is how many risks sit there. Cells with no
By default, cell colour is the risk level, derived from the position in the two scales,
using the fixed status palette. Your own risk levels can replace it; see the last sections. The number in a cell is how many risks sit there. Cells with no
risks still show their risk colour.

== Five by five
Expand Down Expand Up @@ -116,3 +116,94 @@ count would otherwise be dropped without a word. The widget says so instead.
"likelihood": ["Low", "Medium", "High"],
"impact": ["Low", "Medium", "High"]
{{/report}}

== Custom risk levels

When your risk method defines its own risk level for every cell, give the levels and a
level grid. The cells are then coloured by your method instead of their position, and
the levels are listed in a legend. Each level picks one of five colour steps: 1 green,
2 light green, 3 amber, 4 red, 5 dark red.

{{#report}}
"name": "base/reports/riskMatrixWidget",
"title": "Risk matrix",
"legend": "open risks by likelihood and impact",
"likelihood": ["Very Low", "Low", "Moderate", "High", "Very High"],
"impact": ["Very Low", "Low", "Moderate", "High", "Very High"],
"levels": [
{"name": "Very Low", "colour": 1},
{"name": "Low", "colour": 2},
{"name": "Moderate", "colour": 3},
{"name": "High", "colour": 4},
{"name": "Very High", "colour": 5}
],
"levelGrid": [
[1, 1, 2, 2, 3],
[1, 2, 3, 3, 3],
[2, 3, 3, 4, 4],
[2, 3, 4, 4, 5],
[3, 3, 4, 5, 5]
],
"cells": [
{"likelihood": 5, "impact": 5, "count": 1},
{"likelihood": 4, "impact": 5, "count": 2},
{"likelihood": 3, "impact": 4, "count": 4},
{"likelihood": 3, "impact": 3, "count": 6},
{"likelihood": 2, "impact": 3, "count": 11},
{"likelihood": 2, "impact": 2, "count": 5},
{"likelihood": 1, "impact": 1, "count": 3}
]
{{/report}}

== Custom risk levels, three levels

{{#report}}
"name": "base/reports/riskMatrixWidget",
"title": "Risk matrix",
"likelihood": ["Rare", "Unlikely", "Possible", "Likely"],
"impact": ["Minor", "Moderate", "Major", "Severe"],
"levels": [
{"name": "Acceptable", "colour": 1},
{"name": "Tolerable", "colour": 3},
{"name": "Unacceptable", "colour": 4}
],
"levelGrid": [
[1, 1, 1, 2],
[1, 1, 2, 3],
[1, 2, 3, 3],
[2, 3, 3, 3]
],
"cells": [
{"likelihood": 4, "impact": 4, "count": 1},
{"likelihood": 2, "impact": 3, "count": 3},
{"likelihood": 1, "impact": 1, "count": 5}
]
{{/report}}

== A level grid that does not fit

The schema cannot tie the size of the level grid to the two scales, or its entries to the
number of levels. Cells without a valid level are drawn in grey, and the widget says why.

{{#report}}
"name": "base/reports/riskMatrixWidget",
"title": "Risk matrix",
"legend": "one row is short and one cell names a missing level",
"likelihood": ["Rare", "Unlikely", "Possible", "Likely"],
"impact": ["Minor", "Moderate", "Major", "Severe"],
"levels": [
{"name": "Low", "colour": 1},
{"name": "Medium", "colour": 3},
{"name": "High", "colour": 4}
],
"levelGrid": [
[1, 1, 2, 2],
[1, 2, 2],
[2, 2, 3, 3],
[2, 3, 3, 4]
],
"cells": [
{"likelihood": 2, "impact": 4, "count": 2},
{"likelihood": 4, "impact": 4, "count": 1}
]
{{/report}}
Loading