How to adjust colors in the UI of OptaPlanner? - jfreechart

I am currently using the OptaPlanner's job schedule algorithm to create a certain planning. I want every execution mode used in the planning to be shown in a different color (instead of all different projects to be shown in different colors). Is it possible to implement this and if so, how? I have been searching through the code for a while now and have no idea how to do this.

This cannot be done easily with the Project Scheduling Swing application that's part of OptaPlanner project. It plots the data using JFreeChart and I couldn't find a simple way to associate metadata (like color) with the data that's being plotted.
You can override YIntervalRenderer behavior to return color of your choice based on data item's row (seriesIndex) and column (item's index in the series) but you have to keep the mapping between execution mode and [row, column] yourself, which is cumbersome.
Here's an example of modified ProjectJobSchedulingPanel that does the above:
public class ProjectJobSchedulingPanel extends SolutionPanel<Schedule> {
private static final Logger logger = LoggerFactory.getLogger(ProjectJobSchedulingPanel.class);
private static final Paint[] PAINT_SEQUENCE = DefaultDrawingSupplier.DEFAULT_PAINT_SEQUENCE;
public static final String LOGO_PATH = "/org/optaplanner/examples/projectjobscheduling/swingui/projectJobSchedulingLogo.png";
public ProjectJobSchedulingPanel() {
setLayout(new BorderLayout());
}
#Override
public void resetPanel(Schedule schedule) {
removeAll();
ChartPanel chartPanel = new ChartPanel(createChart(schedule));
add(chartPanel, BorderLayout.CENTER);
}
private JFreeChart createChart(Schedule schedule) {
YIntervalSeriesCollection seriesCollection = new YIntervalSeriesCollection();
Map<Project, YIntervalSeries> projectSeriesMap = new LinkedHashMap<>(
schedule.getProjectList().size());
ExecutionMode[][] executionModeByRowAndColumn = new ExecutionMode[schedule.getProjectList().size()][schedule.getAllocationList().size()];
YIntervalRenderer renderer = new YIntervalRenderer() {
#Override
public Paint getItemPaint(int row, int column) {
ExecutionMode executionMode = executionModeByRowAndColumn[row][column];
logger.info("getItemPaint: ExecutionMode [{},{}]: {}", row, column, executionMode);
return executionMode == null
? TangoColorFactory.ALUMINIUM_5
: PAINT_SEQUENCE[(int) (executionMode.getId() % PAINT_SEQUENCE.length)];
}
};
Map<Project, Integer> seriesIndexByProject = new HashMap<>();
int maximumEndDate = 0;
int seriesIndex = 0;
for (Project project : schedule.getProjectList()) {
YIntervalSeries projectSeries = new YIntervalSeries(project.getLabel());
seriesCollection.addSeries(projectSeries);
projectSeriesMap.put(project, projectSeries);
renderer.setSeriesShape(seriesIndex, new Rectangle());
renderer.setSeriesStroke(seriesIndex, new BasicStroke(3.0f));
seriesIndexByProject.put(project, seriesIndex);
seriesIndex++;
}
for (Allocation allocation : schedule.getAllocationList()) {
int startDate = allocation.getStartDate();
int endDate = allocation.getEndDate();
YIntervalSeries projectSeries = projectSeriesMap.get(allocation.getProject());
int column = projectSeries.getItemCount();
executionModeByRowAndColumn[seriesIndexByProject.get(allocation.getProject())][column] = allocation.getExecutionMode();
logger.info("ExecutionMode [{},{}] = {}", seriesIndexByProject.get(allocation.getProject()), column, allocation.getExecutionMode());
projectSeries.add(allocation.getId(), (startDate + endDate) / 2.0,
startDate, endDate);
maximumEndDate = Math.max(maximumEndDate, endDate);
}
NumberAxis domainAxis = new NumberAxis("Job");
domainAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
domainAxis.setRange(-0.5, schedule.getAllocationList().size() - 0.5);
domainAxis.setInverted(true);
NumberAxis rangeAxis = new NumberAxis("Day (start to end date)");
rangeAxis.setRange(-0.5, maximumEndDate + 0.5);
XYPlot plot = new XYPlot(seriesCollection, domainAxis, rangeAxis, renderer);
plot.setOrientation(PlotOrientation.HORIZONTAL);
// Uncomment this to use Tango color sequence instead of JFreeChart default sequence.
// This results in color per project mode.
// DefaultDrawingSupplier drawingSupplier = new DefaultDrawingSupplier(
// TangoColorFactory.SEQUENCE_1,
// DefaultDrawingSupplier.DEFAULT_FILL_PAINT_SEQUENCE,
// DefaultDrawingSupplier.DEFAULT_OUTLINE_PAINT_SEQUENCE,
// DefaultDrawingSupplier.DEFAULT_STROKE_SEQUENCE,
// DefaultDrawingSupplier.DEFAULT_OUTLINE_STROKE_SEQUENCE,
// DefaultDrawingSupplier.DEFAULT_SHAPE_SEQUENCE);
// plot.setDrawingSupplier(drawingSupplier);
return new JFreeChart("Project Job Scheduling", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
}
}
Result:
Another approach would be to implement JFreeChart interfaces and make custom Dataset and Renderer so that you could plot Allocations directly. Similar to the Gantt chart implementaion in JFreeChart.
Or write your custom UI from the ground up. Depends op how much effort you're willing to put into it :)

Related

JFreeChart - combining TimeSeries and OHLCDataset - second dataset is shifted

I'm having a Problem constructing a combined Candlestick Chart and Line Chart (both on the same plot). For the Candlesticks I use OHLCDataset and for the "moving average" linechart I use TimeSeries. However the Linecharts get drawn at the wrong timepoint along the axis. I have printed all DateTime Elements to make shure I did not set the wrong time or date but when printed they show exactly the date times they are supposed to. In the Chart however they start 6 hours too early. I first thought this would be a Timezone Issue but I'm setting the timezone on both to EST.
Here are the code snippets that create the dataset and assign it to the XYPlot
The OHLCDataset retrieving method (time[i] is a Date Object):
public OHLCDataset getOHLCDataset(){
OHLCSeries ohlcSeries = new OHLCSeries("Candlesticks");
for(int i=0; i<close.length; i++){
ohlcSeries.add(RegularTimePeriod.createInstance(Minute.class, time[i], TimeZone.getTimeZone("EST")), open[i], max[i], min[i], close[i]);
}
OHLCSeriesCollection ohlcCollection = new OHLCSeriesCollection();
ohlcCollection.addSeries(ohlcSeries);
return ohlcCollection;
}
The TimeSeries retrieving method (time[i] is a Date Object - the same as above):
public XYDataset getAverageXYDataset(int periods, int frame){
TimeSeries x = new TimeSeries("moving average " + periods + " periods");
if(frame>60){
for(int i=periods-1; i<close.length; i++){
double sum = 0;
for(int j=i; j>i-periods; j--){
sum += close[j];
}
x.add(RegularTimePeriod.createInstance(Hour.class, time[i], TimeZone.getTimeZone("EST")), sum/periods);
}
}else{
for(int i=periods-1; i<close.length; i++){
double sum = 0;
for(int j=i; j>i-periods; j--){
sum += close[j];
}
x.add(RegularTimePeriod.createInstance(Minute.class, time[i], TimeZone.getTimeZone("EST")), sum/periods);
}
}
return new TimeSeriesCollection(x);
}
The code that adds the datasets to the plot:
OHLCDataset dataset1 = dataset.getOHLCDataset();
XYDataset smallAverageDataset = dataset.getAverageXYDataset(20, period);
XYDataset bigAverageDataset = dataset.getAverageXYDataset(50, period);
// create the jfreechart - add candlestickdataset first
String title2 = dataset.getTime()[0] + " - " + dataset.getTime()[dataset.getTime().length-1];
JFreeChart chart = createChart(dataset1, title2);
// get the xyplot and set other datasets
chart.getXYPlot().setDataset(1, smallAverageDataset);
chart.getXYPlot().setDataset(2, bigAverageDataset);
Here is the method createChart:
private static JFreeChart createChart(final OHLCDataset dataset, String title) {
DateAxis xAxis = createXAxis();
NumberAxis yAxis = createYAxis();
MyCandlestickRenderer candlestickRenderer = createCandlestickRenderer();
plot = new XYPlot(dataset, xAxis, yAxis, candlestickRenderer);
JFreeChart chart = new JFreeChart(
title,
new Font("SansSerif", Font.BOLD, 24),
plot,
false
);
return chart;
}
And here is the createXAxis method:
private static DateAxis createXAxis(){
DateAxis domainAxis = new DateAxis();
domainAxis.setAutoRange(true);
domainAxis.setTickLabelsVisible(true);
domainAxis.setAutoTickUnitSelection(true);
return domainAxis;
}
I cannot figure out why there is such an offset on the linecharts but as you see I set the same Timezones for all Datasets.
Thanks in advance for the Help.
In JFreeChart's TimeSeries class, the x-values are time periods rather than specific points in time. The TimeSeriesCollection class presents this data via the XYDataset interface and has to choose a specific x-value for each data item. The setXPosition method (in TimeSeriesCollection) sets a flag that determines which point in the time period is used (start, middle or end).

DynamicReport - How to plot too many data points

I am trying to create LineChartReport using more than 1000 data points. Problem is that the X Axis should show the time stamp, and since there are too many data points, the data gets overlapped and no comprehensible data is shown. So I need help on following 2 points:
1. Limit the data points on X-Axis (only) to say 25. The number of data points for the graph/chart still remains at 1000
2. Rotate the Timestamp data by 90 degrees so that the Timestamp data is recorded correctly and not truncated.
Have tried to get the domain axis and manipulate it, like this, but the library does not allow that:
CategoryAxis domainAxis = chart.getCategoryPlot().getDomainAxis();
domainAxis.setMinorTickMarksVisible(false);
domainAxis.clearCategoryLabelToolTips();
chart.getCategoryPlot().getDataset().getColumnKeys()
CategoryDataset ds = chart.getCategoryPlot().getDataset();
List ls = ds.getColumnKeys();
List ls2 = new ArrayList();
int i = 0;
for (Iterator it = ls.iterator(); it.hasNext(); ) {
it.next();
if (i % 2 != 0) {
ls2.add(ls.get(i));
}
i++;
}
chart.getCategoryPlot().setDataset(ds);
Sample image with 10 data points appear here: https://drive.google.com/drive/u/0/folders/0B-m6SCJULOTRdHZ6cUwxX041SHM
Any suggestions ??
The below codes are based on DynamicReport 4.0.2. I didn't test them in other versions.
Regarding your first question, you want 1000 points of data, and just want the few data in line chart. In this case, you need to use the different data source for you data table and line chart.
Firstly, create the subreport for the data table and set up.
SubreportBuilder subreport = cmp.subreport(
report().setTemplate(Templates.reportTemplate)
.addColumn(
col.column("Name", "name", type.stringType()),
col.column("Counts", "value", type.integerType())
)
);
JasperReportBuilder reportContent = report();
subreport.setDataSource(allDatasource);
reportContent.summary(subreport, cmp.verticalGap(20));
Secondly, prepare another data source for line chart and set up.
reportContent.setTemplate(Templates.reportTemplate)
/* add title */
.title(title, subtitle,
/* add chart in the head of title */
cmp.verticalList(LINE_CHART)
/* set style */
.setStyle(stl.style().setBottomPadding(30).setTopPadding(30)))
/* set data source for line chart*/
.setDataSource(dataSource);
About your second question, you need to create customizer at first.
public class DynamicLineCustomizer implements DRIChartCustomizer, Serializable {
private static final long serialVersionUID = -8493880774698206000L;
#Override
public void customize(JFreeChart jFreeChart, ReportParameters reportParameters) {
CategoryPlot plot = jFreeChart.getCategoryPlot();
CategoryAxis domainAxis = plot.getDomainAxis();
domainAxis.setCategoryLabelPositions(CategoryLabelPositions
.createUpRotationLabelPositions(Math.PI / 6.0));
}
}
Then use this customizer in line chard builder.
LineChartBuilder lineChart = cht.lineChart()
.customizers(new DynamicLineCustomizer())
.setCategory(columns[0])
.series(createSeries(columns))
.setCategoryAxisFormat(cht.axisFormat().setLabel("TimeStamp"))
.seriesColors(seriesColors);
The line chart and data table will be like below:
This finally worked for me (hope it helps somebody) :
Ref: http://www.dynamicreports.org/forum/viewtopic.php?f=1&t=1046
private void build(String startDate, String endDate) {
TextColumnBuilder<Integer> i = col.column("I", "I", type.integerType());
TextColumnBuilder<Integer> b = col.column("B", "B", type.integerType());
TextColumnBuilder<Integer> t = col.column("T", "T", type.integerType());
TextColumnBuilder<Date> timeColumn = col.column("TimeStamp", "TimeStamp", type.dateType());
createDataSource(startDate, endDate);
try {
TimeSeriesChartBuilder timeSeriesChartBuilder1 = cht.timeSeriesChart();
timeSeriesChartBuilder1.series(cht.serie(b), cht.serie(t), cht.serie(i));
timeSeriesChartBuilder1.setShowShapes(false);
timeSeriesChartBuilder1.setDataSource(dataSource);
timeSeriesChartBuilder1.setTimePeriod(timeColumn);
timeSeriesChartBuilder1.setTimePeriodType(TimePeriod.SECOND);
timeSeriesChartBuilder1.setTitle("ABC Information");
JasperReportBuilder builder = report()
.summary(cht.multiAxisChart(timeSeriesChartBuilder1))
.setTemplate(Templates.reportTemplate)
.title(Templates.createTitleComponent("ABC Complete Info"))
;
builder.show();
} catch (Exception e) {
e.printStackTrace();
}
}

How can I pass a variable from one class to another class and use it as a value in JFreeChart [duplicate]

This question already has an answer here:
I have a bar chart which I want to update and I tried the revalidate and repaint methods but with no success
(1 answer)
Closed 8 years ago.
I want to generate a bar graph based from user inputs but, I tried passing it from my main class to the class where I coded my graph and it doesn't work.
here's a part of my main class. It's were I will get the value.
public double computeE1() {
double x1 = sFrame.s1;
double x2 = tFrame.t1;
double x3 = fFrame.f1;
E1 = 5.278 + ((-0.172)*x1) + ((-0.197)*x2) + ((-0.191)*x3);
return E1;
}
and here's my JFreeChart class
public class BarChart extends ApplicationFrame {
GUImain gui; //main class
public BarChart(final String title)
{
super(title);
final CategoryDataset dataset = createDataset();
final JFreeChart chart = createChart(dataset);
final ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setPreferredSize(new Dimension(500,270));
setContentPane(chartPanel);
}
private CategoryDataset createDataset()
{
double e1 = gui.E1;
double e2 = gui.E2;
double e3 = gui.E3;
double e4 = gui.E4;
DefaultCategoryDataset ds = new DefaultCategoryDataset();
ds.addValue(e1, "asdas", null);
ds.addValue(e2, "asdasda", null);
ds.addValue(e3, "sar", null);
ds.addValue(e4, "asda", null);
return ds;
}
This can be very confusing when you are just learning Java. The trick is to have a "control" class, which controls and coordinates the other elements of the program (ie analytics, ui, etc). Your your case, I'd probably try to include the class that has your analytics into the class that you're using for your UI... Is the analytics class stateless? https://softwareengineering.stackexchange.com/.../whats-the-difference-between-stateful-and-stateless

Plotting time in milliseconds - JFreeChart (Step Chart)

I'm trying to plot a step chart with the following properties:
x-axis: Time (ms) [Actual data contains this as a double value]
y-axis: Another value stored as an integer.
I'm filling up the dataset as follows:
private XYSeries populateStepChartDataSet(HashMap<Double, Integer> dataGrid){
XYSeries xySeries = new XYSeries("Step Plot", true, true);
if(dataGrid != null){
for (Double timeStamp : dataGrid.keySet()) {
xySeries.add(timeStamp, dataGrid.get(timeStamp));
}
}
return xySeries;
}
And the section where I create the plot is as follows:
final XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(populateStepChartDataSet(dspDataGrid));
final JFreeChart chart = ChartFactory.createXYStepChart(
title,
xAxisLabel, yAxisLabel,
dataset,
PlotOrientation.VERTICAL,
true, // legend
true, // tooltips
false // urls
);
What I expect is the plot to show time in ms at the x-axis but this value is getting converted to some weird time. Here's how the plot looks
Can someone please help me get back the timestamp in ms format for the x-axis?
It looks like the x Axis is formatting as a date one way of adressing this is to provide a NumberFormatOverride
Add this code after your chart is created:
XYPlot plot = (XYPlot)chart.getPlot();
plot.setDomainAxis(0, new NumberAxis());
NumberAxis axis = (NumberAxis) plot.getDomainAxis();
axis.setNumberFormatOverride( new NumberFormat(){
#Override
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
return new StringBuffer(String.format("%f", number));
}
#Override
public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
return new StringBuffer(String.format("%9.0f", number));
}
#Override
public Number parse(String source, ParsePosition parsePosition) {
return null;
}
} );
axis.setAutoRange(true);
axis.setAutoRangeIncludesZero(false);
You shold then get this chart:
FYI, use entrySet() whenever you iterate through a Map instead of iterating through the keySet() and then getting the value for each key.

Jfreechart get Mouse Co-ordinates

I have been trying to get current Mouse Co-ordinates in a JfreeChart and found that the following solution was working partially
JFreeChart get mouse coordinates
I have been using OHLC Dataset to draw the chart and while I could get the RangeAxis properly (meaning in the subplot values), I couldn't make anything out of the value recieved for the X-Axis from the above example.
I am sure that I am receiving the values in some other format (not the displayed date format), anyone could point out what I am doing wrong?
Got it solved after few hours of experiment. Here is the code for a complete MouseMotionListener. Just added this to the chartPanel and voila! - it works!
The chartY returns proper value of Y-Axis and the dateString returns complete date. Tried it in an OHLC Chart and seems proper.
MouseMotionListener mouselisten = new MouseMotionListener() {
public void mouseDragged(MouseEvent e) {
//
}
public void mouseMoved(MouseEvent e) {
Point2D p = e.getPoint();
Rectangle2D plotArea = chartPanel.getScreenDataArea();
XYPlot plot = (XYPlot) chart.getPlot(); // your plot
double chartX = plot.getDomainAxis().java2DToValue(p.getX(), plotArea, plot.getDomainAxisEdge());
double chartY = plot.getRangeAxis().java2DToValue(p.getY(), plotArea, plot.getRangeAxisEdge());
DecimalFormat dfT = new DecimalFormat("00");
GregorianCalendar gc = new GregorianCalendar();
long lDte = (long)chartX;
Date dtXX = new Date(lDte);
gc.setTime(dtXX);
String sDD = dfT.format(Double.valueOf(String.valueOf(gc.get(GregorianCalendar.DAY_OF_MONTH))));
String sMM = dfT.format(Double.valueOf(String.valueOf(gc.get(GregorianCalendar.MONTH)+1)));
String sYY = dfT.format(Double.valueOf(String.valueOf(gc.get(GregorianCalendar.YEAR))));
String dateString = sDD +"/"+ sMM +"/"+ sYY;
}
};

Resources