Thank you Paul Griffith and pturmel for your insightful suggestions. Following your advice, I have successfully implemented a simulated Box and Whisker plot using JFreeChart within Ignition.
With some assistance from Gemini to refine the statistical logic and rendering, I’ve managed to generate a consistent 56-rack distribution with fixed color palettes and optimized axis layouts. While the simulation is working well, I recognize there is still room for improvement in terms of data binding efficiency and script modularity.
I’ve included my current scripts below for reference. I would appreciate any further feedback on how to make this more robust for a production environment.
def updateData(data, sample):
import random
headers = ["Rack", "Value"]
rows = []
# Iterate through 56 Racks
for i in range(1, 57):
rackName = "Rack %02d" % i
# Set a random base power (10 - 30 kW)
base_power = random.uniform(10, 30)
# Set random volatility (Standard deviation between 1.0 and 5.0)
# This ensures some racks have tight data (small box) while others are dispersed (large box)
variation = random.uniform(1.0, 5.0)
# Generate 20 data points per Rack
for _ in range(20):
val = random.gauss(base_power, variation)
# Ensure no negative power values and round to two decimal places
val = max(0, round(val, 2))
rows.append([rackName, val])
# Store the result in the 'data' dictionary for the Chart Script to call
data['rawIgnitionData'] = system.dataset.toDataSet(headers, rows)
def configureChart(data, chart):
from org.jfree.data.statistics import DefaultBoxAndWhiskerCategoryDataset
from org.jfree.chart.renderer.category import BoxAndWhiskerRenderer
from org.jfree.chart.axis import CategoryLabelPositions
from org.jfree.ui import RectangleInsets
import java.awt.Color
import java.awt.BasicStroke
import random
jfreeDS = DefaultBoxAndWhiskerCategoryDataset()
# --- 1. Data Generation ---
for i in range(1, 57):
rackName = "Rack %02d" % i
base = random.uniform(10.0, 30.0)
box_half = random.uniform(1.0, 5.0)
q1, q3 = base - box_half, base + box_half
med = base + random.uniform(-1.0, 1.0)
ext = random.uniform(2.0, 12.0)
low_limit = max(0.0, q1 - ext)
high_limit = q3 + ext
# Construct statistical points: [Min, Q1, Median, Q3, Max]
points = [low_limit, low_limit, q1, med, q3, high_limit, high_limit]
jfreeDS.add(points, "Power", rackName)
plot = chart.getCategoryPlot()
plot.setDataset(jfreeDS)
# --- 2. Renderer Settings (Custom Colorful Boxes - Fixed Palette) ---
class CustomRenderer(BoxAndWhiskerRenderer):
def __init__(self):
self.colors = []
# Use a local Random instance with a fixed seed (42)
# to ensure the color list remains consistent across refreshes.
color_gen = random.Random(42)
for _ in range(56):
self.colors.append(java.awt.Color(color_gen.randint(40, 180),
color_gen.randint(40, 180),
color_gen.randint(40, 180)))
def getItemPaint(self, row, column):
# Assign color based on column index; e.g., Rack 01 (index 0)
# will always use the first color in the list.
return self.colors[column % len(self.colors)]
renderer = CustomRenderer()
renderer.setFillBox(True)
renderer.setSeriesOutlinePaint(0, java.awt.Color.BLACK)
renderer.setSeriesOutlineStroke(0, java.awt.BasicStroke(1.2))
renderer.setArtifactPaint(java.awt.Color.WHITE) # Sets the color of the median line
try:
renderer.setMeanVisible(False)
renderer.setWhiskerWidth(0.8)
renderer.setUseOutlinePaintForWhiskers(True)
except:
# Graceful exit for older JFreeChart versions lacking these methods
pass
plot.setRenderer(renderer)
# --- 3. X-Axis Optimization (Domain Axis) ---
domainAxis = plot.getDomainAxis()
domainAxis.setLowerMargin(0.01)
domainAxis.setUpperMargin(0.01)
domainAxis.setCategoryMargin(0.05)
domainAxis.setCategoryLabelPositions(CategoryLabelPositions.DOWN_90)
domainAxis.setTickLabelFont(domainAxis.getTickLabelFont().deriveFont(7.0))
# --- 4. Y-Axis & Margin Optimization (Compressing left side space) ---
rangeAxis = plot.getRangeAxis()
rangeAxis.setRange(0.0, 45.0)
rangeAxis.setLabel("kW")
rangeAxis.setLabelFont(rangeAxis.getLabelFont().deriveFont(9.0))
rangeAxis.setTickLabelFont(rangeAxis.getTickLabelFont().deriveFont(9.0))
rangeAxis.setLabelInsets(RectangleInsets(0, 0, 0, 0))
chart.removeLegend()
# Set Plot Insets: (Top, Left, Bottom, Right)
plot.setInsets(RectangleInsets(10, 35, 60, 10))