# Vision Chart (not EasyChart) Min & Max Date

**URL:** <https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606>\
**Category:** Ignition\
**Tags:** vision, ignition81, scripting\
**Created:** [March 3, 2023, 8:10pm UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606 "2023-03-03T20:10:30Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![ricardo.ortolani](https://avatars.discourse-cdn.com/v4/letter/r/c68b51/32.png) [@ricardo.ortolani](https://forum.inductiveautomation.com/u/ricardo.ortolani)\
**Post date:** [March 3, 2023, 8:10pm UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/1 "2023-03-03T20:10:30Z")

</div>

This is in Vision and I have 2 components, a Chart (not easychart) and Date Range.

My dataset range from Jan 1st to March 2nd on the Chart component.

Instead of reloading the data every time I change the date range.

I want to bind the start and end date from the Date Range component to the X-Axis properties Max Date and Min Date, so the Chart would only display the range the user pick.

but primary the data would not need to be call every time the range changes. So I wonder if anyone has dynamically changes Max and Mix from the Chart X-Axis properties using a Date Range component.

thanks,

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [March 3, 2023, 9:08pm UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/2 "2023-03-03T21:08:34Z")

</div>

Query the data once, stash it in a custom property, then use scripting to filter your original dataset down to apply to the actual chart component.

---

<div class="post-metadata">

**Author:** ![ricardo.ortolani](https://avatars.discourse-cdn.com/v4/letter/r/c68b51/32.png) [@ricardo.ortolani](https://forum.inductiveautomation.com/u/ricardo.ortolani)\
**Post date:** [March 3, 2023, 9:18pm UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/3 "2023-03-03T21:18:12Z")

</div>

Hello @paul-griffith  
I am not sure if I follow the process, so add the data into a dataset custom property, than write a script to parse the data inside and add to the chart dataset, so it will display?

I am trying to see if this approach would be fast if someone is sliding the date range, which was one of the reasons I was trying to change the chart properties max and min, since the data is already load.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [March 3, 2023, 9:31pm UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/4 "2023-03-03T21:31:10Z")

</div>

If the dataset is ordered by timestamp, it's quite efficient to 'filter' it by timestamp. You could use `system.dataset.deleteRows`, or Phil's Simulation Aids' `view` function, or convert to a PyDataset and slice; there's lots of possible approaches. The key thing is that by having the data in memory, you're avoiding the significantly slower network transfer of data. Slicing a dataset in-memory is much, much faster than issuing a new query to the DB.

---

<div class="post-metadata">

**Author:** ![ricardo.ortolani](https://avatars.discourse-cdn.com/v4/letter/r/c68b51/32.png) [@ricardo.ortolani](https://forum.inductiveautomation.com/u/ricardo.ortolani)\
**Post date:** [March 3, 2023, 10:03pm UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/5 "2023-03-03T22:03:41Z")

</div>

@paul-griffith the dataset will be order by timestamp and I don't want to delete, since the user will be able to slide the date range back in time or forward or increase to see all the data.

which one of the reason I start to explore the Max and Min properties, since it can change the view range on the chart and the dataset doesn't change.

I will try to figure out how to get to that property and see if I can make that work.

thanks for the help so far.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [March 3, 2023, 10:11pm UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/6 "2023-03-03T22:11:49Z")

</div>

I'm suggesting you don't modify the _chart_ properties at all.

Let's say you have a containing group/template/container/whatever. Within it you have your Chart component, and your Date Range.  
Have a custom property on the _group_ that runs your tag history query with the hardcoded start and end date. Cache the _entire_ dataset in a custom property.  
Then on your date range, use a script so that whenever the date range is changed, your 'filtering' script will kick off. This will _copy_ all the rows from the cached dataset into a _new_ dataset on the chart component. The original dataset is never changed; but a _new_ dataset is created and applied directly to the chart.

Changing the min/max properties of the component is probably _possible_, but requires diving into JFreeChart's API.

---

<div class="post-metadata">

**Author:** ![justinedwards.jle](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/justinedwards.jle/32/37854_2.png) [@justinedwards.jle](https://forum.inductiveautomation.com/u/justinedwards.jle)\
**Post date:** [March 4, 2023, 6:38am UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/7 "2023-03-04T06:38:59Z")

</div>

If this is really what you want to do, here is how:

Add the following script to your Date Range component's propertyChange event handler:

```auto
from org.jfree.chart.axis import DateAxis
chartComponent = event.source.parent.getComponent('Chart')#Change this to the actual path of your chart
plot = chartComponent.chart.plot
dateAxis = DateAxis()
if event.propertyName == 'selectedDateRange':
	minDate = event.source.startDate
	maxDate = event.source.endDate
	dateAxis.setRange(minDate.getTime(), maxDate.getTime())
	plot.setDomainAxis(dateAxis)

```

---

<div class="post-metadata">

**Author:** ![ricardo.ortolani](https://avatars.discourse-cdn.com/v4/letter/r/c68b51/32.png) [@ricardo.ortolani](https://forum.inductiveautomation.com/u/ricardo.ortolani)\
**Post date:** [March 7, 2023, 7:11pm UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/8 "2023-03-07T19:11:50Z")

</div>

@justinedwards.jle thanks for the info, I try that and it work great.

@paul-griffith I originally want go with the option @justinedwards.jle recommend, since we have several thousands of data points, possible hundred of thousands. and have to parse and copy and paste every time, I believe it will not be efficient.

But I still going to try both scenarios and add timers to see which one is faster.

thanks again for all the help, also have a small second question if any of you might know.

I set Chart component mode to X-Trace, and I want change the background color on the label, by any chance any of you guys might know are that property is?

below is a picture which the graph, the way it look is hard to ready the labels  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/3/b/3bdf834b751de821c54dd3fa09e60efe1bed7a62.png)

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [March 7, 2023, 8:28pm UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/9 "2023-03-07T20:28:53Z")

</div>

> [@ricardo.ortolani](#):
>
> and I want change the background color on the label

Pretty sure you're stuck with that. Among other things, that is a behavior that I fixed when I re-implemented X-Trace in [my NoteChart module](https://inductiveautomation.com/moduleshowcase/module/automation-professionals-llc-notechart).

---

<div class="post-metadata">

**Author:** ![justinedwards.jle](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/justinedwards.jle/32/37854_2.png) [@justinedwards.jle](https://forum.inductiveautomation.com/u/justinedwards.jle)\
**Post date:** [March 8, 2023, 1:48am UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/10 "2023-03-08T01:48:22Z")

</div>

> [@ricardo.ortolani](#):
>
> hard to read

A simple option would be to experiment with annotation fonts.

**Example:** _#using the configureChart extension function_

```auto
#def configureChart(self, chart):
	from java.awt import Font
	plot = chart.plot
	font = Font('Verdana Bold', Font.BOLD, 18)
	plot.setAnnotationFont(font)

```

**Result:**  
 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/c/2/c20c72f58dbb622dac70345fcfc4b23513352317.png)

---

<div class="post-metadata">

**Author:** ![justinedwards.jle](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/justinedwards.jle/32/37854_2.png) [@justinedwards.jle](https://forum.inductiveautomation.com/u/justinedwards.jle)\
**Post date:** [March 8, 2023, 6:07am UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/11 "2023-03-08T06:07:25Z")

</div>

A problem I just worked on gave me the idea of building my own annotation generator to produce the effect you are looking for. **EDIT: [a slightly more developed version was posted here](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/12) that prevents label overlap**

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/7/e/7ef329bcabcc3fafb827316c8795dd955a84a36a.png)

![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/b/f/bf9974b8e378af27ef73ea155ba953b3eed97ddf.png)

To get this effect, add the following script to the chart's getXTraceLabel extension function:

> **getXTraceLabel extension function**
>
> ```auto
> #def getXTraceLabel(self, chart, penName, yValue):
> return ''#This prevents the stock annotations from being visible
> 
> ```

Then, add the following script to the chart's propertyChange event handler:

> **propertyChange event handler**
>
> ```auto
> if event.propertyName == 'selectedXValue':
> from org.jfree.chart.annotations import XYTextAnnotation
> from java.awt.font import FontRenderContext, TextLayout
> from java.awt import Color, Font
> data = event.source.Data #or whatever your custom data property is
> plot = event.source.chart.plot
> domainAxis = plot.domainAxis
> for annotation in plot.getAnnotations():
> if isinstance(annotation, XYTextAnnotation):
> plot.removeAnnotation(annotation)
> if event.source.chart.plot.annotationMode == 2: #This line ensures that the labels will only be added in XTrace Mode
> domainLocation = plot.domainCrosshairValue
> backgroundFont = Font(Font.MONOSPACED, Font.PLAIN, 18)
> annotationFont = Font(Font.MONOSPACED, Font.BOLD, 18)
> for row in range(data.rowCount):
> if data.getValueAt(row, 0).getTime() == domainLocation:
> for column in range(1, data.columnCount):
> penName = data.getColumnName(column)
> yValue = data.getValueAt(row, column)
> text = penName + ': ' +str(yValue)
> background = XYTextAnnotation(u'█' * len(text), domainLocation, yValue)
> background.setPaint(Color.WHITE)
> background.setFont(backgroundFont)
> plot.addAnnotation(background)
> annotation = XYTextAnnotation(text, domainLocation, yValue)
> textColor = plot.renderer.getSeriesPaint(column-1)
> annotation.setPaint(textColor)
> annotation.setFont(annotationFont)
> plot.addAnnotation(annotation)
> #The following hocus pocus is needed to keep the label within the bounds of the chart when the selected x position is near the edge:
> domainWidth = domainAxis.upperBound - domainAxis.lowerBound
> midPoint = domainAxis.lowerBound + (domainWidth * .5)
> chartWidth = event.source.width
> layout = TextLayout(text, annotationFont, FontRenderContext(None, False, False))
> pixelOffset = layout.getBounds().width *.5
> offsetRatio = pixelOffset/float(chartWidth)
> domainOffset = domainWidth * offsetRatio
> if domainLocation > midPoint:
> annotation.setX(domainLocation - domainOffset)
> background.setX(domainLocation - domainOffset)
> else:
> annotation.setX(domainLocation + domainOffset)
> background.setX(domainLocation + domainOffset)
> 
> ```

The chart's XYTextAnnotations do not have a background paint property, so this script simply adds two annotations for every datapoint: one with a solid row of rectangle characters to simulate a background color, and the other with the actual desired label text.

---

<div class="post-metadata">

**Author:** ![justinedwards.jle](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/justinedwards.jle/32/37854_2.png) [@justinedwards.jle](https://forum.inductiveautomation.com/u/justinedwards.jle)\
**Post date:** [March 9, 2023, 10:01am UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/12 "2023-03-09T10:01:03Z")

</div>

The above script produced a result that looked good, but after playing around with it, I didn't like how the labels overlapped making some of them unreadable, so for fun, I developed the script further. It now adjusts the label positions slightly if they overlap.

**Here is the result:**

[https://us1.discourse-cdn.com/inductiveautomation/original/3X/1/9/19d5722627d2bee26c32f8f7fca038d5d9125a9f.mp4](https://us1.discourse-cdn.com/inductiveautomation/original/3X/1/9/19d5722627d2bee26c32f8f7fca038d5d9125a9f.mp4)

**Here are the updated scripts:** (In case anybody wants to use them or develop them further)

> **propertyChange event handler**
>
> ```auto
> if event.propertyName == 'selectedXValue':
> from org.jfree.chart.annotations import XYTextAnnotation
> from java.awt.font import FontRenderContext, TextLayout
> from java.awt import Color, Font
> plot = event.source.chart.plot
> data = event.source.Data #or whatever the custom data property is
> backgroundFont = Font(Font.MONOSPACED, Font.PLAIN, 18)
> annotationFont = Font(Font.MONOSPACED, Font.BOLD, 18)
> def getYValues(layout, yMidpoint):
> rangeAxis = plot.rangeAxis
> rangeHeight = float(rangeAxis.upperBound - rangeAxis.lowerBound)
> chartHeight = float(event.source.chartRenderingInfo.chartArea.height)
> labelHeight = float(layout.bounds.height)
> labelRange = (labelHeight/chartHeight) * rangeHeight
> y1 = float(yMidpoint) - (labelRange * .5)
> y2 = float(yMidpoint) + (labelRange * .5)
> return [y1, y2]
> def getAnnotationX(layout, domainLocation): #Ensures labels stay within the bounds of the chart even when crosshair is near the edge
> domainAxis = plot.domainAxis
> domainWidth = domainAxis.upperBound - domainAxis.lowerBound
> midPoint = domainAxis.lowerBound + (domainWidth * .5)
> chartWidth = event.source.chartRenderingInfo.chartArea.width
> pixelOffset = layout.bounds.width *.5
> offsetRatio = pixelOffset/float(chartWidth)
> domainOffset = domainWidth * offsetRatio
> if domainLocation > midPoint:
> return (domainLocation - domainOffset)
> else:
> return (domainLocation + domainOffset)
> def getAnnotationText(row, column):
> penName = data.getColumnName(column)
> penValue = data.getValueAt(row, column)
> text = penName + ': ' +str(penValue)
> return text
> def adjustAnnotations(annotations):
> adjustedHeaders = ['column', 'text', 'x', 'y1', 'y2']
> adjustedData = []
> sortedData = system.dataset.sort(annotations, 3)
> lowerBound = plot.rangeAxis.lowerBound
> upperBound = plot.rangeAxis.upperBound
> previousY2 = None
> previousY1 = None
> for row in range(sortedData.rowCount):
> column = sortedData.getValueAt(row, 0)
> text = sortedData.getValueAt(row, 1)
> x = sortedData.getValueAt(row, 2)
> y1 = sortedData.getValueAt(row, 3)
> y2 = sortedData.getValueAt(row, 4)
> if y2 > lowerBound and y1 < upperBound:
> if row == 0:
> labelHeight = y2 - y1
> lowerLabelY1 = float(lowerBound)
> lowerLabelY2 = lowerLabelY1 + (2 * labelHeight)
> if y1 < lowerLabelY2:
> yOffset = float(lowerLabelY2 - y1)
> offsetY1 = y1 + yOffset
> offsetY2 = y2 + yOffset
> adjustedData.append([column, text, x, offsetY1, offsetY2])
> previousY2 = offsetY2
> else:
> adjustedData.append([column, text, x, y1, y2])
> previousY2 = y2
> else:
> if y1 < previousY2:
> yOffset = float(previousY2 - y1)
> offsetY1 = y1 + yOffset
> offsetY2 = y2 + yOffset
> adjustedData.append([column, text, x, offsetY1, offsetY2])
> previousY2 = offsetY2
> else:
> adjustedData.append([column, text, x, y1, y2])
> previousY2 = y2
> adjustedDataset = system.dataset.toDataSet(adjustedHeaders, adjustedData)
> finalSortedData = system.dataset.sort(adjustedDataset, 3, False)
> finalizedData = []
> for row in range(finalSortedData.rowCount):
> column = finalSortedData.getValueAt(row, 0)
> text = finalSortedData.getValueAt(row, 1)
> x = finalSortedData.getValueAt(row, 2)
> y1 = finalSortedData.getValueAt(row, 3)
> y2 = finalSortedData.getValueAt(row, 4)
> if row == 0:
> if y2 > upperBound:
> yOffset = float(y2 - upperBound)
> offsetY1 = y1 - yOffset
> offsetY2 = y2 - yOffset
> finalizedData.append([column, text, x, offsetY1, offsetY2])
> previousY1 = offsetY1
> else:
> finalizedData.append([column, text, x, y1, y2])
> previousY1 = y1
> else:
> if y2 > previousY1:
> yOffset = float(y2 - previousY1)
> offsetY1 = y1 - yOffset
> offsetY2 = y2 - yOffset
> finalizedData.append([column, text, x, offsetY1, offsetY2])
> previousY1 = offsetY1
> else:
> finalizedData.append([column, text, x, y1, y2])
> previousY1 = y1
> return system.dataset.toDataSet(adjustedHeaders, finalizedData)
> def getAnnotations():
> domainLocation = plot.domainCrosshairValue
> headers = ['column', 'text', 'x', 'y1', 'y2']
> subData = []
> for row in range(data.rowCount):
> if data.getValueAt(row, 0).getTime() == domainLocation:
> for column in range(1, data.columnCount):
> text = getAnnotationText(row, column)
> layout = TextLayout(u'█' * len(text), annotationFont, FontRenderContext(None, False, False))
> x = getAnnotationX(layout, domainLocation)
> yMidpoint = data.getValueAt(row, column)
> yValues = getYValues(layout, yMidpoint)
> y1 = yValues[0]
> y2 = yValues[1]
> subData.append([column, text, x, y1, y2])
> annotationData = system.dataset.toDataSet(headers, subData)
> annotations = adjustAnnotations(annotationData)
> return annotations			
> def setAnnotation():
> annotations = getAnnotations()
> for row in range(annotations.rowCount):
> column = annotations.getValueAt(row, 0)
> text = annotations.getValueAt(row, 1)
> x = annotations.getValueAt(row, 2)
> y1 = annotations.getValueAt(row, 3)
> y2 = annotations.getValueAt(row, 4)
> y = y1 + ((y2 - y1) * .5)
> background = XYTextAnnotation(u'█' * len(text), x, y)
> background.setPaint(Color.WHITE)
> background.setFont(backgroundFont)
> plot.addAnnotation(background)		
> annotation = XYTextAnnotation(text, x, y)
> textColor = plot.renderer.getSeriesPaint(column-1)
> annotation.setPaint(textColor)
> annotation.setFont(annotationFont)
> plot.addAnnotation(annotation)
> for annotation in plot.getAnnotations():
> if isinstance(annotation, XYTextAnnotation):
> plot.removeAnnotation(annotation)
> if event.source.chart.plot.annotationMode == 2: #This line ensures that the labels will only be added in XTrace Mode
> setAnnotation()
> 
> ```

> **getXTraceLabel extension function**
>
> ```auto
> #def getXTraceLabel(self, chart, penName, yValue):
> return ''
> 
> ```

---

<div class="post-metadata">

**Author:** ![ricardo.ortolani](https://avatars.discourse-cdn.com/v4/letter/r/c68b51/32.png) [@ricardo.ortolani](https://forum.inductiveautomation.com/u/ricardo.ortolani)\
**Post date:** [March 9, 2023, 2:08pm UTC](https://forum.inductiveautomation.com/t/vision-chart-not-easychart-min-max-date/70606/13 "2023-03-09T14:08:05Z")

</div>

@justinedwards.jle this is super nice, I tested and works really well.

thanks again for the help 🙂
