I finally developed a reliable way to achieve this result. Initially, I tried various label generators, but all I could get them to do is apply a single label to each series, so it appears that I was mistaken in my initial suggestion.
What ended up working quite well are XYTextAnnotations. Below is a script that iterates through the chart data, and calculates the appropriate place to put the annotations. Since timeDiff is what was requested in the original post, in the below example, I calculate it for each data block and apply it accordingly.
Here is the script:
from org.jfree.chart.annotations import XYTextAnnotation
from java.awt import Color
chartComponent = system.gui.getParentWindow(event).getComponentForPath('Root Container.Status Chart')
data = chartComponent.data
chart = chartComponent.chart
plot = chart.XYPlot
for annotation in plot.getAnnotations():
if isinstance(annotation, XYTextAnnotation):
plot.removeAnnotation(annotation)
def generateLabel(series, item, lastItemIndex):
x = series - 1
startDate = data.getValueAt(lastItemIndex, 0).getTime()
endDate = data.getValueAt(item, 0).getTime()
y = (float(startDate) + float(endDate)) * 0.5
timeDiff = str((float(endDate) - float(startDate)) / 1000) #Time diff is being calculated here, but this could be literally anything
annotation = XYTextAnnotation(timeDiff, x, y) #Timediff string added to chart
annotation.setPaint(Color.BLACK)
annotation.setFont(chartComponent.rangeAxisFont)
plot.addAnnotation(annotation)
for series in range(1, data.columnCount):
lastItemValue = data.getValueAt(0, series)
nextItemValue = data.getValueAt(1, series)
lastItemIndex = 0
for item in range(1, data.rowCount):
if lastItemValue != nextItemValue:
generateLabel(series, item, lastItemIndex)
if (item + 1) < data.rowCount:
lastItemValue = nextItemValue
nextItemValue = data.getValueAt(item + 1, series)
lastItemIndex = item
else:
generateLabel(series, item, lastItemIndex)
else:
if (item + 1) < data.rowCount:
nextItemValue = data.getValueAt(item + 1, series)
else:
generateLabel(series, item, lastItemIndex)
Here is the result:
Here is the result zoomed:
The script automatically removes all old annotations before applying new ones, so it can be fired as much as needed. I would probably stick this into a custom method and trigger this once when the window opens, and then retrigger it any time the data changes off of a property change event script.
Edit: Getting the rangeAxis upper bound is not needed, and it was simply overlooked artifact from the development process. That logic has been removed from the above script