Added arrival chart

This commit is contained in:
Robert Vokac 2024-03-10 15:06:21 +00:00
parent 7c4c26b0b6
commit 73e5ba2ceb
No known key found for this signature in database
GPG Key ID: 693D30BEE3329055
7 changed files with 340 additions and 44 deletions

View File

@ -185,6 +185,7 @@ Smileys can be colored or white-black (can be set in configuration)
## Todos
* New table: YEAR
* Custom arrival target
* Split to Maven modules
* Junit, Mockito, etc.
* Checkstyle

View File

@ -51,6 +51,12 @@
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>com.github.rjeschke</groupId>
<artifactId>txtmark</artifactId>

View File

@ -3,6 +3,7 @@ package org.nanoboot.utils.timecalc.entity;
import lombok.Getter;
import lombok.Setter;
import org.nanoboot.utils.timecalc.app.TimeCalcException;
import org.nanoboot.utils.timecalc.swing.common.ArrivalChartData;
import org.nanoboot.utils.timecalc.utils.common.TTime;
import java.time.LocalDate;
@ -35,7 +36,7 @@ public class WorkingDayForStats extends WorkingDay {
private final TTime departure;
public static void fillStatisticsColumns(List<WorkingDayForStats> list) {
if(list.isEmpty()) {
if (list.isEmpty()) {
//nothing to do
return;
}
@ -50,7 +51,8 @@ public class WorkingDayForStats extends WorkingDay {
map.put(w.getId(), w);
}
);
int year = years.stream().findFirst().orElseThrow(() -> new TimeCalcException("Set years is empty."));
int year = years.stream().findFirst().orElseThrow(
() -> new TimeCalcException("Set years is empty."));
Calendar cal7DaysAgo = Calendar.getInstance();
Calendar cal14DaysAgo = Calendar.getInstance();
@ -68,7 +70,7 @@ public class WorkingDayForStats extends WorkingDay {
int dayMaximum = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
for (int day = 1; day <= dayMaximum; day++) {
String id = WorkingDay.createId(year, month, day);
if(!map.containsKey(id)) {
if (!map.containsKey(id)) {
//nothing to do
continue;
}
@ -86,10 +88,10 @@ public class WorkingDayForStats extends WorkingDay {
String id28 = WorkingDay.createId(cal28DaysAgo);
String id56 = WorkingDay.createId(cal56DaysAgo);
System.out.println("id7=" + id7);
System.out.println("id14=" + id14);
System.out.println("id28=" + id28);
System.out.println("id56=" + id56);
// System.out.println("id7=" + id7);
// System.out.println("id14=" + id14);
// System.out.println("id28=" + id28);
// System.out.println("id56=" + id56);
if (map.containsKey(id7)) {
list7.remove(map.get(id7));
}
@ -103,7 +105,7 @@ public class WorkingDayForStats extends WorkingDay {
list56.remove(map.get(id56));
}
WorkingDayForStats wd = map.get(id);
if(!wd.isThisDayTimeOff()) {
if (!wd.isThisDayTimeOff()) {
Stream.of(list7, list14, list28, list56).forEach(l -> {
l.add(wd);
});
@ -128,15 +130,20 @@ public class WorkingDayForStats extends WorkingDay {
.mapToDouble(Double::doubleValue)
.average()
.orElse(0.0));
System.out.println(WorkingDay.createId(cal) + " 1 :: " + list7.size() );
System.out.println(WorkingDay.createId(cal) + " 2 :: " + list14.size() );
System.out.println(WorkingDay.createId(cal) + " 3 :: " + list28.size() );
System.out.println(WorkingDay.createId(cal) + " 4 :: " + list56.size() );
// System.out.println(
// WorkingDay.createId(cal) + " 1 :: " + list7.size());
// System.out.println(
// WorkingDay.createId(cal) + " 2 :: " + list14.size());
// System.out.println(
// WorkingDay.createId(cal) + " 3 :: " + list28.size());
// System.out.println(
// WorkingDay.createId(cal) + " 4 :: " + list56.size());
}
}
}
public static List<WorkingDayForStats> createList(List<WorkingDay> list) {
List<WorkingDayForStats> result = new ArrayList<>();
for (WorkingDay wd : list) {
@ -146,6 +153,48 @@ public class WorkingDayForStats extends WorkingDay {
return result;
}
public static ArrivalChartData toArrivalChartData(
List<WorkingDayForStats> list, double target, String startDate, String endDate) {
list.remove(0);
list.remove(0);
list.remove(0);
list.remove(0);
list.remove(0);
list.remove(0);
list.remove(0);
String[] days = new String[list.size()];
double[] arrival = new double[list.size()];
double[] ma7 = new double[list.size()];
double[] ma14 = new double[list.size()];
double[] ma28 = new double[list.size()];
double[] ma56 = new double[list.size()];
ArrivalChartData acd = new ArrivalChartData();
acd.setDays(days);
acd.setArrival(arrival);
acd.setMa7(ma7);
acd.setMa14(ma14);
acd.setMa28(ma28);
acd.setMa56(ma56);
acd.setTarget(0);
if(startDate != null && !startDate.isEmpty()) {
acd.setStartDate(startDate);
}
if(endDate != null && !endDate.isEmpty()) {
acd.setEndDate(endDate);
}
for(int i = 0; i < list.size(); i++) {
WorkingDayForStats wdfs = list.get(i);
days[i] = wdfs.getId();
arrival[i] = wdfs.isThisDayTimeOff() ? wdfs.getArrivalTimeMovingAverage7Days() - target : wdfs.getArrivalAsDouble() - target;
ma7[i] = wdfs.getArrivalTimeMovingAverage7Days() - target;
ma14[i] = wdfs.getArrivalTimeMovingAverage14Days() - target;
ma28[i] = wdfs.getArrivalTimeMovingAverage28Days() - target;
ma56[i] = wdfs.getArrivalTimeMovingAverage56Days() - target;
}
return acd;
}
public WorkingDayForStats(WorkingDay workingDay) {
this(workingDay.getId(),
workingDay.getYear(),
@ -161,15 +210,26 @@ public class WorkingDayForStats extends WorkingDay {
workingDay.isTimeOff());
}
public WorkingDayForStats(String id, int year, int month, int day, int arrivalHour, int arrivalMinute, int overtimeHour, int overtimeMinute, int workingTimeInMinutes, int pauseTimeInMinutes, String note, boolean timeOff) {
super(id, year, month, day, arrivalHour, arrivalMinute, overtimeHour, overtimeMinute, workingTimeInMinutes, pauseTimeInMinutes, note, timeOff);
this.arrival = this.isThisDayTimeOff() ? null : new TTime(arrivalHour, arrivalMinute);
this.overtime = this.isThisDayTimeOff() ? null : new TTime(overtimeHour, overtimeMinute);
this.work = this.isThisDayTimeOff() ? null : TTime.ofMinutes(workingTimeInMinutes);
this.pause = this.isThisDayTimeOff() ? null : TTime.ofMinutes(pauseTimeInMinutes);
this.departure = this.isThisDayTimeOff() ? null : this.arrival.add(work).add(pause).add(overtime);
public WorkingDayForStats(String id, int year, int month, int day,
int arrivalHour, int arrivalMinute, int overtimeHour,
int overtimeMinute, int workingTimeInMinutes,
int pauseTimeInMinutes, String note, boolean timeOff) {
super(id, year, month, day, arrivalHour, arrivalMinute, overtimeHour,
overtimeMinute, workingTimeInMinutes, pauseTimeInMinutes, note,
timeOff);
this.arrival = this.isThisDayTimeOff() ? null :
new TTime(arrivalHour, arrivalMinute);
this.overtime = this.isThisDayTimeOff() ? null :
new TTime(overtimeHour, overtimeMinute);
this.work = this.isThisDayTimeOff() ? null :
TTime.ofMinutes(workingTimeInMinutes);
this.pause = this.isThisDayTimeOff() ? null :
TTime.ofMinutes(pauseTimeInMinutes);
this.departure = this.isThisDayTimeOff() ? null :
this.arrival.add(work).add(pause).add(overtime);
this.departureHour = this.isThisDayTimeOff() ? -1 : departure.getHour();
this.departureMinute = this.isThisDayTimeOff() ? -1 : departure.getMinute();
this.departureMinute =
this.isThisDayTimeOff() ? -1 : departure.getMinute();
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month - 1);
@ -179,6 +239,8 @@ public class WorkingDayForStats extends WorkingDay {
}
public String getDayOfWeekAsString() {
return LocalDate.of(getYear(), getMonth() ,getDay()).getDayOfWeek().toString();
return LocalDate.of(getYear(), getMonth(), getDay()).getDayOfWeek()
.toString();
}
}

View File

@ -0,0 +1,153 @@
package org.nanoboot.utils.timecalc.swing.common;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Font;
/**
* @author Robert Vokac
* @since 20.02.2024
*/
public class ArrivalChart extends JPanel {
public ArrivalChart(ArrivalChartData data) {
this(data.getDays(), data.getArrival(), data.getMa7(), data.getMa14(), data.getMa28(), data.getMa56(), data.getTarget(),
data.getStartDate(), data.getEndDate());
}
public ArrivalChart(String[] days, double[] arrival, double[] ma7,
double[] ma14, double[] ma28, double[] ma56, double target, String startDate, String endDate) {
this.setLayout(null);
this.setVisible(true);
//
String title = "Arrivals";
CategoryDataset dataset =
createDataset(days, arrival, ma7, ma14, ma28, ma56, target, title, startDate, endDate);
JFreeChart chart = createChart(dataset, title);
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
chartPanel.setBackground(Color.white);
chartPanel.setDomainZoomable(true);
chartPanel.setMouseZoomable(true);
this.add(chartPanel);
chartPanel.setBounds(10, 10, 800, 400);
}
public static JFreeChart createChart(CategoryDataset dataset,
String title) {
JFreeChart chart = ChartFactory.createLineChart(
title,
"Date",
title,
dataset,
PlotOrientation.VERTICAL,
true,
true,
false
);
// Plot plot = chart.getPlot();
//
// XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
// chart.setBorderVisible(false);
// BiConsumer<Integer, Color> setSeries = (i, c) -> {
// renderer.setSeriesPaint(i, c);
// renderer.setSeriesStroke(i, new BasicStroke(1.0f));
// renderer.setSeriesShape(i, new Rectangle());
// };
// setSeries.accept(0, Color.GRAY);
// setSeries.accept(1, Color.BLACK);
// setSeries.accept(2, Color.RED);
// setSeries.accept(3, Color.GREEN);
// setSeries.accept(4, Color.BLUE);
// setSeries.accept(5, Color.ORANGE);
//
// plot.setRenderer(renderer);
// plot.setBackgroundPaint(Color.white);
//
// plot.setRangeGridlinesVisible(true);
// plot.setRangeGridlinePaint(Color.BLACK);
//
// plot.setDomainGridlinesVisible(true);
// plot.setDomainGridlinePaint(Color.BLACK);
// chart.getLegend().setFrame(BlockBorder.NONE);
chart.setTitle(new TextTitle(title,
new Font("Serif", Font.BOLD, 18)
)
);
return chart;
}
private CategoryDataset createDataset(String[] days, double[] arrival,
double[] ma7, double[] ma14, double[] ma28,
double[] ma56, double target, String title, String startDate, String endDate) {
if (startDate == null) {
startDate = "0-0-0";
}
if (endDate == null) {
endDate = "0-0-0";
}
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
String seriesArrival = "Arrival";
String seriesMa7 = "MA7";
String seriesMa14 = "MA14";
String seriesMa28 = "MA28";
String seriesMa56 = "MA56";
String seriesTarget = "Target";
String[] dayArray0 = startDate.split("-");
int year1 = Integer.valueOf(dayArray0[0]);
int month1 = Integer.valueOf(dayArray0[1]);
int day1 = Integer.valueOf(dayArray0[2]);
String[] dayArray1 = endDate.split("-");
int year2 = Integer.valueOf(dayArray1[0]);
int month2 = Integer.valueOf(dayArray1[1]);
int day2 = Integer.valueOf(dayArray1[2]);
for (int i = 0; i < days.length; i++) {
String date = days[i];
String[] dayArray = date.split("-");
int year = Integer.valueOf(dayArray[0]);
int month = Integer.valueOf(dayArray[1]);
int day = Integer.valueOf(dayArray[2]);
if(year1 != 0) {
if(month < month1) {
continue;
}
if(month == month1 && day < day1) {
continue;
}
}
if(year2 != 0) {
if(month > month2) {
continue;
}
if(month == month2 && day > day2) {
continue;
}
}
dataset.addValue(arrival[i], seriesArrival, date);
dataset.addValue(ma7[i], seriesMa7, date);
dataset.addValue(ma14[i], seriesMa14, date);
dataset.addValue(ma28[i], seriesMa28, date);
dataset.addValue(ma56[i], seriesMa56, date);
dataset.addValue(target, seriesTarget, date);
}
return dataset;
}
}

View File

@ -0,0 +1,26 @@
package org.nanoboot.utils.timecalc.swing.common;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* @author Robert
* @since 11.03.2024
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class ArrivalChartData {
private String[] days;
private double[] arrival;
private double[] ma7;
private double[] ma14;
private double[] ma28;
private double[] ma56;
private double target;
private String startDate;
private String endDate;
}

View File

@ -423,7 +423,7 @@ public class MainWindow extends TWindow {
});
workDaysButton.addActionListener(e -> {
if (workingDaysWindow == null) {
this.workingDaysWindow = new WorkingDaysWindow(workingDayRepository, time);
this.workingDaysWindow = new WorkingDaysWindow(workingDayRepository, time, 7d);
}
workingDaysWindow.setVisible(true);
});

View File

@ -2,6 +2,7 @@ package org.nanoboot.utils.timecalc.swing.common;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
@ -17,6 +18,7 @@ import org.nanoboot.utils.timecalc.persistence.api.WorkingDayRepositoryApi;
import org.nanoboot.utils.timecalc.swing.progress.Time;
import org.nanoboot.utils.timecalc.utils.common.NumberFormats;
import org.nanoboot.utils.timecalc.utils.common.TTime;
import org.nanoboot.utils.timecalc.utils.common.Utils;
/**
* @author Robert Vokac
@ -33,15 +35,20 @@ public class WorkingDaysWindow extends TWindow {
private final WorkingDayRepositoryApi workingDayRepository;
private final Time time;
private final JButton reloadButton;
private final double target;
private final TTextField startTextField;
private final TTextField endTextField;
private ArrivalChart arrivalChart;
private JTable table = null;
private final JScrollPane scrollPane;
public WorkingDaysWindow(WorkingDayRepositoryApi workingDayRepository, Time time) {
setSize(1100, 700);
public WorkingDaysWindow(WorkingDayRepositoryApi workingDayRepository, Time time, double target) {
setTitle("Work Days");
this.workingDayRepository = workingDayRepository;
this.time = time;
this.target = target;
int year = time.yearProperty.getValue();
this.setLayout(null);
@ -78,6 +85,7 @@ public class WorkingDaysWindow extends TWindow {
});
add(exitButton);
exitButton.setBounds(reloadButton.getX() + reloadButton.getWidth() + SwingUtils.MARGIN, reloadButton.getY(), 100, 30);
TLabel deleteLabel = new TLabel("Delete:");
add(deleteLabel);
deleteLabel.setBounds(exitButton.getX() + exitButton.getWidth() + SwingUtils.MARGIN, exitButton.getY(), 100, 30);
@ -85,21 +93,60 @@ public class WorkingDaysWindow extends TWindow {
add(deleteTextField);
deleteTextField.setBounds(deleteLabel.getX() + deleteLabel.getWidth() + SwingUtils.MARGIN, deleteLabel.getY(), 100, 30);
deleteTextField.addActionListener(e -> {
if(deleteTextField.getText().isEmpty()) {
//nothing to do
return;
}
if(!Utils.askYesNo(this, "Do you really want to delete this day: " + deleteTextField.getText(), "Day deletion")) {
return;
}
workingDayRepository.delete(deleteTextField.getText());
reloadButton.doClick();
});
TLabel startLabel = new TLabel("Start:");
add(startLabel);
startLabel.setBounds(deleteTextField.getX() + deleteTextField.getWidth() + SwingUtils.MARGIN, deleteTextField.getY(), 50, 30);
this.startTextField = new TTextField();
add(startTextField);
startTextField.setBounds(startLabel.getX() + startLabel.getWidth() + SwingUtils.MARGIN, startLabel.getY(), 100, 30);
startTextField.addActionListener(e -> {
reloadButton.doClick();
});
TLabel endLabel = new TLabel("End:");
add(endLabel);
endLabel.setBounds(startTextField.getX() + startTextField.getWidth() + SwingUtils.MARGIN, startTextField.getY(), 50, 30);
this.endTextField = new TTextField();
add(endTextField);
endTextField.setBounds(endLabel.getX() + endLabel.getWidth() + SwingUtils.MARGIN, endLabel.getY(), 100, 30);
endTextField.addActionListener(e -> {
reloadButton.doClick();
});
//
ArrivalChartData acd = new ArrivalChartData(new String[]{}, new double[]{}, new double[]{}, new double[]{}, new double[]{}, new double[]{},7d, null, null);
this.arrivalChart = new ArrivalChart(acd);
arrivalChart.setBounds(SwingUtils.MARGIN, years.getY() + years.getHeight()+ SwingUtils.MARGIN,
800,
400);
add(arrivalChart);
//
this.scrollPane
= new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setBounds(SwingUtils.MARGIN, years.getY() + years.getHeight()+ SwingUtils.MARGIN,
getWidth() - 3 * SwingUtils.MARGIN,
getHeight() - 4 * SwingUtils.MARGIN - 50);
scrollPane.setBounds(SwingUtils.MARGIN, arrivalChart.getY() + arrivalChart.getHeight()+ SwingUtils.MARGIN,
1000,
300);
add(scrollPane);
scrollPane.setViewportView(table);
loadYear(year, time);
setSize(scrollPane.getWidth() + 3 * SwingUtils.MARGIN, scrollPane.getY() + scrollPane.getHeight() + 4 * SwingUtils.MARGIN);
}
public void doReloadButtonClick() {
@ -162,10 +209,10 @@ public class WorkingDaysWindow extends TWindow {
list2.add(wdfs.getNote());
list2.add(wdfs.isTimeOff() ? YES : NO);
list2.add(QUESTION_MARK);
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage7Days() - 7));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage14Days() - 7));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage28Days() - 7));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage56Days() - 7));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage7Days() - target));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage14Days() - target));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage28Days() - target));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage56Days() - target));
} else {
list2.add(wdfs.getDayOfWeekAsString());
TTime overtime = new TTime(wdfs.getOvertimeHour(), wdfs.getOvertimeMinute());
@ -179,10 +226,10 @@ public class WorkingDaysWindow extends TWindow {
list2.add(wdfs.getNote());
list2.add(wdfs.isTimeOff() ? YES : NO);
list2.add(QUESTION_MARK);
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage7Days() - 7));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage14Days() - 7));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage28Days() - 7));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage56Days() - 7));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage7Days() - target));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage14Days() - target));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage28Days() - target));
list2.add(NumberFormats.FORMATTER_FIVE_DECIMAL_PLACES.format(wdfs.getArrivalTimeMovingAverage56Days() - target));
}
}
@ -249,18 +296,19 @@ public class WorkingDaysWindow extends TWindow {
// return c;
// }
};
scrollPane.setViewportView(table);
table.setBounds(30, 30, 750, 600);
//table.setDefaultRenderer(Object.class, new ColorRenderer());
reloadChart(WorkingDayForStats.toArrivalChartData(wdfsList, 7d, startTextField.getText(), endTextField.getText()));
}
public void reloadChart(ArrivalChartData newArrivalChartData) {
Rectangle bounds = this.arrivalChart.getBounds();
remove(this.arrivalChart);
this.arrivalChart = new ArrivalChart(newArrivalChartData);
add(arrivalChart);
arrivalChart.setBounds(bounds);
add(arrivalChart);
}
}