Awesome
If you’ve got high density charts, I’d recommend disabling the drawing of points, it’ll speed things up by quite a bit.
Awesome
If you’ve got high density charts, I’d recommend disabling the drawing of points, it’ll speed things up by quite a bit.
Has anyone tried to do a heatmap using EMBR charts? I was thinking I could use matrix type to do it but am having trouble getting the scales.y.type = category to use the label from datasets.data.label to position the boxes.
I'm having some trouble with the Line component. I'm using Embr Charts 3.0.0 and Ignition 8.1.35. When using a line chart it is not updating to show the current props.series, the chart is always displaying the previous props.series data. I can display the props.series in a Text Area component and confirm it is correct and the chart displays the previous data. It does not matter if I bind directly on props.series with a query and transform or if I use a script to populate it. Even calling .refresh() after the script populates props.series or setting the redraw property true has no effect. The chart is always displaying the previous data instead of the current data.
Which component? Chart.js, ApexCharts, or Legacy ApexCharts?
Could you provide a view.json
export that shows the problem?
The new ApexCharts component has a Heat Map example ready to go, and for the Chart.js component I'd recommend this sample page. We don't have access to the helpers
object, so we handle mapping the colors ourselves.
{
"custom": {},
"params": {},
"props": {},
"root": {
"children": [
{
"meta": {
"name": "Chartjs"
},
"position": {
"height": 300,
"width": 505,
"x": 102,
"y": 20
},
"props": {
"data": {
"datasets": [
{
"backgroundColor": "(context) \u003d\u003e {\nconst value \u003d context.dataset.data[context.dataIndex].v;\nconst alpha \u003d (value - 5) / 40;\n\nreturn `rgba(0, 128, 0, ${alpha})`;\n} ",
"borderColor": "(context) \u003d\u003e {\nconst value \u003d context.dataset.data[context.dataIndex].v;\nconst alpha \u003d (value - 5) / 40;\n\nreturn `rgba(0, 100, 0, ${alpha})`;\n}",
"data": [
{
"v": 11,
"x": "A",
"y": "X"
},
{
"v": 12,
"x": "A",
"y": "Y"
},
{
"v": 13,
"x": "A",
"y": "Z"
},
{
"v": 21,
"x": "B",
"y": "X"
},
{
"v": 22,
"x": "B",
"y": "Y"
},
{
"v": 23,
"x": "B",
"y": "Z"
},
{
"v": 31,
"x": "C",
"y": "X"
},
{
"v": 32,
"x": "C",
"y": "Y"
},
{
"v": 33,
"x": "C",
"y": "Z"
}
],
"height": "(context) \u003d\u003e (context.chart.chartArea || {}).height / 3 - 1",
"label": "Matrix",
"width": "(context) \u003d\u003e (context.chart.chartArea || {}).width / 3 - 1"
}
]
},
"options": {
"plugins": {
"legend": {
"display": false
}
},
"scales": {
"x": {
"grid": {
"display": false
},
"labels": [
"A",
"B",
"C"
],
"type": "category"
},
"y": {
"grid": {
"display": false
},
"labels": [
"X",
"Y",
"Z"
],
"offset": true,
"type": "category"
}
}
},
"type": "matrix"
},
"type": "embr.chart.chart-js"
},
{
"meta": {
"name": "ApexCharts"
},
"position": {
"height": 300,
"width": 594,
"x": 65,
"y": 358
},
"props": {
"options": {
"colors": [
"#008FFB"
],
"dataLabels": {
"enabled": false
},
"title": {
"align": "left",
"text": "Heat Map Chart"
}
},
"series": [
{
"data": [
{
"fill": {
"colors": [],
"gradient": {
"colorStops": [],
"gradientToColors": [],
"opacityFrom": [],
"opacityTo": [],
"stops": []
},
"image": {
"src": []
},
"opacity": [],
"pattern": {
"style": []
},
"type": []
},
"x": 0,
"y": 12
},
{
"x": 1,
"y": 16
},
{
"x": 2,
"y": 23
},
{
"x": 3,
"y": 36
},
{
"x": 4,
"y": 15
},
{
"x": 5,
"y": 50
},
{
"x": 6,
"y": 28
},
{
"x": 7,
"y": 42
},
{
"x": 8,
"y": 47
},
{
"x": 9,
"y": 56
},
{
"x": 10,
"y": 63
},
{
"x": 11,
"y": 52
},
{
"x": 12,
"y": 22
},
{
"x": 13,
"y": 37
},
{
"x": 14,
"y": 19
},
{
"x": 15,
"y": 32
},
{
"x": 16,
"y": 25
},
{
"x": 17,
"y": 44
}
],
"name": "Metric1"
},
{
"data": [
{
"x": 0,
"y": 33
},
{
"x": 1,
"y": 45
},
{
"x": 2,
"y": 18
},
{
"x": 3,
"y": 30
},
{
"x": 4,
"y": 41
},
{
"x": 5,
"y": 59
},
{
"x": 6,
"y": 22
},
{
"x": 7,
"y": 13
},
{
"x": 8,
"y": 50
},
{
"x": 9,
"y": 31
},
{
"x": 10,
"y": 27
},
{
"x": 11,
"y": 40
},
{
"x": 12,
"y": 52
},
{
"x": 13,
"y": 29
},
{
"x": 14,
"y": 56
},
{
"x": 15,
"y": 63
},
{
"x": 16,
"y": 19
},
{
"x": 17,
"y": 32
}
],
"name": "Metric2"
},
{
"data": [
{
"x": 0,
"y": 38
},
{
"x": 1,
"y": 50
},
{
"x": 2,
"y": 28
},
{
"x": 3,
"y": 63
},
{
"x": 4,
"y": 22
},
{
"x": 5,
"y": 54
},
{
"x": 6,
"y": 39
},
{
"x": 7,
"y": 51
},
{
"x": 8,
"y": 45
},
{
"x": 9,
"y": 60
},
{
"x": 10,
"y": 30
},
{
"x": 11,
"y": 42
},
{
"x": 12,
"y": 19
},
{
"x": 13,
"y": 57
},
{
"x": 14,
"y": 48
},
{
"x": 15,
"y": 31
},
{
"x": 16,
"y": 55
},
{
"x": 17,
"y": 28
}
],
"name": "Metric3"
},
{
"data": [
{
"x": 0,
"y": 22
},
{
"x": 1,
"y": 27
},
{
"x": 2,
"y": 33
},
{
"x": 3,
"y": 25
},
{
"x": 4,
"y": 40
},
{
"x": 5,
"y": 32
},
{
"x": 6,
"y": 48
},
{
"x": 7,
"y": 45
},
{
"x": 8,
"y": 52
},
{
"x": 9,
"y": 41
},
{
"x": 10,
"y": 37
},
{
"x": 11,
"y": 44
},
{
"x": 12,
"y": 27
},
{
"x": 13,
"y": 34
},
{
"x": 14,
"y": 29
},
{
"x": 15,
"y": 31
},
{
"x": 16,
"y": 46
},
{
"x": 17,
"y": 56
}
],
"name": "Metric4"
},
{
"data": [
{
"x": 0,
"y": 41
},
{
"x": 1,
"y": 39
},
{
"x": 2,
"y": 56
},
{
"x": 3,
"y": 44
},
{
"x": 4,
"y": 59
},
{
"x": 5,
"y": 38
},
{
"x": 6,
"y": 51
},
{
"x": 7,
"y": 43
},
{
"x": 8,
"y": 29
},
{
"x": 9,
"y": 50
},
{
"x": 10,
"y": 64
},
{
"x": 11,
"y": 36
},
{
"x": 12,
"y": 32
},
{
"x": 13,
"y": 40
},
{
"x": 14,
"y": 27
},
{
"x": 15,
"y": 42
},
{
"x": 16,
"y": 28
},
{
"x": 17,
"y": 46
}
],
"name": "Metric5"
},
{
"data": [
{
"x": 0,
"y": 45
},
{
"x": 1,
"y": 47
},
{
"x": 2,
"y": 38
},
{
"x": 3,
"y": 50
},
{
"x": 4,
"y": 62
},
{
"x": 5,
"y": 33
},
{
"x": 6,
"y": 56
},
{
"x": 7,
"y": 34
},
{
"x": 8,
"y": 39
},
{
"x": 9,
"y": 61
},
{
"x": 10,
"y": 42
},
{
"x": 11,
"y": 54
},
{
"x": 12,
"y": 28
},
{
"x": 13,
"y": 46
},
{
"x": 14,
"y": 43
},
{
"x": 15,
"y": 30
},
{
"x": 16,
"y": 41
},
{
"x": 17,
"y": 52
}
],
"name": "Metric6"
},
{
"data": [
{
"x": 0,
"y": 32
},
{
"x": 1,
"y": 36
},
{
"x": 2,
"y": 40
},
{
"x": 3,
"y": 46
},
{
"x": 4,
"y": 29
},
{
"x": 5,
"y": 35
},
{
"x": 6,
"y": 41
},
{
"x": 7,
"y": 54
},
{
"x": 8,
"y": 31
},
{
"x": 9,
"y": 50
},
{
"x": 10,
"y": 38
},
{
"x": 11,
"y": 47
},
{
"x": 12,
"y": 39
},
{
"x": 13,
"y": 42
},
{
"x": 14,
"y": 33
},
{
"x": 15,
"y": 48
},
{
"x": 16,
"y": 46
},
{
"x": 17,
"y": 44
}
],
"name": "Metric7"
},
{
"data": [
{
"x": 0,
"y": 39
},
{
"x": 1,
"y": 45
},
{
"x": 2,
"y": 51
},
{
"x": 3,
"y": 49
},
{
"x": 4,
"y": 37
},
{
"x": 5,
"y": 43
},
{
"x": 6,
"y": 35
},
{
"x": 7,
"y": 56
},
{
"x": 8,
"y": 32
},
{
"x": 9,
"y": 41
},
{
"x": 10,
"y": 48
},
{
"x": 11,
"y": 50
},
{
"x": 12,
"y": 37
},
{
"x": 13,
"y": 45
},
{
"x": 14,
"y": 43
},
{
"x": 15,
"y": 30
},
{
"x": 16,
"y": 51
},
{
"x": 17,
"y": 54
}
],
"name": "Metric8"
},
{
"data": [
{
"x": 0,
"y": 31
},
{
"x": 1,
"y": 44
},
{
"x": 2,
"y": 35
},
{
"x": 3,
"y": 55
},
{
"x": 4,
"y": 48
},
{
"x": 5,
"y": 34
},
{
"x": 6,
"y": 46
},
{
"x": 7,
"y": 52
},
{
"x": 8,
"y": 41
},
{
"x": 9,
"y": 36
},
{
"x": 10,
"y": 59
},
{
"x": 11,
"y": 32
},
{
"x": 12,
"y": 45
},
{
"x": 13,
"y": 50
},
{
"x": 14,
"y": 28
},
{
"x": 15,
"y": 43
},
{
"x": 16,
"y": 41
},
{
"x": 17,
"y": 39
}
],
"name": "Metric9"
}
],
"type": "heatmap"
},
"type": "embr.chart.apex-charts"
}
],
"meta": {
"name": "root"
},
"type": "ia.container.coord"
}
}
It is the ApexChart. This view demonstrates the issue I'm seeing.
{
"custom": {},
"params": {},
"props": {},
"root": {
"children": [
{
"events": {
"component": {
"onActionPerformed": {
"config": {
"script": "\timport math\n\n\tselection \u003d self.props.value\n\n\tdata_points \u003d []\n\tnow \u003d system.date.now()\n\tfor i in range(100):\n\t\ttimestamp \u003d system.date.addSeconds(now, i)\n\t\tif selection \u003d\u003d \u0027sine\u0027:\n\t\t\tvalue \u003d math.sin(i * 0.2) * 50 + 50\n\t\telse:\n\t\t\tvalue \u003d math.cos(i * 0.2) * 50 + 50\n\t\tdata_points.append({\u0027x\u0027: timestamp, \u0027y\u0027: value})\n\n\tseries_label \u003d \u0027Sine Wave\u0027 if selection \u003d\u003d \u0027sine\u0027 else \u0027Cosine Wave\u0027\n\ttransformed_data \u003d [\n\t {\n\t \u0027name\u0027: series_label,\n\t \u0027data\u0027: data_points\n\t }\n\t]\n\n\tchart \u003d self.getSibling(\u0027ApexLineChart\u0027)\n\tchart.props.series \u003d transformed_data"
},
"scope": "G",
"type": "script"
}
}
},
"meta": {
"name": "Dropdown"
},
"position": {
"height": 36,
"width": 256,
"x": 48,
"y": 32
},
"props": {
"options": [
{
"label": "Sine Wave",
"value": "sine"
},
{
"label": "Cosine Wave",
"value": "cosine"
}
],
"value": "sine"
},
"type": "ia.input.dropdown"
},
{
"meta": {
"name": "ApexLineChart"
},
"position": {
"height": 300,
"width": 500,
"x": 48,
"y": 96
},
"props": {
"options": {
"chart": {
"zoom": {
"enabled": false
}
},
"xaxis": {
"type": "datetime"
}
}
},
"type": "embr.chart.apex-charts"
},
{
"meta": {
"name": "TextArea"
},
"position": {
"height": 299,
"width": 614,
"x": 48,
"y": 463
},
"propConfig": {
"props.text": {
"binding": {
"config": {
"path": "../ApexLineChart.props.series"
},
"type": "property"
}
}
},
"type": "ia.input.text-area"
}
],
"meta": {
"name": "root"
},
"type": "ia.container.coord"
}
}
First off just want to see how impressive this project is.
Been using ApexCharts for a long time now but just started converting over to Embr.
Only issue I am having trouble with is,
Is there a way to enable the drop shadows on the bars and lines in chartJS like apex has?
This is perfect, I can reproduce the issue.
Will ~probably~ have a chance to fix it tonight/tomorrow.
It looks like a issue with the memoization of the series
property when the entire object is replaced; changing individual series[0].data
values works, but replacing the whole series
property causes everything to lag by 1 render.
That's exactly what it was, version 3.0.1
includes a fix.
Related Question:
Is there a better way to create my props reducer that will retain object references?
// Existing
getPropsReducer(tree: PropertyTree): ChartProps {
return {
type: tree.readString('type'),
options: tree.read('options', {}),
series: tree.read('series', {}),
redraw: tree.read('redraw', false),
events: tree.read('events', {}),
style: tree.readStyle('style'),
} as never
Whenever anything in the PropertyTree
changes, I'm getting entirely new references for all of my component's properties. This makes React work way harder than it needs to.
For example, in the ApexCharts component, I only want to call updateSeries
if the series
property has changed. But by naively using a useEffect(..., [props.props.series])
, the effect will be called for every render with any PropertyTree
change, including changes that don't touch the series
property.
To mitigate this I'm memoizing using deep comparison:
const series = useDeepCompareMemo(() => {
return props.props.series
}, [props.props.series])
This works, and series
is maintained through other changes in the property tree.
Is it expected that I need to do this? What are first party/other component authors doing?
I'm having trouble with the ApexCharts Radar. The props.options.xaxis.categories do not update on the chart in a web client, even though it works as expected in the designer.
The following view JSON demonstrates the issue.
{
"custom": {},
"params": {},
"props": {},
"root": {
"children": [
{
"events": {
"component": {
"onActionPerformed": {
"config": {
"script": "\tselection \u003d self.props.value\n\tif selection \u003d\u003d \u0027high\u0027:\n\t\tcategories \u003d [\u0027Speed\u0027, \u0027Power\u0027, \u0027Efficiency\u0027, \u0027Uptime\u0027]\n\t\tactual_data \u003d [95, 88, 92, 99]\n\t\ttitle_text \u003d \u0027High Performance Profile\u0027\n\telse:\n\t\tcategories \u003d [\u0027Speed\u0027, \u0027Power\u0027, \u0027Efficiency\u0027, \u0027Uptime\u0027]\n\t\tactual_data \u003d [65, 72, 55, 80]\n\t\ttitle_text \u003d \u0027Low Performance Profile\u0027\n\t\n\ttarget_data \u003d [100] * len(actual_data)\n\tseries_config \u003d [{\u0027name\u0027: \u0027Actual\u0027, \u0027data\u0027: actual_data}, {\u0027name\u0027: \u0027Target\u0027, \u0027data\u0027: target_data}]\n\toptions_config \u003d {\u0027xaxis\u0027: {\u0027categories\u0027: categories}, \u0027yaxis\u0027: {\u0027min\u0027: 0, \u0027max\u0027: 120}, \u0027title\u0027: {\u0027text\u0027: title_text}}\n\tchart \u003d self.getSibling(\u0027ApexRadarChart\u0027)\n\tchart.custom.chartConfig \u003d {\u0027series\u0027: series_config, \u0027options\u0027: options_config}"
},
"scope": "G",
"type": "script"
}
}
},
"meta": {
"name": "Dropdown"
},
"position": {
"height": 36,
"width": 256,
"x": 48,
"y": 32
},
"props": {
"options": [
{
"label": "High Performance Profile",
"value": "high"
},
{
"label": "Low Performance Profile",
"value": "low"
}
],
"value": "low"
},
"type": "ia.input.dropdown"
},
{
"custom": {
"chartConfig": {
"options": {},
"series": []
}
},
"meta": {
"name": "ApexRadarChart"
},
"position": {
"height": 300,
"width": 400,
"x": 48,
"y": 96
},
"propConfig": {
"props.options": {
"binding": {
"config": {
"path": "this.custom.chartConfig.options"
},
"type": "property"
}
},
"props.series": {
"binding": {
"config": {
"path": "this.custom.chartConfig.series"
},
"type": "property"
}
}
},
"props": {
"type": "radar"
},
"type": "embr.chart.apex-charts"
}
],
"meta": {
"name": "root"
},
"type": "ia.container.coord"
}
}
Wow, this was a wild one. I'm going to try and document this as I understand it.
series.data
item to be { x: 0, y: 0 }
.
x
and y
values can cause ApexCharts to freak out during the render stage.+
button to add a new item to the array.series
property was defaulting to an array of 1 series with a single data point of { x: 0, y: 0 }
.
{ x: 0, y: 0 }
seems to break the Radar chart's X-Axis.updateOptions
method refuses to work after rendering { x, y }
points in a Radar chart (even through the series is eventually updated after the binding resolves).I just need to specify that the default series
property should be an empty array .
TLDR: Non-persistent bindings -> default schema is rendered ->line series data != radar series data -> ApexCharts updateOptions
method breaks.
Here's a local build, an official fix should be available next week.
Embr-Charts-3.0.2-RC.modl (2.7 MB)