Yes in my example chart time column is not used
but right now i need to use the timeframe for bar width adjustment
Y axis is electric and gas percentage value will be ploted based on work order number
Yes in my example chart time column is not used
but right now i need to use the timeframe for bar width adjustment
Y axis is electric and gas percentage value will be ploted based on work order number
This is a non-trivial chunk of code you are asking volunteers to write. They keep telling you to try to write it yourself, and then they can review. Show your code.
I have tired one thing passing start and end time in different column but i got a error
I have created x axis with time
You shouldn't need to do this. You are going to have to calculate the start position of each bar relative to width of your chart, so you will need to develop some kind of time ratio relative to the total amount of time in your dataset. The code needed at the beginning of the configure chart extension function will look something like this:
data = self.data
width = self.width
maxTimeValue = data.getRowCount()-1
chartStartTime = data.getValueAt(0,0)
chartEndTime = data.getValueAt(maxTimeValue,0)
totalTime = system.date.minutesBetween(chartStartTime,chartEndTime)
timeRatio = float(width)/float(totalTime)
The time ratio can then be used to calculate the start positions of each bar based upon the end time of the previous value, and this will be done within the calculateBarW0 definition. It will look something like this:
barStartTime = data.getValueAt(column,0)
barStartingLocation = float(system.date.minutesBetween(chartStartTime, barStartTime))*timeRatio
The if
statements used to define column positions in my original example will need to be deleted. They were only necessary because the numbers I used were arbitrary and uncalculated. To calculate the bar width, some sort of scale parameter will have to be developed. I imagine that that calculation will look something like this:
barEndTime = data.getValueAt((column + 1), 0
barScaleParameter = float(system.date.minutesBetween(barStartTime, barEndTime))*scaleParameter
Although looking at the untested code above, I can foresee that at some point an out of bounds exception is going to have to be handled in some way.
As pturmel and lrose indicated, this is not trivial development, and it will require quite a bit of time and experimentation to perfect. I find this problem interesting, so I'm inclined to mess around with it, and if I find the time, I probably will, but unfortunately, I don't have that kind of time today. If I do get some time to experiment, and I learn something worth sharing, I will post it here. When you figure this out, please do the same.
I did find some time to play around with this, and I applied the ideas I listed in the previous post to the example I gave above using a modified version of your dataset. I discovered that some padding is necessary to get the timestamps to render properly.
def configureChart(self, chart):
from org.jfree.chart.renderer.category import BarRenderer, StandardBarPainter
from java.awt import Color
chart.setTitle("Histogram")
padding = self.width * .25
data = self.data
width = self.width - padding
maxTimeValue = data.getRowCount()-1
chartStartTime = data.getValueAt(0,0)
chartEndTime = data.getValueAt(maxTimeValue,0)
totalTime = system.date.minutesBetween(chartStartTime,chartEndTime)
timeRatio = float(width)/float(totalTime)
scaleRatio = float(self.width)*0.018
class DynamicWidthRenderer(BarRenderer):
def calculateBarW0(BarRenderer, plot, orientation, dataArea, domainAxis, state, row, column):
barStartTime = data.getValueAt(column,0)
barStartingLocation = float(system.date.minutesBetween(chartStartTime, barStartTime))*timeRatio + padding * .5
try:
barEndTime = data.getValueAt((column + 1), 0)
barScaleParameter = float(system.date.minutesBetween(barStartTime, barEndTime))*scaleRatio
except:
barScaleParameter = float(width + padding - barStartingLocation)*scaleRatio
dataArea.width = barScaleParameter
BarRenderer.calculateBarWidth(plot, dataArea, 0, state)
return barStartingLocation
def getItemPaint(BarRenderer, row, column):
if column % 2 == 0:
return Color.orange
else:
return Color.yellow
chart.getCategoryPlot().setRenderer(DynamicWidthRenderer())
chart.getCategoryPlot().getRenderer().setBarPainter(StandardBarPainter())
chart.getCategoryPlot().getRenderer().setShadowVisible(False)
The scale calculation needs to be developed a little further, but the arbitrary number I stuck in there gets the bars pretty close in my designer.
The next step for your usage case will probably be to develop a label generator to add the work order numbers and time stamps to the bar chart. Just read the java doc I linked to and experiment with the methods listed.
It will probably be easiest to put your full dataset in a custom propery, and then just access or move the data from that dataset as needed to render the chart. In the example above, I only used two columns from your dataset to render the chart:
This was the result:
Shrinking the width of the chart and adding/removing bars was also tested with positive results:
one question to show work order number in bar. we need to create separate dataset?
No separate dataset is needed. You’re providing the renderer implementation so you get to determine how it processes the provided dataset.
This question was actually addressed earlier in the thread.
Yaa i understood i will try to implement that one
I have tired your same dataset but i am getting chart like this
Its it something i missing?
I have created category axis for X axis and y axis is number axis
and chart type is category chart
i have checked the console i am not getting any error
If the main dataset in a custom property, then it can be called in the same way the data is called from the chart's rendering dataset. For example, if the custom property is called rawData and the work order numbers are in the second column from the left [column index 1], then the script to access the first row of data would be:
self.rawData.getValueAt(0,1)
.
I like the idea of building a string for the label. It would be something like:
label = 'WO# ' + str(self.rawData.getValueAt(column,1)) + '\n' + str(system.date.format(barStartTime, "MM/dd/yy HH:mm:ss")) + '\nto\n' + str(system.date.format(barEndTime, "HH:mm:ss"))
The output should look like this:
I haven't worked on this at all, so I'm not sure if html would be applicable, but if so, then the code would probably look like this instead:
label = '<html><center>WO# ' + str(self.rawData.getValueAt(column,1)) + '<br>' + str(system.date.format(barStartTime, "MM/dd/yy HH:mm:ss")) + '<br>to<br>' + str(system.date.format(barEndTime, "HH:mm:ss"))
It looks like the code is working properly. You will probably want to use a print statement to troubleshoot your starting positions:
print str(column) + ', '+ str(chartStartTime) + ', '+ str(barStartTime) + ', '+ str(barStartingLocation)
This should help you identify any meridian reciprocations or other datetime anomalies in your dataset. Also, that scale ratio I threw in there was based on what worked with my dataset and not any reasonable algorithm. It will almost certainly need to be experimented with and developed further.
I played around with this a little more this morning, and came up with a more flexible approach to changing the tick labels.
First, I added the following method to the beginning of the configureChart extension function to hide the stock labels and create a set of new labels to take their place:
def dynamicLabelCreator(self, chart, rawData):
domainAxis = chart.getCategoryPlot().getDomainAxis()
for row, category in enumerate(chart.getCategoryPlot().getCategories()):
for component in self.getComponents():
if isinstance(component, PMILabel) and component.name == category:
self.remove(component)
domainAxis.setTickLabelPaint(category, Color(0,0,0,0))
customLabel = PMILabel()
labelName = category
customLabel.setName(labelName)
customLabel.setVisible(True)
self.add(customLabel)
rawData = self.rawData
dynamicLabelCreator(self, chart, rawData)
Then, I set the stock labels to a 90 degree orientation to give myself more room at the bottom of the chart, and while experimenting, I found that the bars rendered better if I set the lower margin of the category axis to .1
Finally, I modified the calculateW0 method to set the labels based on the center of the bar minus 1/2 the width of the label. Initially, the result looked good, but as I added more bars, the labels began to overlap:
Below is the full code. I am certain this will still require quite a bit of tweaking, changing, and refactoring to produce an acceptable product for your usage case, but there should be more than enough directions here to go off of. Good Luck!
def configureChart(self, chart):
from com.inductiveautomation.factorypmi.application.components import PMILabel
from java.awt import Color
from org.jfree.chart.renderer.category import BarRenderer, StandardBarPainter
from java.text import NumberFormat
from org.jfree.chart.labels import StandardCategoryItemLabelGenerator
def dynamicLabelCreator(self, chart, rawData):
domainAxis = chart.getCategoryPlot().getDomainAxis()
for row, category in enumerate(chart.getCategoryPlot().getCategories()):
for component in self.getComponents():
if isinstance(component, PMILabel) and component.name == category:
self.remove(component)
domainAxis.setTickLabelPaint(category, Color(0,0,0,0))
customLabel = PMILabel()
labelName = category
customLabel.setName(labelName)
customLabel.setVisible(True)
customLabel.rotation = 290
self.add(customLabel)
rawData = self.rawData
dynamicLabelCreator(self, chart, rawData)
chart.setTitle("Histogram")
data = self.data
padding = self.width * .25
width = self.width - padding
maxTimeValue = data.getRowCount()-1
chartStartTime = data.getValueAt(0,0)
chartEndTime = data.getValueAt(maxTimeValue,0)
totalTime = system.date.minutesBetween(chartStartTime,chartEndTime)
timeRatio = float(width)/float(totalTime)
scaleRatio = float(self.width)*0.018
class DynamicWidthRenderer(BarRenderer):
def calculateBarW0(BarRenderer, plot, orientation, dataArea, domainAxis, state, row, column):
from com.inductiveautomation.factorypmi.application.components import PMILabel
barStartTime = data.getValueAt(column,0)
barStartingLocation = float(system.date.minutesBetween(chartStartTime, barStartTime))*timeRatio + padding * .5
customLabel = self.getComponent(column)
try:
barEndTime = data.getValueAt((column + 1), 0)
barScaleParameter = float(system.date.minutesBetween(barStartTime, barEndTime))*scaleRatio
customLabel.text = '<html><b><center>WO# ' + str(self.rawData.getValueAt(column,1)) + '<br>' + str(system.date.format(barStartTime, "MM/dd HH:mm:ss")) + '<br>to ' + str(system.date.format(barEndTime, "HH:mm:ss"))
labelOffset = customLabel.width * .5
labelLocation = barStartingLocation+((float(system.date.minutesBetween(barStartTime, barEndTime))*.5)*timeRatio)-labelOffset
except:
barScaleParameter = float(width + padding - barStartingLocation)*scaleRatio
customLabel.text = '<html><b><center>WO# ' + str(self.rawData.getValueAt(column,1)) + '<br>' + str(system.date.format(barStartTime, "MM/dd HH:mm:ss")) + '<br>to ' + '[...]'
labelOffset = customLabel.width * .5
labelLocation = barStartingLocation - (.5 * labelOffset)
heightJustification = float(len(chart.getCategoryPlot().getCategories()[column]))*9
customLabel.setLocation(int(labelLocation), self.height - int(heightJustification))
customLabel.setSize(250,250)
dataArea.width = barScaleParameter
BarRenderer.calculateBarWidth(plot, dataArea, 0, state)
return barStartingLocation
def getItemPaint(BarRenderer, row, column):
if column % 2 == 0:
return Color.blue
else:
return Color.yellow
chart.getCategoryPlot().setRenderer(DynamicWidthRenderer())
numberFormat = NumberFormat.getInstance()
defaultFormat = StandardCategoryItemLabelGenerator.DEFAULT_LABEL_FORMAT_STRING
labelGenerator = StandardCategoryItemLabelGenerator(defaultFormat, numberFormat)
chart.getCategoryPlot().getRenderer().setBaseItemLabelGenerator(labelGenerator)
chart.getCategoryPlot().getRenderer().setBaseItemLabelsVisible(True)
chart.getCategoryPlot().getRenderer().setBarPainter(StandardBarPainter())
chart.getCategoryPlot().getRenderer().setShadowVisible(False)
I tired using area chart instead of bar chart. width of the bar is adjusting ok now
but line chart showing one hour difference from the actula time
I have attached my project file for your reference
can you please correct which place i am doing worng
BarWidth.zip (16.1 KB)
I don't see anything wrong with the way the data is actually displayed. The problem is the xTrace is getting its date from the bar dataset, which is actually accurate for the bar that the trace is over even if it's not accurate for the depicted domain position. Is there no way to correlate the dates between the two datasets?
Hi
I think data is populated correct only. I have checked with Mark mode. and checked the timing. It shows correct time
one more question
i have another dataset with WO number and i have populate the wO numbers in Bar chart
Note - both dataset timing are same only
I want to populate like this
You could experiment with adding a custom label generator. Expanding upon the configureChart extension function code that you are using to create that border, the code will end up looking something like this:
#def configureChart(self, chart):
from java.awt import BasicStroke, Color
from org.jfree.chart import labels
from org.jfree.chart.renderer.xy import XYStepAreaRenderer
#This will be your WO datset, so correct the path accordingly
labelData = self.parent.getComponent('Power Table 3').data
class CustomLabelGenerator(labels.XYItemLabelGenerator):
def generateLabel(self, dataset, series, item):
woNum = labelData.getValueAt(item, 1)
label = str(woNum)
return label
plot = chart.plot
plot.setOutlineStroke(BasicStroke(1))
plot.setOutlinePaint(Color.black)
renderer = plot.renderer
generator = CustomLabelGenerator()
renderer.setSeriesItemLabelGenerator(0, generator)
renderer.setSeriesItemLabelsVisible(0, True)
That chart is going to have multiple renderers, so it is possible that you will need to build a function that finds the specific renderer that you want to set the label generator to. It will look like this:
from org.jfree.chart.renderer import #The renderer you are looking for
def getRenderer(plot):
for renderer in range(plot.rendererCount):
plotRenderer = plot.getRenderer(renderer)
print plotRenderer
if isinstance(plotRenderer, #The renderer you are looking for):
return plotRenderer
plot = chart.plot
renderer = getRenderer(plot)
Since this is an XYPlot, XYText annotations would also be an option. Just convert the date in the dataset to millis for the domain axis location, and use the bar chart value for the range axis location. Here is an example of this that I developed for a status chart:
Hi have tired this script
#def configureChart(self, chart):
from java.awt import BasicStroke, Color
from org.jfree.chart import labels
from org.jfree.chart.renderer.xy import XYStepAreaRenderer
#This will be your WO datset, so correct the path accordingly
labelData = self.parent.getComponent('Power Table 3').data
class CustomLabelGenerator(labels.XYItemLabelGenerator):
def generateLabel(self, dataset, series, item):
woNum = labelData.getValueAt(item, 1)
label = str(woNum)
return label
plot = chart.plot
plot.setOutlineStroke(BasicStroke(1))
plot.setOutlinePaint(Color.black)
renderer = plot.renderer
generator = CustomLabelGenerator()
renderer.setSeriesItemLabelGenerator(0, generator)
renderer.setSeriesItemLabelsVisible(0, True)
this above script adding labels to line chart. Not in area bar
then tired your tooltip script for area bar
but that also not working.
I'm sorry you had trouble figuring out. I was able to adapt the tool tip script to your usage case. Here is the script:
#def configureChart(self, chart):
from java.awt import BasicStroke, Color
from org.jfree.chart.annotations import XYTextAnnotation
plot = chart.getPlot()
plot.setOutlineStroke(BasicStroke(1))
plot.setOutlinePaint(Color.black)
for annotation in plot.getAnnotations():
if isinstance(annotation, XYTextAnnotation):
plot.removeAnnotation(annotation)
#This will be your WO datset, so correct the path accordingly
labelData = self.parent.getComponent('Power Table 1').data
barData = self.parent.getComponent('Power Table').data
for row in range(0, labelData.rowCount - 1):
text = str(labelData.getValueAt(row, 1))
startTime = float(barData.getValueAt(row, 0).getTime())
endTime = float(barData.getValueAt(row + 1 , 0).getTime())
topBound = float(barData.getValueAt(row, 2))
bottomBound = float(barData.getValueAt(row, 1))
x = (startTime + endTime) * .5
y = (topBound + bottomBound) * .5
annotation = XYTextAnnotation(text, x, y)
plot.addAnnotation(annotation)
Here is the result:
working perfectly thank you soo much
@justinedwards.jle
one issue
last row label is not showing in bar
i changed for loop like this and checked - this error is coming
for row in range(labelData.getRowCount()):
14:40:15.528 [AWT-EventQueue-0] ERROR Vision.ClassicChart - Error invoking extension method.
org.python.core.PyException: java.lang.ArrayIndexOutOfBoundsException: java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
at org.python.core.Py.JavaError(Py.java:547)
at org.python.core.Py.JavaError(Py.java:538)
at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:192)
at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:208)
at org.python.core.PyObject.__call__(PyObject.java:494)
at org.python.core.PyObject.__call__(PyObject.java:498)
at org.python.core.PyMethod.__call__(PyMethod.java:156)
at org.python.pycode._pyx171.configureChart$1(<extension-method configureChart>:35)
at org.python.pycode._pyx171.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.setExtensionFunctions(PMIChart.java:466)
at com.inductiveautomation.factorypmi.designer.eventhandling.ComponentScriptEditor.applyChanges(ComponentScriptEditor.java:596)
at com.inductiveautomation.factorypmi.designer.eventhandling.ComponentScriptEditor$4.actionPerformed(ComponentScriptEditor.java:327)
at java.desktop/javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
at java.desktop/javax.swing.DefaultButtonModel.setPressed(Unknown Source)
at java.desktop/javax.swing.plaf.basic.BasicButtonListener.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)
Caused by: java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
at com.inductiveautomation.ignition.common.BasicDataset.getValueAt(BasicDataset.java:122)
at jdk.internal.reflect.GeneratedMethodAccessor480.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.python.core.PyReflectedFunction.__call__(PyReflectedFunction.java:190)
... 53 common frames omitted