Classic chart: X axis questions

UPDATE:
Question #2 has been answered. I’m still looking for an answer to #1.

In a classic chart…

  1. Is it possible to have the time and date display in the X axis? It’s only displaying the time, which isn’t the most helpful when a run spans more than one day.

  2. Is it possible to have the chart NOT plot gaps in time? We had a run shut down one day and start up two days later. That gap in time is on the chart. So all the temperatures, that changed significantly from shut down to start up, have a straight line that goes from their old to their new values, and the straight line spans two days. It looks very awkward.

Notice my awesome mouse hand writing…

1)It’s possible to zoom out the X-axis in the Client (also in the Preview Mode of the Designer, for that matter) by right clicking on the chart, and selecting Zoom Out > Range axis. Do this once or twice and you should be able to zoom out over a couple days. You can zoom back in after if need be.

  1. If there isn’t any data in the Dataset for that time frame, you can go into the Chart Customizer, select your Dataset from the Dataset Properties tab, and change the Type Property to Discontinuous Lines. This should create a gap in your data (i.e. no random straight lines).

If there IS data from that time period, you will have to use some crafty SQL query to selectively leave out the down time.

Thanks, pscott. I believe no. 2 in your reply is what I’m looking for. At least in this case, I’m not allowing end-users to zoom in and out of the chart. I’m feeding the chart only the data from the current run from a SQL view. And no, there is no data in the data set from the time gap I’m talking about here.

As for my first question, I still don’t have an answer. Look at the screenshot in my first post. It only shows the time on the X axis. I’d like it to display date and time, not just time. Anyone?

The chart does its best to format the date/time on the x-axis based on how much you have zoomed in. It’s not always perfect. :slight_smile:

Go to Chart Customizer > X-Axes > and scroll to the bottom. You’ll see “Display Date in Title” Enabling this will display the date below your x-axis, which will probably give your users the information they want.

Thanks for the reply. But that actually doesn’t give me the information I need. If a run spans multiple days, it only displays one of those dates. This is why I’d like it right in with the time on the X axis.

I had a similar problem with a bar chart and posted a possible solution here.
I should be possible to adjust this for the classic chart, if you are a bit experienced in java programming.

EDIT:
I just realized that things changed bit in 7.6. The solution posted above for the StatusChart is not working anymore, since there is no reliable event to trigger the custom script.
Good news is that we now have the ‘configureChart’ extension function for the classic chart, that seems to be made exactly for this situation (IA team: Can we have this for other chart types also?).

Put the following code in the configureChart function:

def configureChart(self, chart):
	import app
	app.chartUtils.setDateTickUnits(chart, "Displaying %1$td.%1$tm.%1$tY %1$tH:%1$tM to %2$td.%2$tm.%2$tY %2$tH:%2$tM")

and create the app.chartUtils script module with the code below. You can adjust the tick units in the getDefaultTickUnits() method. The result will be something like this:
[attachment=0]custom-date-axis.png[/attachment]

# module app.chartUtils
global app
import app

# Constants for axis
DOMAIN_AXIS = 0
RANGE_AXIS = 1

# Replaces all DateAxis in the given chart with a custom axis
# Call this method from the configureChart extension function
# Parameters:
#  chart:	The JFreeChart object
#  label:	A custom axis label. Java's String.format method is used to format this label.
#			Available format args: %1$ - start Date, %2$ end Date
#  axis:	app.chartUtils.DOMAIN_AXIS (default) or app.chartUtils.RANGE_AXIS
#  units:	An org.jfree.chart.axis.TickUnits object (see getDefaultTickUnits())
def setDateTickUnits(chart, label = None, axis = DOMAIN_AXIS, units = None):
	
	if units == None:
		units = app.chartUtils.getDefaultTickUnits()
	
	# Apply the new tick units to the chart
	plot = chart.getPlot()
	
	# Iterate all axis
	from org.jfree.chart.axis import DateAxis
	if axis == app.chartUtils.DOMAIN_AXIS:
		axisCount = plot.getDomainAxisCount()
		for index in range(0, axisCount):
			axis = plot.getDomainAxis(index)
			if isinstance(axis, DateAxis):
				customAxis = app.chartUtils.CustomDateAxis(label)
				customAxis.setStandardTickUnits(units)
				customAxis.setConfigFromAxis(axis)
				plot.setDomainAxis(index,customAxis)

# Create a new org.jfree.chart.axis.TickUnits
def getDefaultTickUnits():
	from org.jfree.chart.axis import TickUnits
	from org.jfree.chart.axis import DateTickUnit
	from java.text import SimpleDateFormat
	units = TickUnits()
	
	# Format definitions
	# Formats are created with default timezone and default locale
	# format.setTimezone might be used to override the timezone
	# Additional space is added to the format to prevent ticks from being
	# displayed without space (happens sometimes when using anchored layout).
	formatMSec = SimpleDateFormat(" HH:mm:ss SS")
	formatSec = SimpleDateFormat(" HH:mm:ss")
	formatMin = SimpleDateFormat(" HH:mm")
	formatHour = SimpleDateFormat(" HH:mm")
	formatDay = SimpleDateFormat(" dd.MM HH:mm")
	formatMonth = SimpleDateFormat(" dd.MM.yyyy")
	formatYear = SimpleDateFormat(" MMM yyyy")
	
	# Add the units
	units.add(DateTickUnit(DateTickUnit.MILLISECOND, 5, formatMSec))
	units.add(DateTickUnit(DateTickUnit.MILLISECOND, 50, DateTickUnit.MILLISECOND, 5, formatMSec))
	units.add(DateTickUnit(DateTickUnit.MILLISECOND, 250, DateTickUnit.MILLISECOND, 50, formatMSec))
	units.add(DateTickUnit(DateTickUnit.MILLISECOND, 500, DateTickUnit.MILLISECOND, 50, formatMSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 1, DateTickUnit.MILLISECOND, 50, formatSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 2, DateTickUnit.MILLISECOND, 250, formatSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 5, DateTickUnit.MILLISECOND, 250, formatSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 10, DateTickUnit.MILLISECOND, 500, formatSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 30, DateTickUnit.SECOND, 2, formatSec))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 1, DateTickUnit.SECOND, 5, formatSec))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 2, DateTickUnit.SECOND, 10, formatMin))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 5, DateTickUnit.SECOND, 30, formatMin))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 10, DateTickUnit.MINUTE, 1, formatMin))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 20, DateTickUnit.MINUTE, 5, formatMin))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 30, DateTickUnit.MINUTE, 5, formatMin))
	units.add(DateTickUnit(DateTickUnit.HOUR, 1, DateTickUnit.MINUTE, 5, formatHour))
	units.add(DateTickUnit(DateTickUnit.HOUR, 2, DateTickUnit.MINUTE, 10, formatHour))
	units.add(DateTickUnit(DateTickUnit.HOUR, 4, DateTickUnit.MINUTE, 30, formatHour))
	units.add(DateTickUnit(DateTickUnit.HOUR, 6, DateTickUnit.HOUR, 1, formatDay))
	units.add(DateTickUnit(DateTickUnit.HOUR, 12, DateTickUnit.HOUR, 2, formatDay))
	units.add(DateTickUnit(DateTickUnit.DAY, 1, DateTickUnit.HOUR, 4, formatDay))
	units.add(DateTickUnit(DateTickUnit.DAY, 2, DateTickUnit.HOUR, 4, formatDay))
	units.add(DateTickUnit(DateTickUnit.DAY, 7, DateTickUnit.DAY, 1, formatDay))
	units.add(DateTickUnit(DateTickUnit.DAY, 14, DateTickUnit.DAY, 1, formatDay))
	units.add(DateTickUnit(DateTickUnit.MONTH, 1, DateTickUnit.DAY, 1, formatDay))
	units.add(DateTickUnit(DateTickUnit.MONTH, 2, DateTickUnit.DAY, 7, formatMonth))
	units.add(DateTickUnit(DateTickUnit.MONTH, 3, DateTickUnit.DAY, 14, formatMonth))
	units.add(DateTickUnit(DateTickUnit.MONTH, 6, DateTickUnit.MONTH, 1, formatMonth))
	units.add(DateTickUnit(DateTickUnit.YEAR, 1, DateTickUnit.MONTH, 1, formatYear))
	units.add(DateTickUnit(DateTickUnit.YEAR, 2, DateTickUnit.MONTH, 3, formatYear))
	
	return units
 	
# Override DateAxis#getLabel to return min & max date in custom format
from org.jfree.chart.axis import DateAxis
class CustomDateAxis(DateAxis):
	def __init__(self, label):
		self.customLabel = label
	
	def getLabel(self):
		if self.customLabel == None:
			return super(DateAxis, self)
		else:	
			from java.lang import String
			minDate = super(app.chartUtils.CustomDateAxis, self).getMinimumDate()
			maxDate = super(app.chartUtils.CustomDateAxis, self).getMaximumDate()
			# Modify the format given here, ore add additional logic to change according to the range
			return String.format(self.customLabel, minDate, maxDate)
		
	# Copy the config from another axis
	def setConfigFromAxis(self, other):
		from org.jfree.chart.axis import DateAxis
		super(DateAxis, self).setLabelAngle(other.labelAngle)
		super(DateAxis, self).setLabelFont(other.labelFont)
		super(DateAxis, self).setLabelPaint(other.labelPaint)

		super(DateAxis, self).setTickLabelFont(other.tickLabelFont)
		super(DateAxis, self).setTickLabelPaint(other.tickLabelPaint)
		super(DateAxis, self).setTickLabelsVisible(other.tickLabelsVisible)
		super(DateAxis, self).setVerticalTickLabels(other.verticalTickLabels)
		
		super(DateAxis, self).setTickMarkInsideLength(other.tickMarkInsideLength)
		super(DateAxis, self).setTickMarkOutsideLength(other.tickMarkOutsideLength)
		super(DateAxis, self).setTickMarkPaint(other.tickMarkPaint)
		super(DateAxis, self).setTickMarksVisible(other.tickMarksVisible)
			
		super(DateAxis, self).setNegativeArrowVisible(other.negativeArrowVisible)
		super(DateAxis, self).setPositiveArrowVisible(other.positiveArrowVisible)
		super(DateAxis, self).setLowerMargin(other.lowerMargin)
		super(DateAxis, self).setUpperMargin(other.upperMargin)
2 Likes

Wow that works awesome! This has been driving me crazy for awhile now. Thanks!

I’ve been using this chartUtil script in a couple of projects with Vision 7.9 and it’s working great.
Thank you @chi for this. :+1:

Now I’m working on a new project with Vision 8.1.11 and I tried to use this script also, but I’m getting an error and I’m not good enough to ‘decipher’ this error.


and this is my chartUtil script:

# module chartUtils
#global project
#import project

# Constants for axis
DOMAIN_AXIS = 0
RANGE_AXIS = 1

# Replaces all DateAxis in the given chart with a custom axis
# Call this method from the configureChart extension function
# Parameters:
#  chart:	The JFreeChart object
#  label:	A custom axis label. Java's String.format method is used to format this label.
#			Available format args: %1$ - start Date, %2$ end Date
#  axis:	chartUtils.DOMAIN_AXIS (default) or chartUtils.RANGE_AXIS
#  units:	An org.jfree.chart.axis.TickUnits object (see getDefaultTickUnits())
def setDateTickUnits(chart, label = None, axis = DOMAIN_AXIS, units = None):
	
	if units == None:
		units = chartUtils.getDefaultTickUnits()
	
	# Apply the new tick units to the chart
	plot = chart.getPlot()
	
	# Iterate all axis
	from org.jfree.chart.axis import DateAxis
	if axis == chartUtils.DOMAIN_AXIS:
		axisCount = plot.getDomainAxisCount()
		for index in range(0, axisCount):
			axis = plot.getDomainAxis(index)
			if isinstance(axis, DateAxis):
				customAxis = chartUtils.CustomDateAxis(label)
				customAxis.setStandardTickUnits(units)
				customAxis.setConfigFromAxis(axis)
				plot.setDomainAxis(index,customAxis)

# Create a new org.jfree.chart.axis.TickUnits
def getDefaultTickUnits():
	from org.jfree.chart.axis import TickUnits
	from org.jfree.chart.axis import DateTickUnit
	from java.text import SimpleDateFormat
	units = TickUnits()
	
	# Format definitions
	# Formats are created with default timezone and default locale
	# format.setTimezone might be used to override the timezone
	# Additional space is added to the format to prevent ticks from being
	# displayed without space (hprojectens sometimes when using anchored layout).
	formatMSec = SimpleDateFormat(" HH:mm:ss SS")
	formatSec = SimpleDateFormat(" HH:mm:ss")
	formatMin = SimpleDateFormat(" HH:mm")
	formatHour = SimpleDateFormat(" HH:mm")
	formatDay = SimpleDateFormat(" dd.MM HH:mm")
	formatMonth = SimpleDateFormat(" dd MMM yyyy")
	formatYear = SimpleDateFormat(" MMM yyyy")
	
	# Add the units
	units.add(DateTickUnit(DateTickUnit.MILLISECOND, 5, formatMSec))
	units.add(DateTickUnit(DateTickUnit.MILLISECOND, 50, DateTickUnit.MILLISECOND, 5, formatMSec))
	units.add(DateTickUnit(DateTickUnit.MILLISECOND, 250, DateTickUnit.MILLISECOND, 50, formatMSec))
	units.add(DateTickUnit(DateTickUnit.MILLISECOND, 500, DateTickUnit.MILLISECOND, 50, formatMSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 1, DateTickUnit.MILLISECOND, 50, formatSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 2, DateTickUnit.MILLISECOND, 250, formatSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 5, DateTickUnit.MILLISECOND, 250, formatSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 10, DateTickUnit.MILLISECOND, 500, formatSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 30, DateTickUnit.SECOND, 2, formatSec))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 1, DateTickUnit.SECOND, 5, formatSec))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 2, DateTickUnit.SECOND, 10, formatMin))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 5, DateTickUnit.SECOND, 30, formatMin))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 10, DateTickUnit.MINUTE, 1, formatMin))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 20, DateTickUnit.MINUTE, 5, formatMin))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 30, DateTickUnit.MINUTE, 5, formatMin))
	units.add(DateTickUnit(DateTickUnit.HOUR, 1, DateTickUnit.MINUTE, 5, formatHour))
	units.add(DateTickUnit(DateTickUnit.HOUR, 2, DateTickUnit.MINUTE, 10, formatHour))
	units.add(DateTickUnit(DateTickUnit.HOUR, 4, DateTickUnit.MINUTE, 30, formatHour))
	units.add(DateTickUnit(DateTickUnit.HOUR, 6, DateTickUnit.HOUR, 1, formatDay))
	units.add(DateTickUnit(DateTickUnit.HOUR, 12, DateTickUnit.HOUR, 2, formatDay))
	units.add(DateTickUnit(DateTickUnit.DAY, 1, DateTickUnit.HOUR, 4, formatDay))
	units.add(DateTickUnit(DateTickUnit.DAY, 2, DateTickUnit.HOUR, 4, formatDay))
	units.add(DateTickUnit(DateTickUnit.DAY, 7, DateTickUnit.DAY, 1, formatDay))
	units.add(DateTickUnit(DateTickUnit.DAY, 14, DateTickUnit.DAY, 1, formatDay))
	units.add(DateTickUnit(DateTickUnit.MONTH, 1, DateTickUnit.DAY, 1, formatDay))
	units.add(DateTickUnit(DateTickUnit.MONTH, 2, DateTickUnit.DAY, 7, formatMonth))
	units.add(DateTickUnit(DateTickUnit.MONTH, 3, DateTickUnit.DAY, 14, formatMonth))
	units.add(DateTickUnit(DateTickUnit.MONTH, 6, DateTickUnit.MONTH, 1, formatMonth))
	units.add(DateTickUnit(DateTickUnit.YEAR, 1, DateTickUnit.MONTH, 1, formatYear))
	units.add(DateTickUnit(DateTickUnit.YEAR, 2, DateTickUnit.MONTH, 3, formatYear))
	
	return units
 	
# Override DateAxis#getLabel to return min & max date in custom format
from org.jfree.chart.axis import DateAxis
class CustomDateAxis(DateAxis):
	def __init__(self, label):
		self.customLabel = label
	
	def getLabel(self):
		if self.customLabel == None:
			return super(DateAxis, self)
		else:	
			from java.lang import String
			minDate = super(chartUtils.CustomDateAxis, self).getMinimumDate()
			maxDate = super(chartUtils.CustomDateAxis, self).getMaximumDate()
			# Modify the format given here, ore add additional logic to change according to the range
			return String.format(self.customLabel, minDate, maxDate)
		
	# Copy the config from another axis
	def setConfigFromAxis(self, other):
		from org.jfree.chart.axis import DateAxis
		super(DateAxis, self).setLabelAngle(other.labelAngle)
		super(DateAxis, self).setLabelFont(other.labelFont)
		super(DateAxis, self).setLabelPaint(other.labelPaint)

		super(DateAxis, self).setTickLabelFont(other.tickLabelFont)
		super(DateAxis, self).setTickLabelPaint(other.tickLabelPaint)
		super(DateAxis, self).setTickLabelsVisible(other.tickLabelsVisible)
		super(DateAxis, self).setVerticalTickLabels(other.verticalTickLabels)
		
		super(DateAxis, self).setTickMarkInsideLength(other.tickMarkInsideLength)
		super(DateAxis, self).setTickMarkOutsideLength(other.tickMarkOutsideLength)
		super(DateAxis, self).setTickMarkPaint(other.tickMarkPaint)
		super(DateAxis, self).setTickMarksVisible(other.tickMarksVisible)
			
		super(DateAxis, self).setNegativeArrowVisible(other.negativeArrowVisible)
		super(DateAxis, self).setPositiveArrowVisible(other.positiveArrowVisible)
		super(DateAxis, self).setLowerMargin(other.lowerMargin)
		super(DateAxis, self).setUpperMargin(other.upperMargin)

Can @chi or anyone else help, please?

That error means that the DateAxis class has no method setLabelAngle. Either JFreeChart changed the API (unlikely) or something strange causes the import to fail.
Can you add print self before the failing line and post the result? I am not at the office until thursday, so i can’t test myself until then.

This is what I get

<class 'chartUtils.CustomDateAxis'>
07:17:58.348 [AWT-EventQueue-0] ERROR Vision.ClassicChart - Error invoking extension method.
org.python.core.PyException: TypeError: can't convert <super: <class 'org.jfree.chart.axis.DateAxis'>, <CustomDateAxis object>> to java.lang.String
	at org.python.core.Py.TypeError(Py.java:236)
	at org.python.core.Py.tojava(Py.java:568)
	at org.python.proxies.chartUtils$CustomDateAxis$130.getLabel(Unknown Source)
	at org.jfree.chart.axis.DateAxis.hashCode(DateAxis.java:1848)
	at java.base/java.lang.Object.toString(Unknown Source)
	at org.python.core.PyJavaType$8.__call__(PyJavaType.java:1071)
	at org.python.core.PyObjectDerived.__repr__(PyObjectDerived.java:104)
	at org.python.core.PyObject$__repr___exposer.__call__(Unknown Source)
	at org.python.core.PyObjectDerived.__str__(PyObjectDerived.java:92)
	at org.python.core.StdoutWrapper.printToFile(StdoutWrapper.java:108)
	at org.python.core.StdoutWrapper.print(StdoutWrapper.java:186)
	at org.python.core.StdoutWrapper.println(StdoutWrapper.java:272)
	at org.python.core.Py.println(Py.java:1905)
	at org.python.pycode._pyx208.setConfigFromAxis$6(<module:chartUtils>:129)
	at org.python.pycode._pyx208.call_function(<module:chartUtils>)
	at org.python.core.PyTableCode.call(PyTableCode.java:173)
	at org.python.core.PyBaseCode.call(PyBaseCode.java:150)
	at org.python.core.PyFunction.__call__(PyFunction.java:426)
	at org.python.core.PyMethod.__call__(PyMethod.java:141)
	at org.python.pycode._pyx208.setDateTickUnits$1(<module:chartUtils>:29)
	at org.python.pycode._pyx208.call_function(<module:chartUtils>)
	at org.python.core.PyTableCode.call(PyTableCode.java:173)
	at org.python.core.PyBaseCode.call(PyBaseCode.java:306)
	at org.python.core.PyFunction.function___call__(PyFunction.java:474)
	at org.python.core.PyFunction.__call__(PyFunction.java:469)
	at org.python.pycode._pyx130.configureChart$1(<extension-method configureChart>:71)
	at org.python.pycode._pyx130.call_function(<extension-method configureChart>)
	at org.python.core.PyTableCode.call(PyTableCode.java:173)
	at org.python.core.PyBaseCode.call(PyBaseCode.java:306)
	at org.python.core.PyFunction.function___call__(PyFunction.java:474)
	at org.python.core.PyFunction.__call__(PyFunction.java:469)
	at org.python.core.PyFunction.__call__(PyFunction.java:459)
	at org.python.core.PyFunction.__call__(PyFunction.java:454)
	at com.inductiveautomation.vision.api.client.components.model.ExtensionFunction.invoke(ExtensionFunction.java:151)
	at com.inductiveautomation.factorypmi.application.components.PMIChart.createChartImpl(PMIChart.java:481)
	at com.inductiveautomation.factorypmi.application.components.chart.PMILineChartPanel.createChart(PMILineChartPanel.java:135)
	at com.inductiveautomation.factorypmi.application.components.PMIChart.setChartType(PMIChart.java:1239)
	at com.inductiveautomation.factorypmi.application.components.PMIChart.localeChanged(PMIChart.java:234)
	at com.inductiveautomation.factorypmi.application.components.util.ComponentVisitor$LocaleChangedVisitor.visit(ComponentVisitor.java:356)
	at com.inductiveautomation.factorypmi.application.components.util.ComponentVisitor.walk(ComponentVisitor.java:90)
	at com.inductiveautomation.factorypmi.application.components.util.ComponentVisitor.walk(ComponentVisitor.java:87)
	at com.inductiveautomation.factorypmi.application.components.util.ComponentVisitor.walk(ComponentVisitor.java:87)
	at com.inductiveautomation.factorypmi.application.components.util.ComponentVisitor.walk(ComponentVisitor.java:68)
	at com.inductiveautomation.factorypmi.application.FPMIWindow.lambda$new$0(FPMIWindow.java:122)
	at com.inductiveautomation.ignition.designer.i18n.DesignerLocalizationManager.fireLocaleChanged(DesignerLocalizationManager.java:531)
	at com.inductiveautomation.ignition.designer.i18n.DesignerLocalizationManager.setCurrentLocale(DesignerLocalizationManager.java:197)
	at com.inductiveautomation.factorypmi.application.FPMISystem.setMode(FPMISystem.java:45)
	at com.inductiveautomation.factorypmi.designer.workspace.WindowWorkspace$1.propertyChange(WindowWorkspace.java:287)
	at java.desktop/java.beans.PropertyChangeSupport.fire(Unknown Source)
	at java.desktop/java.beans.PropertyChangeSupport.firePropertyChange(Unknown Source)
	at java.desktop/java.beans.PropertyChangeSupport.firePropertyChange(Unknown Source)
	at java.desktop/java.beans.PropertyChangeSupport.firePropertyChange(Unknown Source)
	at java.desktop/java.awt.Component.firePropertyChange(Unknown Source)
	at java.desktop/javax.swing.JComponent.firePropertyChange(Unknown Source)
	at com.inductiveautomation.ignition.designer.designable.AbstractDesignableWorkspace.setDesignTime(AbstractDesignableWorkspace.java:347)
	at com.inductiveautomation.factorypmi.designer.workspace.WindowWorkspace$Handler$9.itemStateChanged(WindowWorkspace.java:2375)
	at com.inductiveautomation.ignition.client.util.action.StateChangeAction.setSelected(StateChangeAction.java:59)
	at com.inductiveautomation.ignition.client.util.gui.Listen.lambda$toStateChange$6(Listen.java:86)
	at java.desktop/javax.swing.AbstractButton.fireItemStateChanged(Unknown Source)
	at com.jidesoft.swing.JideToggleButton.access$000(Unknown Source)
	at com.jidesoft.swing.JideToggleButton$1.itemStateChanged(Unknown Source)
	at java.desktop/javax.swing.DefaultButtonModel.fireItemStateChanged(Unknown Source)
	at com.jidesoft.swing.JideToggleButton$ToggleButtonModel.setSelected(Unknown Source)
	at com.jidesoft.swing.JideToggleButton$ToggleButtonModel.setPressed(Unknown Source)
	at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(Unknown Source)
	at com.jidesoft.plaf.basic.BasicJideButtonListener.mouseReleased(Unknown Source)
	at java.desktop/java.awt.AWTEventMulticaster.mouseReleased(Unknown Source)
	at java.desktop/java.awt.Component.processMouseEvent(Unknown Source)
	at java.desktop/javax.swing.JComponent.processMouseEvent(Unknown Source)
	at java.desktop/java.awt.Component.processEvent(Unknown Source)
	at java.desktop/java.awt.Container.processEvent(Unknown Source)
	at java.desktop/java.awt.Component.dispatchEventImpl(Unknown Source)
	at java.desktop/java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.desktop/java.awt.Component.dispatchEvent(Unknown Source)
	at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
	at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
	at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
	at java.desktop/java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.desktop/java.awt.Window.dispatchEventImpl(Unknown Source)
	at java.desktop/java.awt.Component.dispatchEvent(Unknown Source)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(Unknown Source)
	at java.desktop/java.awt.EventQueue$4.run(Unknown Source)
	at java.desktop/java.awt.EventQueue$4.run(Unknown Source)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
	at java.desktop/java.awt.EventQueue$5.run(Unknown Source)
	at java.desktop/java.awt.EventQueue$5.run(Unknown Source)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
	at java.desktop/java.awt.EventQueue.dispatchEvent(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.desktop/java.awt.EventDispatchThread.run(Unknown Source)

when I add print statement:

	# Copy the config from another axis
	def setConfigFromAxis(self, other):
		from org.jfree.chart.axis import DateAxis
		print type(self)
		print self
		super(DateAxis, self).setLabelAngle(other.labelAngle)
		super(DateAxis, self).setLabelFont(other.labelFont)
		super(DateAxis, self).setLabelPaint(other.labelPaint)

		super(DateAxis, self).setTickLabelFont(other.tickLabelFont)
		super(DateAxis, self).setTickLabelPaint(other.tickLabelPaint)
		super(DateAxis, self).setTickLabelsVisible(other.tickLabelsVisible)
		super(DateAxis, self).setVerticalTickLabels(other.verticalTickLabels)
		
		super(DateAxis, self).setTickMarkInsideLength(other.tickMarkInsideLength)
		super(DateAxis, self).setTickMarkOutsideLength(other.tickMarkOutsideLength)
		super(DateAxis, self).setTickMarkPaint(other.tickMarkPaint)
		super(DateAxis, self).setTickMarksVisible(other.tickMarksVisible)
			
		super(DateAxis, self).setNegativeArrowVisible(other.negativeArrowVisible)
		super(DateAxis, self).setPositiveArrowVisible(other.positiveArrowVisible)
		super(DateAxis, self).setLowerMargin(other.lowerMargin)
		super(DateAxis, self).setUpperMargin(other.upperMargin)

The cause might be the upgrade to Jython 2.7.2 with Ignition 8.1.9. It looks like this Jython release changed something in Java subclassing.

I did a quick test and replacing super(DateAxis, self). with self. seems to work.

1 Like

You are right. :+1:
Everywhere, where there’s super(...), you can replace it with self.

# module chartUtils
#global project
#import project

# Constants for axis
DOMAIN_AXIS = 0
RANGE_AXIS = 1

# Replaces all DateAxis in the given chart with a custom axis
# Call this method from the configureChart extension function
# Parameters:
#  chart:	The JFreeChart object
#  label:	A custom axis label. Java's String.format method is used to format this label.
#			Available format args: %1$ - start Date, %2$ end Date
#  axis:	chartUtils.DOMAIN_AXIS (default) or chartUtils.RANGE_AXIS
#  units:	An org.jfree.chart.axis.TickUnits object (see getDefaultTickUnits())
def setDateTickUnits(chart, label = None, axis = DOMAIN_AXIS, units = None):
	
	if units == None:
		units = chartUtils.getDefaultTickUnits()
	
	# Apply the new tick units to the chart
	plot = chart.getPlot()
	
	# Iterate all axis
	from org.jfree.chart.axis import DateAxis
	if axis == chartUtils.DOMAIN_AXIS:
		axisCount = plot.getDomainAxisCount()
		for index in range(0, axisCount):
			axis = plot.getDomainAxis(index)
			if isinstance(axis, DateAxis):
				customAxis = chartUtils.CustomDateAxis(label)
				customAxis.setStandardTickUnits(units)
				customAxis.setConfigFromAxis(axis)
				plot.setDomainAxis(index,customAxis)

# Create a new org.jfree.chart.axis.TickUnits
def getDefaultTickUnits():
	from org.jfree.chart.axis import TickUnits
	from org.jfree.chart.axis import DateTickUnit
	from java.text import SimpleDateFormat
	units = TickUnits()
	
	# Format definitions
	# Formats are created with default timezone and default locale
	# format.setTimezone might be used to override the timezone
	# Additional space is added to the format to prevent ticks from being
	# displayed without space (hprojectens sometimes when using anchored layout).
	formatMSec = SimpleDateFormat(" HH:mm:ss SS")
	formatSec = SimpleDateFormat(" HH:mm:ss")
	formatMin = SimpleDateFormat(" HH:mm")
	formatHour = SimpleDateFormat(" HH:mm")
	formatDay = SimpleDateFormat(" dd.MM HH:mm")
	formatMonth = SimpleDateFormat(" dd MMM yyyy")
	formatYear = SimpleDateFormat(" MMM yyyy")
	
	# Add the units
	units.add(DateTickUnit(DateTickUnit.MILLISECOND, 5, formatMSec))
	units.add(DateTickUnit(DateTickUnit.MILLISECOND, 50, DateTickUnit.MILLISECOND, 5, formatMSec))
	units.add(DateTickUnit(DateTickUnit.MILLISECOND, 250, DateTickUnit.MILLISECOND, 50, formatMSec))
	units.add(DateTickUnit(DateTickUnit.MILLISECOND, 500, DateTickUnit.MILLISECOND, 50, formatMSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 1, DateTickUnit.MILLISECOND, 50, formatSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 2, DateTickUnit.MILLISECOND, 250, formatSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 5, DateTickUnit.MILLISECOND, 250, formatSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 10, DateTickUnit.MILLISECOND, 500, formatSec))
	units.add(DateTickUnit(DateTickUnit.SECOND, 30, DateTickUnit.SECOND, 2, formatSec))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 1, DateTickUnit.SECOND, 5, formatSec))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 2, DateTickUnit.SECOND, 10, formatMin))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 5, DateTickUnit.SECOND, 30, formatMin))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 10, DateTickUnit.MINUTE, 1, formatMin))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 20, DateTickUnit.MINUTE, 5, formatMin))
	units.add(DateTickUnit(DateTickUnit.MINUTE, 30, DateTickUnit.MINUTE, 5, formatMin))
	units.add(DateTickUnit(DateTickUnit.HOUR, 1, DateTickUnit.MINUTE, 5, formatHour))
	units.add(DateTickUnit(DateTickUnit.HOUR, 2, DateTickUnit.MINUTE, 10, formatHour))
	units.add(DateTickUnit(DateTickUnit.HOUR, 4, DateTickUnit.MINUTE, 30, formatHour))
	units.add(DateTickUnit(DateTickUnit.HOUR, 6, DateTickUnit.HOUR, 1, formatDay))
	units.add(DateTickUnit(DateTickUnit.HOUR, 12, DateTickUnit.HOUR, 2, formatDay))
	units.add(DateTickUnit(DateTickUnit.DAY, 1, DateTickUnit.HOUR, 4, formatDay))
	units.add(DateTickUnit(DateTickUnit.DAY, 2, DateTickUnit.HOUR, 4, formatDay))
	units.add(DateTickUnit(DateTickUnit.DAY, 7, DateTickUnit.DAY, 1, formatDay))
	units.add(DateTickUnit(DateTickUnit.DAY, 14, DateTickUnit.DAY, 1, formatDay))
	units.add(DateTickUnit(DateTickUnit.MONTH, 1, DateTickUnit.DAY, 1, formatDay))
	units.add(DateTickUnit(DateTickUnit.MONTH, 2, DateTickUnit.DAY, 7, formatMonth))
	units.add(DateTickUnit(DateTickUnit.MONTH, 3, DateTickUnit.DAY, 14, formatMonth))
	units.add(DateTickUnit(DateTickUnit.MONTH, 6, DateTickUnit.MONTH, 1, formatMonth))
	units.add(DateTickUnit(DateTickUnit.YEAR, 1, DateTickUnit.MONTH, 1, formatYear))
	units.add(DateTickUnit(DateTickUnit.YEAR, 2, DateTickUnit.MONTH, 3, formatYear))
	
	return units
 	
# Override DateAxis#getLabel to return min & max date in custom format
from org.jfree.chart.axis import DateAxis
class CustomDateAxis(DateAxis):
	def __init__(self, label):
		self.customLabel = label
	
	def getLabel(self):
		if self.customLabel == None:
			return self
		else:	
			from java.lang import String
			minDate = self.getMinimumDate()
			maxDate = self.getMaximumDate()
			# Modify the format given here, ore add additional logic to change according to the range
			return String.format(self.customLabel, minDate, maxDate)
		
	# Copy the config from another axis
	def setConfigFromAxis(self, other):
		from org.jfree.chart.axis import DateAxis

		self.setLabelAngle(other.labelAngle)
		self.setLabelFont(other.labelFont)
		self.setLabelPaint(other.labelPaint)

		self.setTickLabelFont(other.tickLabelFont)
		self.setTickLabelPaint(other.tickLabelPaint)
		self.setTickLabelsVisible(other.tickLabelsVisible)
		self.setVerticalTickLabels(other.verticalTickLabels)
		
		self.setTickMarkInsideLength(other.tickMarkInsideLength)
		self.setTickMarkOutsideLength(other.tickMarkOutsideLength)
		self.setTickMarkPaint(other.tickMarkPaint)
		self.setTickMarksVisible(other.tickMarksVisible)
			
		self.setNegativeArrowVisible(other.negativeArrowVisible)
		self.setPositiveArrowVisible(other.positiveArrowVisible)
		self.setLowerMargin(other.lowerMargin)
		self.setUpperMargin(other.upperMargin)

Thank you. :beers:

While I appreciate your work and help very much, @chi, I do have another ‘problem’ if you be willing to help.
Your chartUtil script does an excellent job to tick labels in the classic chart component in Vision (so they are not getting crowded), I was wondering if there is a way to do this also on the Bar Chart component in Vision.
Here is my video:


As you can see, on the fourth tab (classic chat) the tick labels newer overlap, no matter what date range I select.
But on the second tab (bar chat) when I select a date range of more than three weeks, the tick labels are not visible anymore…
Do you think is possible?

The BarChart uses a CategoryAxis that tries to draw a text for every category in the displayed dataset. You may try to use a classic chart with a ClusteredXYBarRenderer instead.
This is a very basic example, you have to play with the renderer and axis settings to get the result you want:

def configureChart(self, chart):
	"""
	Provides an opportunity to perform further chart configuration via
	scripting. Doesn't return anything.

	Arguments:
		self: A reference to the component that is invoking this function.
		chart: A JFreeChart object. Refer to the JFreeChart documentation for
		       API details.
	"""
	from org.jfree.chart.renderer.xy import ClusteredXYBarRenderer
	plot = chart.getPlot()
	renderer = ClusteredXYBarRenderer()
	renderer.setShadowVisible(0)
	plot.setRenderer(renderer)