Chart Rendering API
This document provides detailed information for developers about the implementation, usage, and integration of the Chart Rendering API feature within the system.
See also:
- Guide: Chart Development Workflow
- Feature: Progressive Enhancements
- ADR006: Rendering of Charts
- ADR007: Deployment Options for SVG Service for Rendering Charts
- ADR009: JavaScript Library Selection for ASP.NET Core
- ADR015: Chart Rendering API Performance and Scalability Improvements
Overview
As discussed across other documentation above, the need across the service to server side render charts led to the creation of a Node.JS function app with this single responsibility. This API is data agnostic - requests should include both the dataset and associated configuration. One endpoint should exist per chart type.
Goals
Primary Goal
The chart rendering API should return a visual representation of input data and configuration for the following chart types:
- Vertical bar chart
- Horizontal bar chart
The response should include an SVG containing the various correctly dimensioned and positioned elements only, with minimal responsibility over styles. Instead, well-known CSS classes should be set allowing the consumer to resolve to the required styles in the client browser. This helps to reduce the amount of logic in each chart builder as well as bandwidth required for each response.
The endpoints should support single or batched chart requests, and in the case of the latter, resultant SVG should be cross referenced to its original requesting identifier.
Secondary Goals
Support future extensibility through backwards-compatible configuration.
Prerequisites/Dependencies
- Node 22 and/or switch to that version using nvm
- Azure Functions Core Tools
- .NET 8 for end-to-end API tests
External Dependencies
- Various npm packages, notably:
- App Insights for (optional) dependency tracking
Internal Dependencies
- Access to Azure DevOps for validating pipelines
- Access to Azure portal for validating infrastructure
API Reference
POST api/horizontalBarChart
Input
The payload expected by this endpoint is either a single or multiple HorizontalBarChartDefinition types:
| Required Property | Type | Definition |
|---|---|---|
data |
object[] | Array of items to render |
keyField |
string | Key identifier. Must resolve to a property on object types in data. |
valueField |
string or string[] | Value identifier(s). Each must resolve to a property on object types in data. |
valueType |
percent, currency, or numeric |
Describes how values on the chart should be interpreted and formatted |
If multiple definitions are supplied, the id property below is mandatory for each so as to not fail validation.
Where an array of strings is passed in valueField, each value will be represented as a “stack” on the chart
| Optional Property | Type | Default | Definition |
|---|---|---|---|
barHeight |
number | 25 |
Height of horizontal bars |
domainMax |
number | Maximum resolved value | Maximum value for the chart domain. Value may be normalised in case out-of-range. |
domainMin |
number | 0 |
Minimum value for the chart domain. Value may be normalised in case out-of-range. |
groupedKeys |
object | Dictionary of group name to array of key values within that group to apply known styles to | |
highlightKey |
string | Key of an item in data to assign highlight styles to |
|
id |
string | New UUID v4 | Unique identifier of the chart data/configuration combination |
labelField |
string | Keyed off object types in data |
|
labelFormat |
string | Format string to use for labels on y-axis, where %1 is the key and %2 is the label |
|
legendLabels |
string[] | Array of strings to display in the legend of a stacked bar chart. Array order must match the entries in the valueField array |
|
linkFormat |
string | Format string to use for rendering y-axis labels as links, where %1 is the key |
|
missingDataLabel |
string | Label to render in the case of a data point containing null or undefined value | |
missingDataLabelWidth |
number | Width in pixels of the above label (for positioning, due to unpredictable typeface) | |
paddingInner |
number | 0.2 |
The ratio of the range for blank space between bands |
paddingOuter |
number | 0.1 |
The ratio of the range for blank space before the first and after the last band |
sort |
asc or desc |
Sort data by resolved values after normalisation |
|
width |
number | 928 |
Width of chart surface |
xAxisLabel |
string | Label to render on the x-axis |
Output
| Status code | Condition | Response type | Response body definition |
|---|---|---|---|
200 |
Single definition in payload with data only |
application/json |
JSON in the format: { "id": "new uuidv4", "html": "<svg />" } |
200 |
Single definition in payload with data only and x-accept header of image/svg+xml |
image/svg+xml |
<svg /> |
200 |
Single definition in payload with data and id |
application/json |
JSON in the format: { "id": "id from payload", "html": "<svg />" } |
200 |
Array of definitions in payload with data and id |
application/json |
JSON in the format: [{ "id": "id from payload", "html": "<svg />" }, { "id": "next id from payload", "html": "<svg />" }, ...] |
400 |
Invalid payload | application/json |
JSON in the format: { error: "Message"; errors: ["Details"] } |
500 |
Unhandled processing error | application/json |
JSON in the format: { error: "Message" } |
Process
The ‘build template’ steps below refer to standard JavaScript string templates. As part of the performance investigation and move away from the virtual DOM it was seen to be far more performant to build each <svg> in this way from the basic building blocks, e.g.:
<rect>for a bar, with<text>for its label<g>with<line>and<text>for a plain axis tick<g>with<line>,<text>,<a>and<tspan>for an axis tick rendered as a link<g>with<path>and<text>for an axis line and label
The initial work with the virtual DOM helped to identify how each chart element’s x/y/dy/width/height/d attribute(s) should be resolved (based off x and y scales), as did use of the D3 gallery.
flowchart TD
accDescr: Horizontal bar chart request
A[Request received] --> B{Payload parsed?}
B -->|No| C[Return 400]
B -->|Yes| D{Payload validated?}
D -->|No| C[Return 400]
B -->|Yes| E[Normalise data]
E --> F[Resolve groups]
F --> G[Sort data]
G --> H[Resolve domain]
H --> I[Build linear scale for x with domain]
I --> J[Build banded scale for y with normalised data]
J --> K[Build bar <rect> templates, assigning series, highlight and group CSS classes]
K --> L[Build bar label <text> templates]
L --> M{Missing labels configured?}
M -->|Yes| N[Build missing label <rect> and <text> templates]
M -->|No| O[Build x-axis tick <line> and <text> templates]
N --> O
O --> P[Build y-axis tick <line> and <text> templates]
P --> Q[Render templates to <svg>]
Q --> R[Return 200]
Minimal example
Request
curl -X 'POST' \
'http://localhost:7076/api/horizontalBarChart' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"id": "id",
"keyField": "key",
"valueField": "value",
"valueType": "currency",
"data": [
{
"key": "00001",
"value": "1"
},
{
"key": "00002",
"value": "2"
},
{
"key": "00003",
"value": "3"
}
]
}'
Response body
{
"id": "id",
"html": "<svg width=\"928\" height=\"118\" viewBox=\"0,0,928,118\" data-chart-id=\"id\" xmlns=\"http://www.w3.org/2000/svg\"><g><rect x=\"317.3333333333333\" y=\"22.6\" width=\"188.5555555555556\" height=\"20.8\" data-key=\"00001\" class=\"chart-cell chart-cell__series-0\"/><rect x=\"317.3333333333333\" y=\"48.6\" width=\"377.11111111111114\" height=\"20.8\" data-key=\"00002\" class=\"chart-cell chart-cell__series-0\"/><rect x=\"317.3333333333333\" y=\"74.6\" width=\"565.6666666666667\" height=\"20.8\" data-key=\"00003\" class=\"chart-cell chart-cell__series-0\"/></g><g><text x=\"513.8888888888889\" y=\"33\" dy=\"0.35em\" class=\"chart-label chart-label__series-0\">1</text><text x=\"702.4444444444445\" y=\"59\" dy=\"0.35em\" class=\"chart-label chart-label__series-0\">2</text><text x=\"891\" y=\"85\" dy=\"0.35em\" class=\"chart-label chart-label__series-0\">3</text></g><g class=\"chart-axis chart-axis__x\" transform=\"translate(-2,98)\"><path class=\"domain\" stroke=\"currentColor\" d=\"M317.8333333333333,1V0.5H883.5V1\"/><g class=\"chart-tick\" transform=\"translate(317.8333333333333,0)\"><line y2=\"6\" x1=\"1\" x2=\"1\"/><text y=\"9\" dy=\"0.71em\" x1=\"1\" x2=\"1\">£0</text></g><g class=\"chart-tick\" transform=\"translate(506.3888888888889,0)\"><line y2=\"6\" x1=\"1\" x2=\"1\"/><text y=\"9\" dy=\"0.71em\" x1=\"1\" x2=\"1\">£1</text></g><g class=\"chart-tick\" transform=\"translate(694.9444444444445,0)\"><line y2=\"6\" x1=\"1\" x2=\"1\"/><text y=\"9\" dy=\"0.71em\" x1=\"1\" x2=\"1\">£2</text></g><g class=\"chart-tick\" transform=\"translate(883.5,0)\"><line y2=\"6\" x1=\"1\" x2=\"1\"/><text y=\"9\" dy=\"0.71em\" x1=\"1\" x2=\"1\">£3</text></g></g><g class=\"chart-axis chart-axis__y\" transform=\"translate(314.3333333333333,0)\"><path class=\"domain\" stroke=\"currentColor\" d=\"M0,20.5H0.5V98.5H0\" transform=\"translate(0,0)\"/><g class=\"chart-tick\" transform=\"translate(0,33)\"><line x2=\"-6\"/><text x=\"-9\" dy=\"0.32em\">00001</text></g><g class=\"chart-tick\" transform=\"translate(0,59)\"><line x2=\"-6\"/><text x=\"-9\" dy=\"0.32em\">00002</text></g><g class=\"chart-tick\" transform=\"translate(0,85)\"><line x2=\"-6\"/><text x=\"-9\" dy=\"0.32em\">00003</text></g></g></svg>"
}
Response body as raw SVG
Response body as styled SVG (from browser style sheet)

POST api/verticalBarChart
Input
The payload expected by this endpoint is either a single or multiple VerticalBarChartDefinition types:
| Required Property | Type | Definition |
|---|---|---|
data |
object[] | Array of items to render |
keyField |
string | Key identifier. Must resolve to a property on object types in data. |
valueField |
string | Value identifier. Must resolve to a property on object types in data. |
If multiple definitions are supplied, the id property below is mandatory for each so as to not fail validation.
| Optional Property | Type | Default | Definition |
|---|---|---|---|
domainMax |
number | Maximum resolved value | Maximum value for the chart domain. Value may be normalised in case out-of-range. |
domainMin |
number | 0 |
Minimum value for the chart domain. Value may be normalised in case out-of-range. |
height |
number | 500 |
Height of chart surface |
highlightKey |
string | Key of an item in data to assign highlight styles to |
|
id |
string | New UUID v4 | Unique identifier of the chart data/configuration combination |
sort |
asc or desc |
Sort data by resolved values after normalisation |
|
width |
number | 928 |
Width of chart surface |
Output
| Status code | Condition | Response type | Response body definition |
|---|---|---|---|
200 |
Single definition in payload with data only |
application/json |
JSON in the format: { "id": "new uuidv4", "html": "<svg />" } |
200 |
Single definition in payload with data only and x-accept header of image/svg+xml |
image/svg+xml |
<svg /> |
200 |
Single definition in payload with data and id |
application/json |
JSON in the format: { "id": "id from payload", "html": "<svg />" } |
200 |
Array of definitions in payload with data and id |
application/json |
JSON in the format: [{ "id": "id from payload", "html": "<svg />" }, { "id": "next id from payload", "html": "<svg />" }, ...] |
400 |
Invalid payload | application/json |
JSON in the format: { error: "Message"; errors: ["Details"] } |
500 |
Unhandled processing error | application/json |
JSON in the format: { error: "Message" } |
Process
The ‘build template’ steps below refer to standard JavaScript string templates as above.
flowchart TD
accDescr: Vertical bar chart request
A[Request received] --> B{Payload parsed?}
B -->|No| C[Return 400]
B -->|Yes| D{Payload validated?}
D -->|No| C[Return 400]
B -->|Yes| E[Sort data]
E --> F[Build banded scale for x with domain as all keys]
F --> G[Resolve domain]
G --> H[Build linear scale for y]
H --> I[Build bar <rect> templates, assigning series and highlight CSS classes]
I --> J[Render templates to <svg>]
J --> K[Return 200]
Minimal example
Request
curl -X 'POST' \
'http://localhost:7076/api/verticalBarChart' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"id": "id",
"keyField": "key",
"valueField": "value",
"data": [
{
"key": "00001",
"value": "1"
},
{
"key": "00002",
"value": "2"
},
{
"key": "00003",
"value": "3"
}
]
}'
Response body
{
"id": "id",
"html": "<svg width=\"928\" height=\"500\" viewBox=\"0,0,928,500\" data-chart-id=\"id\" xmlns=\"http://www.w3.org/2000/svg\"><g><rect x=\"60.625\" y=\"340.00000000000006\" height=\"159.99999999999994\" width=\"230.5\" data-bar-index=\"0\" class=\"chart-cell chart-cell__series-0\"/><rect x=\"348.75\" y=\"180.00000000000003\" height=\"320\" width=\"230.5\" data-bar-index=\"1\" class=\"chart-cell chart-cell__series-0\"/><rect x=\"636.875\" y=\"20\" height=\"480\" width=\"230.5\" data-bar-index=\"2\" class=\"chart-cell chart-cell__series-0\"/></g></svg>"
}
Response body as raw SVG
Response body as styled SVG (from browser style sheet)

GET api/health
Returns 200, for consumption by upstream availability checks.
GET api/openapi.json
Gets OpenAPI spec for consumption by Swagger UI.
GET api/swagger/{*swaggerAsset}
Resolves Swagger UI assets to host http://localhost:7076/api/swagger/ locally.
POST api/horizontalBarChart/dom (local development)
This endpoint is excluded from production builds via TSConfig.
During initial rapid development of this chart type, D3 was used to render directly to a virtual DOM using d3-selection and xmldom. Performance of using a virtual DOM was not suitable for production use even with explicit worker management (see ADRs above) but the endpoint remains for local development.
See also:
POST api/verticalBarChart/dom (local development)
This endpoint is excluded from production builds via TSConfig.
See above.
Deployment
The Chart Rendering function app is deployed and managed along with the other function apps in the Platform solution within the monorepo. The Terraform is slightly different due to this being a Node rather than .NET function app, but this is all managed within the functions TF module:
azurerm_linux_function_appresource type used instead ofazurerm_windows_function_appalong with22for thenode_version- Above resource’s identity assigned to dependent service access policies instead of the
windowsequivalent - Optional (elastic/standard) worker count variables supported for fine-tuning configuration
- Separate SKU variable (from
windowsequivalent) to support alternative app service plans per environment
API Tests
API tests against the Chart Rendering endpoints takes place within pipeline runs against main, as per the other function apps. Assertions are done against expected JSON or SVG responses rather than explicit deeper inspection. There are no additional dependencies (such as seeded test data).
Known Issues
- Results are not cached. So far this has not raised any performance concerns.
Horizontal bar chart
- Negative values filtered out from chart with CSS class alone applied to identify as such.
Vertical bar chart
- Vertical bars only are the only rendered elements at this time as relative entries alone required by consumer.
- Negative values may cause unexpected behaviour due to lack of data normalisation.
Horizontal stacked bar charts
When support for stacked bar charts was added, it was decided that all horizontal bar charts would be stacked charts, with existing charts simply rendering with a single data stack, and being visually identical to a non-stacked chart.
Chart legend
Legends are displayed on single data stack horizontal bar charts with grouped keys, and on horizontal bar charts with multiple data stacks, but without grouped keys. Multiple data stacks are not compatible with grouped keys. Legends for both scenarios are built using the legendLabels request property.
For charts with multiple data stacks the valueField property must be an array of strings, each string being a property on objects in data, and legendLabels should contain the names of these properties, eg.:
{
...
"valueField": [
"headTeachers",
"deputyHeadTeachers",
"assistantHeadTeachers",
"leadershipNonTeachers"
],
"legendLabels": [
"Head teachers",
"Deputy head teachers",
"Assistant head teachers",
"Leadership non-teachers"
]
...
}
For charts with a single data stack and grouped keys the site scss must contain specifically named css classes to to provide each legend with the appropriate colour. The CSS class providing the colour should be inside the class hierarchy ssr-chart > chart-cell, and named chart-legend- followed by the text of the legend label, with spaces replaced by hyphens, and in lower case. Care should be taken to align these css classes with those that provide the colour for the bars themselves. For example, if legendLabels contains the following …
...
"legendLabels": [
"Well above average",
"Above average"
]
...
… the SCSS should contain classes like this …
.ssr-chart {
.chart-cell {
&.chart-cell__group-banding-well-above-average {
fill: govuk-colour("turquoise");
stroke: govuk-colour("turquoise");
}
&.chart-cell__group-banding-above-average {
fill: govuk-colour("light-blue");
stroke: govuk-colour("light-blue");
}
&.chart-legend-well-above-average {
fill: govuk-colour("turquoise");
stroke: govuk-colour("turquoise");
}
&.chart-legend-above-average {
fill: govuk-colour("light-blue");
stroke: govuk-colour("light-blue");
}
}
}
Future Features
Clustered (“multi-series”) horizontal bar chart
The horizontal bar charts only support a single series, by design. To support multiple series the input configuration will need to be modified. In front-end-components a separate chart type has been defined for this purpose, which should be avoided unless absolutely necessary for the production-ready version. Prototyping using the existing d3-selection/virtual DOM endpoint would be acceptable in order to identify what changes would need to be merged into the ‘string template’ version.
Looking at various examples there appear to be many ways of formatting a clustered bar chart. To provide backwards compatibility with single-series charts the best solution would probably be to:
- Include multiple series (including padding) in surface height calculation.
- Modify the
<rect>string templates for bars to loop through all keys defined in the multi series configuration. Calculateyto be relative to the key index (achieved with a separate scale band foryusing the series keys for the domain), to allow offset from the parent<g>(see below).
// e.g., from example above
const y0 = d3.scaleBand()
.domain(data.map(d => d[groupKey]))
.rangeRound([margin.top, height - margin.bottom])
.paddingInner(0.1);
const y1 = d3.scaleBand()
.domain(keys)
.rangeRound([y0.bandwidth(), 0])
.padding(0.1)
-
Assign series index or other identifier to
<rect>as well as class (currently hard-coded tochart-cell__series-0). -
Modify the
<text>string templates for labels similar to above. -
Add wrapper
<g>around each of the above with atransformto offset theyattribute to be the number of items in the series multiplied by the bar height (plus some padding).// e.g., from example above .join("g") .attr("transform", d => `translate(0,${y0(d[groupKey])})`)
SVG snippet
<!-- current -->
<svg width="928" height="118" viewBox="0,0,928,118" data-chart-id="id" xmlns="http://www.w3.org/2000/svg">
<g>
<rect x="317.3333333333333" y="22.6" width="188.5555555555556" height="20.8" data-key="00001" class="chart-cell chart-cell__series-0"/>
<rect x="317.3333333333333" y="48.6" width="377.11111111111114" height="20.8" data-key="00002" class="chart-cell chart-cell__series-0"/>
<rect x="317.3333333333333" y="74.6" width="565.6666666666667" height="20.8" data-key="00003" class="chart-cell chart-cell__series-0"/>
</g>
<g>
<text x="513.8888888888889" y="33" dy="0.35em" class="chart-label chart-label__series-0">1</text>
<text x="702.4444444444445" y="59" dy="0.35em" class="chart-label chart-label__series-0">2</text>
<text x="891" y="85" dy="0.35em" class="chart-label chart-label__series-0">3</text>
</g>
</svg>
<!-- proposed single series -->
<svg width="928" height="118" viewBox="0,0,928,118" data-chart-id="id" xmlns="http://www.w3.org/2000/svg">
<g>
<g transform="translate(0,22.6)">
<rect x="317.3333333333333" y="1" width="188.5555555555556" height="20.8" data-key="00001" class="chart-cell chart-cell__series-0"/>
<text x="513.8888888888889" y="11" dy="0.35em" class="chart-label chart-label__series-0">1</text>
</g>
<g transform="translate(0,48.6)">
<rect x="317.3333333333333" y="1" width="377.11111111111114" height="20.8" data-key="00002" class="chart-cell chart-cell__series-0"/>
<text x="702.4444444444445" y="11" dy="0.35em" class="chart-label chart-label__series-0">2</text>
</g>
<g transform="translate(0,74.6)">
<rect x="317.3333333333333" y="1" width="565.6666666666667" height="20.8" data-key="00003" class="chart-cell chart-cell__series-0"/>
<text x="891" y="11" dy="0.35em" class="chart-label chart-label__series-0">3</text>
</g>
</g>
</svg>
<!-- proposed multi series -->
<svg width="928" height="170" viewBox="0,0,928,170" data-chart-id="id" xmlns="http://www.w3.org/2000/svg">
<g>
<g transform="translate(0,22.6)">
<rect x="317.3333333333333" y="1" width="188.5555555555556" height="20.8" data-key="00001" class="chart-cell chart-cell__series-0"/>
<text x="513.8888888888889" y="11" dy="0.35em" class="chart-label chart-label__series-0">1</text>
<rect x="317.3333333333333" y="24" width="565.6666666666667" height="20.8" data-key="00003" class="chart-cell chart-cell__series-1"/>
<text x="891" y="34" dy="0.35em" class="chart-label chart-label__series-1">3</text>
</g>
<g transform="translate(0,72.6)">
<rect x="317.3333333333333" y="1" width="377.11111111111114" height="20.8" data-key="00002" class="chart-cell chart-cell__series-0"/>
<text x="702.4444444444445" y="11" dy="0.35em" class="chart-label chart-label__series-0">2</text>
<rect x="317.3333333333333" y="24" width="188.5555555555556" height="20.8" data-key="00001" class="chart-cell chart-cell__series-1"/>
<text x="513.8888888888889" y="34" dy="0.35em" class="chart-label chart-label__series-1">1</text>
</g>
<g transform="translate(0,122.6)">
<rect x="317.3333333333333" y="1" width="565.6666666666667" height="20.8" data-key="00003" class="chart-cell chart-cell__series-0"/>
<text x="891" y="11" dy="0.35em" class="chart-label chart-label__series-0">3</text>
<rect x="317.3333333333333" y="24" width="377.11111111111114" height="20.8" data-key="00002" class="chart-cell chart-cell__series-1"/>
<text x="702.4444444444445" y="34" dy="0.35em" class="chart-label chart-label__series-1">2</text>
</g>
</g>
</svg>
Other things to note
- If developing first in the
d3-selection/virtual DOM endpoint then it may be worth retrospectively updating that with the latest changes from the ‘string template’ version. A brand new endpoint could also be used, possibly based off one of the identified similar examples. - Domain calculations for
xwill need to take multiple series into account so as to not inadvertently truncate any bars. - Styles may need to be defined in consuming service for newly rendered CSS classes.
- Although the above proposal should work for ‘single series’ charts the SVG output will be different and so API tests will initially fail.
Altering the API interface
There are a couple of ways we could change the chart request to support clustered charts. We already support the valueField being an array of fields, which tells the renderer to use the different fields to build a stacked chart. We could introduce a new request field which controls how the renderer should treat an array of valueField fields - either as multiple stacks, or as multiple series in a clustered chart.
Alternatively we could allow the valueField request property to contain an array of arrays of fields. Each array of fields would represent a data series in a cluster, with each individual field representing a data field in a stacked bar. Therefore the following formats of request would create the follwoing types of chart:
Standard horizontal bar chart with a single data value per bar
"valueField": [
["totalExpenditeure"]
]
Stacked horizontal bar chart with multiple stacked data values per bar
"valueField": [
["Head teachers", "Deputy head teachers", "Assistant head teachers", "Leadership non-teachers"]
]
Clustered bar chart with a single data value per bar
"valueField": [
["balance"],
["outturn"]
]
Clustered stacked bar chart with multiple data value per bar
"valueField": [
["field1", "field2", "field3" ],
["field6", "field5", "field6" ]
]
It isn’t clear that there is a requirement for the final bar chart type there - clustered and stacked - and the argument can be made that the complexity in developing it isn’t worth the benefits.
Should that type of chart be created, there are a number of considerations in the code.
- Summations of the total value of each stacked bar will need to be stored in an array, to match each series in the data
const summationField: keyof T = "valueFieldSum" as [keyof T];
...
sumValueFields(normalisedData, valueFields, summationField);
- The summation field is used to calculate the domain of the x-axis of the chart in
utils.getDomain(), so this will need to be updated to handle an array of summation fields, and for each data item, select the largest value. - The
legendLabelsrequest field will need to contain labels for fields in all data series. This could either be done with a single array as at present, or with an array of labels per data series, if the presentation of labels needs to take into account the different series visually.
Line chart
This chart type does not yet exist in the Chart Rendering API. When implementing, the following features must be configurable:
- Single/multi series
- X-axis label (values/visibility)
- X-axis tick formatter
- Legend (values/visibility/position)
- Value type
- Value dots (visibility)
- Value labels (visibility)
The following features must always be supported:
- Y-axis ticks
- X-axis ticks
- Horizontal grid lines
- Domain management (will be different from that used for bar charts; see #233948 and associated existing implementation)
The current implementation in front-end-components includes all of the above features.
Examples
As per other guidance above, best practice would be to first implement this new chart type based on existing examples using D3 and the virtual DOM. Once this has been completed and the expected SVG outputted the rewrite to use string templates may proceed. Below are some examples to use as a starting point:
Styles
As per the other endpoints, inline styles via SVG attributes should be kept to a minimum and instead class names should be used. To help support line formatting, for example, the following is rendered in the existing client side line charts for a dashed line:
<g class="recharts-layer recharts-line chart-line chart-line-series-1">
<path stroke-width="3" class="recharts-curve recharts-line-curve" stroke-dasharray="20 7" stroke="#3182bd"
fill="none" width="860" height="300" d="M130,181.192L282,193.423L434,56.346L586,86.577L738,187.538L890,337.538">
</path>
</g>
For the proposed server rendered version this should be something like:
<g class="chart-line chart-line-series-1">
<path class="line-curve" fill="none" width="860" height="300"
d="M130,181.192L282,193.423L434,56.346L586,86.577L738,187.538L890,337.538"></path>
</g>
with CSS managing the styles:
.chart-line.chart-line-series-1 > .line-curve {
stroke-dasharray: 20px 7px;
stroke-width: 5px;
stroke: #3182bd;
}
This CSS definition could also then be re-used in the legend for consistency.
Progressive enhancement
The existing client side line charts optionally support tooltips when the mouse enters a value dot. As per the Progressive Enhancements feature, this should be added in once the server rendered chart has been implemented.