Skip to content

Commit

Permalink
Merge pull request #1397 from FlowFuse/384-ui-chart---interpolation-m…
Browse files Browse the repository at this point in the history
…ethod

Added chart interpolation method
  • Loading branch information
joepavitt authored Oct 15, 2024
2 parents 315cd03 + be91b6b commit 465f328
Show file tree
Hide file tree
Showing 10 changed files with 816 additions and 2 deletions.
644 changes: 644 additions & 0 deletions cypress/fixtures/flows/dashboard-chart-interpolation.json

Large diffs are not rendered by default.

63 changes: 63 additions & 0 deletions cypress/tests/widgets/chart-interpolation.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/// <reference types="cypress" />
import should from 'should'

/* eslint-disable cypress/no-unnecessary-waiting */
describe('Node-RED Dashboard 2.0 - Chart - Type: Interpolation', () => {
beforeEach(() => {
cy.deployFixture('dashboard-chart-interpolation')
cy.visit('/dashboard/page1')
})

it('renders charts with correct data', () => {
cy.get('#nrdb-ui-widget-dashboard-ui-chart-linear > div > canvas').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-chart-step > div > canvas').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-chart-bezier > div > canvas').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-chart-cubic > div > canvas').should('exist')
cy.get('#nrdb-ui-widget-dashboard-ui-chart-mono > div > canvas').should('exist')

// Add data points 4, 8, 3 and 1
cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-button-clear'))
cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-button-4'))
cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-button-8'))
cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-button-3'))
cy.clickAndWait(cy.get('#nrdb-ui-widget-dashboard-ui-button-1'))

// eslint-disable-next-line promise/catch-or-return, promise/always-return
cy.window().then(win => {
should(win.uiCharts).is.not.empty()
const linear = win.uiCharts['dashboard-ui-chart-linear']
const step = win.uiCharts['dashboard-ui-chart-step']
const bezier = win.uiCharts['dashboard-ui-chart-bezier']
const cubic = win.uiCharts['dashboard-ui-chart-cubic']
const mono = win.uiCharts['dashboard-ui-chart-mono']

// Check Data for Interpolation
should(linear.chart.config.data).be.an.Object()
should(linear.chart.config.data.datasets).be.an.Array()

// // Check data point values
should(linear.chart.config.data.datasets[0].data).be.an.Array().and.have.length(4)
should(step.chart.config.data.datasets[0].data).be.an.Array().and.have.length(4)
should(bezier.chart.config.data.datasets[0].data).be.an.Array().and.have.length(4)
should(cubic.chart.config.data.datasets[0].data).be.an.Array().and.have.length(4)
should(mono.chart.config.data.datasets[0].data).be.an.Array().and.have.length(4)

// Check interpolation config for linear
should(linear.chart.config.data.datasets[0].tension).be.equal(0)

// Check interpolation config for step
should(step.chart.config.data.datasets[0].stepped).be.equal(true)

// Check interpolation config for bezier
should(bezier.chart.config.data.datasets[0].tension).be.equal(0.4)

// Check interpolation config for cubic
should(cubic.chart.config.data.datasets[0].cubicInterpolationMode).be.equal('default')
should(cubic.chart.config.data.datasets[0].tension).be.equal(0.4)

// Check interpolation config for monotone
should(mono.chart.config.data.datasets[0].cubicInterpolationMode).be.equal('monotone')
should(mono.chart.config.data.datasets[0].tension).be.equal(0.4)
})
})
})
14 changes: 14 additions & 0 deletions docs/nodes/widgets/ui-chart.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,20 @@ Then, the last piece of the puzzle would be to set the `y` property would be one
- If your data is a simple numerical value, you can leave this blank, and the chart will automatically use the value of `msg.payload`.
- If your data is an object, you can provide the key of the value in your data, e.g. `{"myTime": 1234567890, "myValue": 123}` would set the "Y" property to the type `key` and the value `myValue`.

#### Interpolation Methods for Line Charts

Interpolation defines how the line is drawn between data points on a line chart. In Dashboard, you can choose between several interpolation methods to suit your data visualization needs:
- Linear: Draws a straight line between each data point. Best for continuous datasets with smooth transitions.
- Step: Creates a stepped line between points, where the value jumps abruptly to the next point. This method is ideal for visualizing data that changes in discrete steps, such as states or thresholds.
- Bezier: Produces a smooth curve with slight tension between points, creating a more aesthetically pleasing line. Useful for datasets where a smooth transition is important.
- Cubic: Draws a cubic curve for even more smoothing between data points, providing a rounded visual representation.
- Cubic-Mono: Similar to cubic, but with an additional constraint that ensures the curve maintains a monotonic behavior. This means it avoids overshooting between points, making it more stable.

![Example Line Chart with stepped interpolation](/images/node-examples/ui-chart-line-interpolation-stepped.png "Example Line Chart with stepped interpolation"){data-zoomable}
_Example Line Chart with stepped interpolation_

![Example Line Chart with bezier interpolation](/images/node-examples/ui-chart-line-interpolation-bezier.png "Example Line Chart with bezier interpolation"){data-zoomable}
_Example Line Chart with bezier interpolation_

#### Multiple Lines

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions nodes/widgets/locales/en-US/ui_chart.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ <h3>Properties</h3>
<dt>Label <span class="property-type">string</span></dt>
<dd>Text shown above the rendered chart in the Dashboard.</dd>
<dt>Type <span class="property-type">Line | Bar | Scatter | Pie | Doughnut | Histogram</span></dt>
<dt>Interpolation <span class="property-type">Linear | Step | Bezier | Cubic | Cubic Mono</span></dt>
<ul>
<li> <b>linear</b> : Creates straight lines between data points, producing a standard linear chart.</li>
<li> <b>step</b> : Creates step-like transitions between points, useful for representing changes that occur in stages.</li>
<li> <b>bezier</b> : Draws a smooth, curved line with subtle tension, providing a flowing transition between points.</li>
<li> <b>cubic</b> : Draw curved cubic line chart with a tension.</li>
<li> <b>cubic-mono</b> : Similar to cubic, but ensures monotonic behavior.</li>
</ul>
<dd>Choose the type of graph that you wish to render data with. Note
that different data structures are accepted for different chart types.</dd>
<dt>Show Legend <span class="property-type">boolean</span></dt>
Expand Down
8 changes: 7 additions & 1 deletion nodes/widgets/locales/en-US/ui_chart.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,13 @@
"setGridColorDefault": "Use ChartJs Default Grid Colors" ,
"stackSeries": "Group By",
"stackSeriesTrue": "Stacks",
"stackSeriesFalse": "Side-by-Side"
"stackSeriesFalse": "Side-by-Side",
"interpolation": "Interpolation",
"linearInterpolation": "Linear",
"stepInterpolation": "Step",
"bezierInterpolation": "Bezier",
"cubicInterpolation": "Cubic",
"cubicMonoInterpolation": "Cubic-Mono"
},
"notifications": {
"clearSuccess": "Chart data cleared successfully",
Expand Down
25 changes: 24 additions & 1 deletion nodes/widgets/ui_chart.html
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@
}
},
height: { value: 8 },
className: { value: '' }
className: { value: '' },
interpolation: { value: 'linear' }
},
inputs: 1,
outputs: 1,
Expand Down Expand Up @@ -313,6 +314,9 @@
$('#node-input-chartType').on('change', (evt) => {
const value = $(evt.target).val()

const interpolationWrapper = $('#interpolation-wrapper')
interpolationWrapper.hide()

if (value === 'line' || value === 'scatter') {
// for line and scatter
// types - time, linear
Expand All @@ -322,6 +326,9 @@
// show x-axis limit options & points sizing
$('#x-axis-show').show()
$('#point-radius-show').show()
if (value === 'line') {
interpolationWrapper.show()
}
} else if (value === 'pie' || value === 'doughnut') {
// for pie/doughnut
// types - radial
Expand Down Expand Up @@ -428,6 +435,12 @@
if (this.gridColorDefault || this.gridColorDefault === undefined) {
$('#node-chart-grid-color').hide()
}

if (this.interpolation) {
$('#node-input-interpolation').val(this.interpolation)
} else {
$('#node-input-interpolation').val('linear')
}
},
oneditsave: function () {
// Stack By - convert from string values to bool
Expand Down Expand Up @@ -517,6 +530,16 @@ <h4>Chart Configuration</h4>
<input type="checkbox" checked id="node-input-showLegend" style="display: inline-block; width: auto; margin: 0px 0px 0px 4px;">
</div>
</div>
<div id="interpolation-wrapper" class="form-row">
<label for="node-input-interpolation" data-i18n="ui-chart.label.interpolation"></label></label>
<select id="node-input-interpolation">
<option value="linear" data-i18n="ui-chart.label.linearInterpolation"></option>
<option value="step" data-i18n="ui-chart.label.stepInterpolation"></option>
<option value="bezier" data-i18n="ui-chart.label.bezierInterpolation"></option>
<option value="cubic" data-i18n="ui-chart.label.cubicInterpolation"></option>
<option value="cubicMono" data-i18n="ui-chart.label.cubicMonoInterpolation"></option>
</select>
</div>
<div class="form-row" id="config-stackSeries">
<!-- do not use node-input here to prevent NR automatic binding -->
<label for="chart-input-stackSeries"><i class="fa fa-bookmark"></i> <span data-i18n="ui-chart.label.stackSeries"></span></label>
Expand Down
4 changes: 4 additions & 0 deletions nodes/widgets/ui_chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ module.exports = function (RED) {
config.xAxisProperty = config.xAxisProperty || ''
config.yAxisProperty = config.yAxisProperty || ''

if (!config.interporlation || typeof config.interporlation === 'undefined') {
config.interporlation = 'linear'
}

const evts = {
// beforeSend will run before messages are sent client-side, as well as before sending on within Node-RED
// here, we use it to pre-process chart data to format it ready for plotting
Expand Down
52 changes: 52 additions & 0 deletions ui/src/widgets/ui-chart/UIChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export default {
radialChart () {
// radial charts have no placeholder in ChartJS - we need to add one
return this.props.xAxisType === 'radial'
},
interpolation () {
return this.props.interpolation
}
},
watch: {
Expand All @@ -72,6 +75,10 @@ export default {
'props.xAxisFormatType': function (value) {
this.chart.options.scales.x.time.displayFormats = this.getXDisplayFormats(value)
this.update(false)
},
interpolation (value) {
this.setInterpolation(value)
this.update(false)
}
},
created () {
Expand Down Expand Up @@ -507,6 +514,9 @@ export default {
}
}
}
if (this.chartType === 'line') {
this.setInterpolation(this.interpolation)
}
},
limitDataSize () {
let cutoff = null
Expand Down Expand Up @@ -643,6 +653,48 @@ export default {
} else {
this.chart.data.datasets[sIndex] = series
}
},
setInterpolation (interpolationType) {
// Updated chart configs for interpolation as per the new chart.js version
// https://www.chartjs.org/docs/latest/samples/line/interpolation.html
const getInterpolation = (type) => {
switch (type) {
case 'cubic': {
return {
cubicInterpolationMode: 'default',
tension: 0.4
}
}
case 'cubicMono': {
return {
cubicInterpolationMode: 'monotone',
tension: 0.4
}
}
case 'linear': {
return {
tension: 0
}
}
case 'bezier': {
return {
tension: 0.4
}
}
case 'step': {
return {
stepped: true
}
}
}
}
const interpolation = getInterpolation(interpolationType)
this.chart.data.datasets = this.chart.data.datasets.map((d) => {
return {
...d,
...interpolation
}
})
}
}
}
Expand Down

0 comments on commit 465f328

Please sign in to comment.