XYChart Renders Before dataSources Bindings Are Complete

Hi, I'm working on creating a timeline that shows when certain production equipment has been running, running slow, or down. I'm using an XYChart to create something similar to the Gantt Chart example in the manual. The bindings are pulling data from a SQL database and take a second or two to update whenever the start/end times for the timeline are changed.

Here's the problem: it appears that the chart renders before the bindings have been updated, so the chart is blank. If I toggle a random property (e.g. props.legend.enabled), the timeline then displays correctly.

Is there a way to fix this?

Here's a screenshot showing the dataSources property has data, but the cart is blank.

Can you show what one of your bindings look like in the dataSources?

It sounds like you're not getting a refresh trigger on your bindings, which usually points to a binding that is a 'one-shot' and not properly bound to a property that changes or a time interval.

@kyler.kamyszek, here's what the binding looks like. It's just a standard Named Query binding.

I found something interesting - the dataset returned by the Named Query binding changes sizes multiple times while it's updating. Is that normal?

Here's a video:

You have two warning on your component in that video.

Could that be causing the refresh issue?

Are you seeing the same results in runtime? I also noticed you're in read-only gateway mode on the designer. If you have a binding to a tag upstream, that could cause an issue. Does switching to read/write mode change anything?

Hi Kyler,

The error is shown in the screenshot below. The date range is expecting a string instead of an integer, but it only works with an integer in milliseconds. Using a Date or a string of the date (i.e. toString(Date)) doesn't work. Using a string of the milliseconds doesn't work either.
image

There's a couple forum posts about this.

I don't think this would cause the refresh issue, but maybe I'm wrong?

I do see the same results in runtime, but it's not as obvious what's causing the issue because you can't see the properties update in real time.

Same as runtime, Read/Write mode in the Designer doesn't work either.

What's the message handler on the chart doing/do you have any other scripts in the user entry container?

The binding will only evaluate once per input parameter change, so something else is likely affecting things.

Here's a video of a stripped down version of the View. There are no message handlers or scripts.
The bindings:

  • For the XYChart, props.dataSources.Down, Slow, and Run are Named Query bindings.
  • xAxes[0].date.range.min and max are Property Bindings to view.custom.startDate and endDate. A transform converts the dates to millis.
  • The date picker components have their props.value binding set to Bidirectional to write to view.custom.startDate and endDate

I deleted my XY Chart and rebuilt it (aka copy/paste the bindings from the old XY Chart to the new one and set all the props again), and now it renders properly. I originally copied it from a View I downloaded from the Exchange (link). I wonder if the user that created it was doing something advanced that wasn't readily apparent on the surface.

I still have an issue with the Query Binding running twice. It's doing this because the scripts triggered by my buttons change both the Start Date and End Date, and the binding refreshes for each date. The binding refreshes back to back even though the start and end dates are written within the same script. Does anyone know how to avoid this?

1 Like

By the nature of the binding it will update whenever one of the expressions (Value column) in the binding update their value - like when a new date is selected in a picker, which then updates the custom view prop.

You could move away from using a binding, and instead write to the data source via scripting:

# some onClick event somewhere, perhaps on a Button named "Refresh"?
parameters = {"workCenter":self.view.custom.machineID, "startTime": self.view.custom.startDate, "endTime": self.view.custom.endDate}
system.db.runNamedQuery("DownSlowRun_Timeline_Run", parameters)
1 Like

Hi cmallonee,

Thanks for the suggestion. That's the route I'm going to take.

It's unfortunate though. When you look at a component's properties that are controlled by bindings, it's super easy to figure out where they get their value. When you're using scripts for everything, it's difficult and therefore hard to maintain and modify as the Ignition project evolves.

Is it really the intention of the developers for the binding to refresh for every single input parameter that changes? I can't be the only person with multiple input parameters. I started out with a simple script on a button to set a couple date properties to be read by bindings using transforms to update my timeline, pie chart, and down reasons table. And now I have scripts for each button that are 50 lines long to feed these components data.

Maybe a way to disable auto-refresh so the binding can be manually refreshed would be a way to fix this?

There is a way to do this, but it suffers from the same issue you already highlighted.

You could easily bind the XY Chart's data to a named Query and parameterize based off of some custom properties. Then, just change the Button script to take the values from the driving inputs (the pickers) and place those values into the custom properties.

This allows for the clarity of a binding, but you still have the mystery of where the custom property values are coming from.

For certain kinds of bindings in Vision, changes to multiple references will coalesce into a single execution placed at the end of the foreground event queue instead of synchronous. (Charting, in particular.) This coalescing behavior makes sense for many applications. It would have been nice to see similar behavior in Perspective.

If I understand what you're saying, that's actually how I had it set up. For example, a button would set the startDate and endDate in the custom properties, and the named XY Chart's Query binding's input parameters read from these custom properties.

In my test setup, I had a button that ran the script in the screenshot below. I used a dictionary in the hopes that the startDate and endDate would be written simultaneously, and the Query binding would only run once.
image

Here, the query pulls from those custom properties:

Unfortunately, the named Query binding still runs twice. And, since the start date is written first, if the user tries to query an hour window from a month ago, the Named Query binding first runs the query for a month long time window and then again on the hour window that was intended to begin with. Performance takes a massive hit.

Ha. Yah. I had it in my head that we were moving away from directly referencing the properties of the components so we controlled when the parameters were modified, but really we just moved the synchronous setting of the values to a different location.

Unfortunately, I don't think you'll be able to make this more performant.

You could theoretically store all of your data in a custom property (using the original Named Query Binding) and only write that data to the Chart on some sort of trigger (like pressing a refresh button), but then you no longer have a binding that is updating the chart.

It is marked solved, but doesn't seem solved.
Here are techniques that might help:

One date range picker, then in your query use datediff() dateadd() to calculate your end range.
Your window would be constant then, and since only one value changes, the query runs once.

Use one date range picker and then one window size picker for your parameter.
It would update when either is changed. Users can only change them so fast.
Will seem more natural.

Or have two date range pickers. The range will update when either is modified.
Users can only modify so fast.

I would agree that this isn't really solved - we just found a workaround. The problem is the Named Query binding runs once for every single input parameter that is updated, and only the developers can fix that. Therefore, we're stuck with a workaround, but the workaround allows the user to choose any date range and only run the queries once.

Your first suggestion is brilliant and would work great if I only wanted to be able to select entire shifts. I want users to also be able to focus on smaller or larger periods of time though.

Your second and third would work, but my queries take about 1000ms +/- 300ms to run, and I have to run three of them (timeline, pie chart, down reasons table). Therefore, it's important they only run once. (This is another topic entirely, but I really wish I queries could return multiple datasets, because the 3 queries are very similar until the end where they either turn the data into timeline data, group it into pie chart data, or group it by down reasons for the down reasons table.)

Edit: I just realized the original problem was the rendering issue, but we thought maybe the double query was causing it.

It was marked solved because that post explained what was causing the original question, before additional questions were asked. As far as the solutions you provided, the OP is currently using two pickers to drive the range but it's causing an undesired refresh/re-query whenever each picker is modified - so the only option you suggested which might help is the singular picker with a defined range.

@Stephen_Clark : Does the range between the dates often vary? Perhaps if you had only one picker which was the "start date" and a dropdown which specified a range you could get fewer refreshes by calculating the range based on the two input components.

Assuming your users most often need a week of data, the previous approaches would have required them to pick one date and then a date one week after. This is causing the undesirable refresh between date selections. But if you have a range defaulted to your most commonly requested range (say, one week), then you could just use one picker, have a property somewhere which calculates the "end date" based on the start date and the range, and then you query could be bound against this singular property.

The Chart would still refresh whenever either the date or range is changed, but if you're defaulting to the most common range then odds of needing to refresh that undesirable second time are reduced.

That's a great point. Generally, users are going to want to see 1 shift's worth of data which is why I have buttons for our 4 most recent shifts. The date pickers are there if they want to drill down to half a shift or maybe see an entire week's worth of data.

These buttons write to startDate and endDate on the view's custom properties. From there, the Named Query bindings would read those properties and update their components. The problem, however, was they ran twice back to back because startDate and endDate were updated simultaneously. It would be really nice if, like @pturmel said, these reference changes coalesced into a single execution.

I've moved all 3 queries (timeline, pie chart, and down reasons table) to a script on the apply button (see screenshot below), so the queries only once. As it is right now, I just have one script on the Apply button; it's not as nice as using a binding, but it's not the end of the world.

1 Like

You could multiplex a single parameter.