[BUG?] Perspective Power Chart Bug - Embr-Chart as a Replacement?

v8.1.42

Not sure what's going on here, but for some reason i've got major gridlines at 4 days between the first lines and then 5 days for the gridlines thereafter... Really hanging out for the overhaul of this whole component

Edit:
chart.js which @bmusson packaged up in his (very kindly) free Embr-Charts module comes up later on in this topic as a potential replacement for the PowerChart. Link above to download it if you're curious and following along on the journey.

I'm guessing that's a rounding artifact.

(I just don't use that component.)

Yeah.. Still a bug though. Times should be standardised, not just random equal divisions of what's shown :roll_eyes:
There's no other component that gives the ability to scroll back in time though, so while the component is very average, it's all we have

My tag report utility project on the exchange has a time span entry nestable view that takes the work out of producing proper time intervals.

Without the ability to use things like the x trace though, we're hamstrung. I might still check out your tool though.

I've said it before, but I try not to show prospective customers the charting tools in perspective, as they're more likely to dissuade them from choosing ignition

My tool will automatically use an Apex Chart instead of the native TS Chart if the module is installed. (Highly recommended.) Both charts show an XTrace wherever the mouse goes.

The project can be used stand-alone for testing in any gateway that has history. (Though without any security. You have to add that yourself.)

1 Like

Apex charts are great for showing dashboard charts, but they just don't offer the user interaction functionality, like zoom in out, fast traversal of the timeline with the mouse, fast ability to change the y ranges with the mouse, etc. The things I've shown demonstrated in Citect SCADA I my other usability post.

However, what about @bmusson 's chart.js library which uses an canvas to render the chart. It's meant to be far more efficient and quick to render. Again though, the user interaction I don't think is provided by the underlying libraries as their intended usage is more for dashboarding :pensive:

1 Like

What kind of user interaction are you talking about? I may be able to provide examples.

The functionality here:

The buttons themselves like setting span, start/end dates, go to now, track with time, etc. should all be relatively easy to do in scripting, but the user interaction with the chart itself will need JS to implement which I don't have experience with (yet?). If they can be done though, we could eliminate using the Power Chart altogether which would be exciting!

The other things not specifically mentioned in that post, but that are visible in the screencapture, are:

  • the ability to pan the time window back and forth using the mouse to click/drag to swipe through time. And it'd be nice to be able to do this while still in the "x trace" mode.
    edit: it looks like your chart does this already, however I'm not sure if the chart data is updating in realtime or if it's just static, but historical trends need the ability to drag back and forth throughout time, outside of the time window that's currently shown.
  • display the full datetimes for the major gridlines
  • zoom window to allow zooming in on a dragged window

For the embr charts, you the programmer would have to configure when its updating. You could load the initially selected date range data, then when user starts panning the window, load the preceding data in chunks. (1 day at a time?)

I'm thinking something similar to the one example where 500,000 points were loaded in 10 chunks, but in a more on demand fashion. Also, I know performance isn't as much of an issue but you might need to unload chunks of data depending on how far the user is panning.

1 Like

One of my gripes with the Power Chart is that all data in the time window updates every time the time changes, even slightly, so data points are interpolated each time and end up with different values leading to points jumping up and down each time the time window shifts. Obviously this is super dodgy to an operator trying to look at a trend... e.g.

Implementing this ourselves we could eliminate this by keeping the data there when time updates. The issue might be with how much we can keep

Chunk data by days and then remove data the farthest away from the active window? Or maybe wait a timeout period before unloading data at the far side?

Maybe keep the most recent day and any live values added since the chart was started, and drop everything between then and the active window minus a buffer.

I'm not sure of the point counts you would be working with.

My tag report utility normalizes the endpoints to the sample granularity, ensuring uniform inputs to the de-interpolation at each step.

Currently possible using the options.plugins.zoom.pan.onPan/onZoom callback functions. For an example, this will update a custom property with the chart's current bounds as the user pans and zooms. You can use this to load new data for the window (or load data for expected windows, as mentioned above).

// options.plugins.zoom.pan.onPan
(context) => {
  const bounds = context.chart.getZoomedScaleBounds()
  this.store.custom.write('bounds', bounds)
}

There's lots of room for customization here, see this page:

Not only can you specify the display format, but you can specify how date rounding occurs and you have control of display format for specific zoom levels using the options.scales[key].time.displayFormats property.

This is a standard function, set options.plugins.zoom.zoom to something like:

"zoom": {
  "mode": "xy", // allow zooming for both the X and the Y axes
  "drag": {
    "enabled": true,
    "modifierKey": "ctrl" // Control key must be held for dragging to start zooming
  }
}

PS: The bounds property does not update in this example because I only added the script to the onPan listener. It should have also been included in the onZoom listener as well.

This example uses a double-click to reset the zoom, by assigning a double-click listener:

// events.beforeRender
(context) => {
  context.canvas.ondblclick = () => {
    context.resetZoom()
  }
}

It's worth noting that this kind of interaction does not involve the Gateway. There's no client -> server -> client ping-pong happening: the client double clicks on the chart and the zoom is immediately reset.

I'm in the middle of a non-Perspective project at the moment, so it'll be a couple weeks before I can put together a sharable example of panning and zooming between real-time and historical data. However if you try it for yourself and run into issues I'd be more than happy to give input.

4 Likes

I wish I could super like your post, but that all looks great! Keen to get looking into this, hopefully I can find some time soon.

Is it possible to limit the panning to just the timeline/x axis rather than both x and y?
Edit: I think Claude answered my question: yes

options: {
    plugins: {
      zoom: {
        pan: {
          enabled: true,
          mode: 'x',  // This restricts panning to x-axis only
          modifierKey: 'ctrl',  // Optional: require ctrl key to be pressed for panning
        }
      }
    }
  }

And if you wanted to scale/modify the x and y axis ranges shown by clicking and dragging on the x/y axes, would that be possible? e.g. click on the y axes around the 100 mark and drag down to the 0 and the y axis range changes from 0-100 to 0-200 as you drag down? same for time axis? This feature was always useful with Citect's process analyst for easily changing the span of the time window shown

1 Like

Yup, you've got it.

I'd say possible, but advanced.

  1. Detect a click on the axis.
    • From a click event, get the mouse position.
    • Compare the mouse position to the axis bounds (available in chart.scales.y through its left and right properties).
  2. Once a click is detected, add both a mouse-move and a mouse-up listener.
    • When the mouse is moved, update the scale's min and max values based on the movement delta (apply whatever scaling factor you want there).
    • When the mouse is released, uninstall all listeners.

This would be a perfect case for a custom plugin, there's tons of different places you can hook into. For this case afterEvent is probably the most appropriate.

Okay, nerd sniped. Pretty untested, no idea what happens with negative scales.

/// plugins[0].afterEvent
(chart, event) => {
    if (event.event.type !== 'mousedown') return

    const { x: clickX, y: clickY } = event.event
    const { y: scale } = chart.scales

    const onScale = clickX < scale.right && clickY > scale.left
    console.log(onScale)
    if (!onScale) return

    let isDragging = true
    const { min: startMin, max: startMax, height: scaleHeight } = scale

    const onMouseMove = (moveEvent) => {
        if (!isDragging) return

        console.log('onMouseMove')
        const deltaY = moveEvent.clientY - clickY
        const percentChange = (deltaY / scaleHeight) * (startMax - startMin)
        const delta = (percentChange * (startMax - startMin)) / 100

        chart.options.scales.y.min = startMin
        chart.options.scales.y.max = startMax + delta
        chart.update()
    }

    const onMouseUp = () => {
        console.log('onMouseUp')
        isDragging = false
        document.removeEventListener('mousemove', onMouseMove)
        document.removeEventListener('mouseup', onMouseUp)
    }

    document.addEventListener('mousemove', onMouseMove)
    document.addEventListener('mouseup', onMouseUp)

    console.log(chart, event)
}

1 Like

Legend, that's very cool! :heart_eyes: Looking very promising as a replacement component. Also looking more likely that I need to learn javascript...

1 Like

IA's done a great job with their JavaScript-free sandbox, but ultimately Perspective is a web product and the web (unfortunately) runs on JavaScript :sunglasses:

1 Like

Just a heads up, when using an axis of type timeseries on chart.js, panning modifies the tick timeslice when reaching the upper or lower x bounds of the data in the chart. It seems to 'stretch' to keep the entire chart filled horizontally. If there is a setting to disable this behavior I have not found it yet.

It's probably related to the stretching behavior but performance seems worse on timeseries scale vs time or linear

Changing the axis type to time allows you to pan beyond the data in the chart normally.