JFreeChart Gantt display subtasks' overlapping - jfreechart

I need to visualize overlapping of subtasks. In my case, a task is one person1, and subtasks of each task have descriptions "SignIn", "SignOut", "Click" (short-duration tasks), "Call" (long-duration task). What I want is to be able to: 1. See the overlap of 2 calls and see overlap of shortTasks and longTasks.
Something similar to this
My thoughts:
Lower the transparency of subtasks, then the overlapping areas will be rendered as darker. (In real life, if there is a green object with alpha 20 overlap or laying on top of a red object with alpha 100, we should see a render of green + red in the overlapped area?)
Render the shortTasks on a overlay above longTasks, so that the short tasks will displayed above the long task. <-- Still haven't figured out how to do this in code
I've followed this Changing saturation of subtasks, changing transparency in getItemPaint
Color rbg = clut.get(clutIndex);
return new Color(rbg.getRed(), rbg.getGreen(), rbg.getBlue(), 150);
but it's not exactly what I'm looking for, since the overlapped areas are not demonstrated.
private #NotNull IntervalCategoryDataset createDataset() {
final TaskSeries s1 = new TaskSeries("Scheduled");
Task person1 = new Task("Person1",
new SimpleTimePeriod(date(1, Calendar.APRIL, 2001),
date(30, Calendar.APRIL, 2001)));
// Subtasks
Task subTask1 = new Task("SignIn",
new SimpleTimePeriod(date(1, Calendar.APRIL, 2001),
date(2, Calendar.APRIL, 2001))); // no overlap
Task subTask2 = new Task("Call1",
new SimpleTimePeriod(date(5, Calendar.APRIL, 2001),
date(20, Calendar.APRIL, 2001))); // day 5 - 20
Task subTask3 = new Task("Click",
new SimpleTimePeriod(date(16, Calendar.APRIL, 2001),
date(17, Calendar.APRIL, 2001))); // overlap with call1 and call2
Task subTask4 = new Task("Call2",
new SimpleTimePeriod(date(15, Calendar.APRIL, 2001),
date(30, Calendar.APRIL, 2001))); // day 15 - 30
person1.addSubtask(subTask1);
person1.addSubtask(subTask2);
person1.addSubtask(subTask3);
person1.addSubtask(subTask4);
s1.add(person1);
final TaskSeriesCollection collection = new TaskSeriesCollection();
collection.add(s1);
return collection;
}
How to code using my approach or is there any better approaches to achieve this?
Thank you!

While GanttRenderer doesn't support AlphaComposite directly, you can adjust the hue, saturation and brightness; saturation is shown here, but ⍺ < 1 may be worth exploring. You can set the mode in your implementation of drawItem(); Xor is pictured. Use the AlphaCompositeDemo cited here to find the desired mode and colors.
private static class MyRenderer extends GanttRenderer {
…
#Override
public void drawItem(Graphics2D g2, CategoryItemRendererState state, Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis, ValueAxis rangeAxis, CategoryDataset dataset, int row, int column, int pass) {
g2.setComposite(AlphaComposite.Xor);
super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis, dataset, row, column, pass);
}
}

Related

How to adjust colors in the UI of OptaPlanner?

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 :)

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();
}
}

JFReeCharts XYPlot is it possible to change shapes inside legends

lt.setPosition(RectangleEdge.BOTTOM);
lt.setItemFont(old);
// get the range axis and add the $ symbol for the values
NumberAxis na = (NumberAxis) plot.getRangeAxis();
// set font
na.setLabelFont(fAxisFont);
na.setTickLabelFont(fAxisFont);
na.setAutoRange(true);
The above is my code snippet.Can anyone tell how to to chang shapes inside legends
I'm using the following workaround:
StandardXYItemRenderer renderer = new StandardXYItemRenderer() {
private static final long serialVersionUID = 0L;
#Override
public LegendItem getLegendItem(int datasetIndex, int series) {
LegendItem legend = super.getLegendItem(datasetIndex,
series);
return new LegendItem(legend.getLabel(),
legend.getDescription(), legend.getToolTipText(),
legend.getURLText(), Plot.DEFAULT_LEGEND_ITEM_BOX,
legend.getFillPaint());
}
};
...
plot.setRenderer(renderer);
Result:
A LegendItem infers its Shape from the corresponding series, which can be changed as shown here. This related example shows one way to render a LegendItem in an external component.

wpf 3d, multiple instances of a Model3DGroup, trying to optimize performance

My program shows a static scene containing several instances, at different places, of a 3d model, for example a tree. This model can be 'heavy', between 50000 and 100000 triangles, and the performance degrade quickly with the number of instances (performance is unacceptable with more than 10 instances).
My first code, without optimizations, looks like this :
public partial class Vue3D : Window
{
public ModelVisual3D modelVisual = new ModelVisual3D();
public Model3DGroup model3DGroup = new Model3DGroup();
public Vue3D()
{
...
modelVisual.Content = model3DGroup;
view1.Children.Add(modelVisual); // view1 = viewport3D
...
BuildScene();
}
void BuildScene()
{
Model3DGroup model = GetModel("tree");
foreach (Point3D pnt in <point collection>)
{
Model3DGroup modelClone = model.Clone();
Transform3DGroup tg = new Transform3DGroup();
tg.Children.Add(new TranslateTransform3D(pnt.X, pnt.Y, pnt.Z>));
modelClone.Transform = tg;
model3DGroup.Children.Add(modelClone);
}
}
}
For each instance i clone the complete model, and add the clone to the viewport after transformation.
Let's say the tree model contains 2 GeometryModel3D :
{mesh1 (20000 triangles), texture1} and {mesh2 (40000 triangles), texture2}.
If i show 10 trees, i'm adding 20 GeometryModel3D.
Since all these instances share the same 2 textures, i thought i could speed up things by creating only 2 GeometryModel3D : one with all the triangles associated with texture1, one with all the triangles associated with texture2. That's what i tried in this code :
void BuildScene()
{
List<Transform3DGroup>> listTg = new List<Transform3DGroup>();
foreach (Point3D pnt in <point collection>)
{
Transform3DGroup tg = new Transform3DGroup();
tg.Children.Add(new TranslateTransform3D(pnt.X, pnt.Y, pnt.Z>));
listTg.Add(tg);
}
Model3DGroup model = GetModel("tree");
foreach (GeometryModel3D gmodel in model.Children)
{
MeshGeometry3D mesh = gmodel.Geometry as MeshGeometry3D;
GeometryModel3D newgmodel = new GeometryModel3D();
newgmodel.Material = gmodel.Material;
newgmodel.BackMaterial = gmodel.BackMaterial;
MeshGeometry3D newmesh = new MeshGeometry3D();
newgmodel.Geometry = newmesh;
foreach (Transform3DGroup tg in listTg)
{
foreach (int indice in mesh.TriangleIndices)
newmesh.TriangleIndices.Add(indice + newmesh.Positions.Count);
foreach (Point3D p3 in mesh.Positions)
newmesh.Positions.Add(tg.Transform(p3));
if (mesh.TextureCoordinates != null)
{
foreach (System.Windows.Point p in mesh.TextureCoordinates)
newmesh.TextureCoordinates.Add(p);
}
}
newmesh.TriangleIndices.Freeze();
newmesh.TextureCoordinates.Freeze();
newmesh.Positions.Freeze();
newgmodel.Material.Freeze();
newgmodel.BackMaterial.Freeze();
newgmodel.Freeze();
model3DGroup.Children.Add(newgmodel);
}
}
And the performance is not better, in fact it's rather worse ... What am I missing here ? It seems that my assumption, i.e. minimizing the number GeometryModel3D would be beneficial, is false. I have read somewhere that in wpf 3d, the number of triangles has no big impact on performance, but that the number of textures was more critical. Here i have a big number of triangles but only 2 textures, only one ModelVisual3D, and all is frozen... Any suggestion ?
I have had good luck with detaching the mesh applying any transformations and then reattaching the mesh afterward. I got the idea from here: http://msdn.microsoft.com/en-us/library/bb613553.aspx

JFreechart: Displaying X axis with values after specific units

I am using jfreechart for displaying line graph.Now, on X axis it shows value for every (x,y) pair on chart.As a result the X axis has huge amount of values getting overlapped.I want to display few values eg after every 5 units or something like that.How is this possible using Jfreechart.
Before the NumberAxis of the chart's plot is drawn, its tick marks are refreshed. The result is a List that includes a NumberTick object for each tick mark of the axis.
By overriding the function NumberAxis.refreshTicks you can control how and if the marks will be shown.
For example, in the following code I get all tick marks and iterate through them looking for TickType.MAJOR. If the value of a major tick mark is not dividable by 5, it gets replaced by a minor tick mark.
As a result, only values dividable by 5 will be shown with their text label.
XYPlot plot = (XYPlot) chart.getPlot();
NumberAxis myAxis = new NumberAxis(plot.getDomainAxis().getLabel()) {
#Override
public List refreshTicks(Graphics2D g2, AxisState state,
Rectangle2D dataArea, RectangleEdge edge) {
List allTicks = super.refreshTicks(g2, state, dataArea, edge);
List myTicks = new ArrayList();
for (Object tick : allTicks) {
NumberTick numberTick = (NumberTick) tick;
if (TickType.MAJOR.equals(numberTick.getTickType()) &&
(numberTick.getValue() % 5 != 0)) {
myTicks.add(new NumberTick(TickType.MINOR, numberTick.getValue(), "",
numberTick.getTextAnchor(), numberTick.getRotationAnchor(),
numberTick.getAngle()));
continue;
}
myTicks.add(tick);
}
return myTicks;
}
};
plot.setDomainAxis(myAxis);

Resources