Musson Industrial’s Embr-Charts Module

Awesome :tada:

If you’ve got high density charts, I’d recommend disabling the drawing of points, it’ll speed things up by quite a bit.

3 Likes

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.

view.json
{
  "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"
  }
}
1 Like

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"
  }
}
1 Like

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?

1 Like

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.

1 Like

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?

1 Like

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.

The Perspective Part

  1. Because your bindings are not persistent, the initial render of the chart will occur with the default schema.
  2. Once the bindings resolve, the chart is updated with your options and series information.
    • If you try this with the Legacy component it will totally crash
    • The new component has "reasonable" defaults that allow the chart to attempt to render with undefined properties.

The Embr Charts Part

  1. The Embr Charts ApexCharts component property schema specifies the default for a new series.data item to be { x: 0, y: 0 }.
    • Undefined x and y values can cause ApexCharts to freak out during the render stage.
    • With these defaults, the chart doesn't crash when a user clicks the + button to add a new item to the array.
  2. Because of this, the series property was defaulting to an array of 1 series with a single data point of { x: 0, y: 0 }.
    • This is weird, but was hard to notice without specifically looking for it.

The ApexCharts Part

  1. Rendering a series containing { x: 0, y: 0 } seems to break the Radar chart's X-Axis.
  2. The chart's 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).

The Fix

I just need to specify that the default series property should be an empty array :man_shrugging:.

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)

4 Likes