/**
* File ./src/main/java/de/lemo/dms/processing/BoxPlotGeneratorForDates.java
* Lemo-Data-Management-Server for learning analytics.
* Copyright (C) 2015
* Leonard Kappe, Andreas Pursian, Sebastian Schwarzrock, Boris Wenzlaff
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
/**
* File ./main/java/de/lemo/dms/processing/BoxPlotGeneratorForDates.java
* Date 2013-01-24
* Project Lemo Learning Analytics
*/
package de.lemo.dms.processing;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import de.lemo.dms.processing.resulttype.BoxPlot;
/**
* Generates Boxplot objects for dates and accumulate them to weeks
* @author Boris Wenzlaff
* @author Sebastian Schwarzrock
*
*/
public class BoxPlotGeneratorForDates {
private static final int WEEK = 7;
private static final int HOUR = 24;
private static final int TIDE = 28;
private static final int THOU = 1000;
private final Map<Integer, HashMap<Date, Long>> weekmap;
private final Map<Integer, HashMap<Date, Long>> hourmap;
private final Map<Integer, HashMap<Date, Long>> tidemap;
private final Set<Date> dates;
private Date min = null, max = null;
private final int[] weekdays;
public BoxPlotGeneratorForDates() {
// initialisieren
this.weekmap = new HashMap<Integer, HashMap<Date, Long>>();
this.hourmap = new HashMap<Integer, HashMap<Date, Long>>();
this.tidemap = new HashMap<Integer, HashMap<Date, Long>>();
this.dates = new HashSet<Date>();
this.weekdays = new int[WEEK];
// wochen
for (int i = 0; i < WEEK; i++) {
this.weekmap.put(Integer.valueOf(i), new HashMap<Date, Long>());
this.weekdays[i] = 0;
}
// stunden
for (int i = 0; i < HOUR; i++) {
this.hourmap.put(Integer.valueOf(i), new HashMap<Date, Long>());
}
// tides
for (int i = 0; i < TIDE; i++) {
this.tidemap.put(Integer.valueOf(i), new HashMap<Date, Long>());
}
}
/**
* add new day to the calculation list
*
* @param timestamp
* timestamp of the data access
*/
@SuppressWarnings("deprecation")
public void addAccess(final long timestamp) {
// datum filtern
final Calendar cal = Calendar.getInstance();
// TODO Länge des TimeStamps prüfen
final Date date = new Date(timestamp * THOU);
cal.setTime(date);
// generate key for day
final Date keydate = new Date(timestamp * THOU);
keydate.setHours(0);
keydate.setMinutes(0);
keydate.setSeconds(0);
// tag und stunde des zugriffs ermitteln
int day = 0, hour = 0, tide = 0;
// stunde
hour = cal.get(Calendar.HOUR_OF_DAY);
// tag
final int cday = cal.get(Calendar.DAY_OF_WEEK);
// TAG 0 = montag
switch (cday) {
case Calendar.MONDAY:
day = 0;
break;
case Calendar.TUESDAY:
day = 1;
break;
case Calendar.WEDNESDAY:
day = 2;
break;
case Calendar.THURSDAY:
day = 3;
break;
case Calendar.FRIDAY:
day = 4;
break;
case Calendar.SATURDAY:
day = 5;
break;
case Calendar.SUNDAY:
day = 6;
break;
}
tide = (4 * day + (hour / 6)) % TIDE;
// add week
if (this.weekmap.get(day).containsKey(keydate)) {
long val = this.weekmap.get(day).get(keydate);
val += 1;
this.weekmap.get(day).put(keydate, new Long(val));
}
else {
this.weekmap.get(day).put(keydate, new Long(1));
}
// add hour
if (this.hourmap.get(hour).containsKey(keydate)) {
long val = this.hourmap.get(hour).get(keydate);
val += 1;
this.hourmap.get(hour).put(keydate, new Long(val));
}
else {
this.hourmap.get(hour).put(keydate, new Long(1));
}
// add tide
if (this.tidemap.get(tide).containsKey(keydate)) {
long val = this.tidemap.get(tide).get(keydate);
val += 1;
this.tidemap.get(tide).put(keydate, new Long(val));
}
else {
this.tidemap.get(tide).put(keydate, new Long(1));
}
this.addDate(keydate);
}
public BoxPlot[] calculateResult() {
BoxPlot[] week = new BoxPlot[WEEK];
BoxPlot[] hour = new BoxPlot[HOUR];
BoxPlot[] tide = new BoxPlot[TIDE];
final BoxPlot[] result = new BoxPlot[WEEK + HOUR + TIDE];
week = this.calculateResultForWeek();
hour = this.calculateResultForHour();
tide = this.calculateResultForTide();
for (int i = 0; i < WEEK; i++) {
result[i] = week[i];
}
for (int i = WEEK; i < 31; i++) {
result[i] = hour[i - WEEK];
}
for (int i = WEEK + HOUR; i < WEEK + HOUR + TIDE; i++) {
result[i] = tide[i - (WEEK + HOUR)];
}
return result;
}
/**
* @return 7 boxplots with the result of a seven day week monday - sunday
*/
public BoxPlot[] calculateResultForWeek() {
final BoxPlot[] resultList = new BoxPlot[WEEK];
for (int i = 0; i < 7; i++) {
Long[] days = null;
final HashMap<Date, Long> currentday = this.weekmap.get(i);
final Date[] dates = currentday.keySet().toArray(new Date[currentday.size()]);
final Long[] sdays = currentday.values().toArray(new Long[currentday.size()]);
// berechnen wie viele tage tatsächlich eingetragen werden müssen
final long dayCount = currentday.size();
if (dayCount <= 0) {
final BoxPlot bp = new BoxPlot();
bp.setName(this.getNameForDay(i));
resultList[i] = bp;
continue;
}
Arrays.sort(dates);
final Date firstDay = dates[0];
long diff = this.weekDiff(firstDay, this.max) + 1;
diff = diff - dayCount;
int addCounter = 0;
if (diff >= 1) {
days = new Long[(int) (currentday.size() + diff)];
for (int j = 0; j < diff; j++) {
days[j] = 0L;
addCounter++;
}
}
else {
days = new Long[currentday.size()];
}
// vollständige liste mit allen daten und den leertagen
for (int j = 0; j < currentday.size(); j++) {
days[j + addCounter] = sdays[j];
}
final BoxPlot bp = this.calcBox(days);
bp.setName(this.getNameForDay(i));
resultList[i] = bp;
}
return resultList;
}
/**
* @return list with boxplots for the hours 0 - 24
*/
public BoxPlot[] calculateResultForHour() {
final BoxPlot[] resultList = new BoxPlot[HOUR];
for (int i = 0; i < HOUR; i++) {
Long[] hours = null;
final HashMap<Date, Long> currenthour = this.hourmap.get(i);
final Date[] dates = currenthour.keySet().toArray(new Date[currenthour.size()]);
final Long[] shours = currenthour.values().toArray(new Long[currenthour.size()]);
// berechnen wie viele tatsächlich tatsächlich eingetragen werden müssen
final long hourCount = currenthour.size();
if (hourCount <= 0) {
final BoxPlot bp = new BoxPlot();
bp.setName(Integer.toString(i));
resultList[i] = bp;
continue;
}
Arrays.sort(dates);
final Date firstDay = dates[0];
long diff = this.hourDiff(firstDay, this.max) + 1;
diff = diff - hourCount;
int addCounter = 0;
if (diff >= 1) {
hours = new Long[(int) (currenthour.size() + diff)];
for (int j = 0; j < diff; j++) {
hours[j] = 0L;
addCounter++;
}
}
else {
hours = new Long[currenthour.size()];
}
// vollständige liste mit allen daten und den leertagen
for (int j = 0; j < currenthour.size(); j++) {
hours[j + addCounter] = shours[j];
}
final BoxPlot bp = this.calcBox(hours);
bp.setName(Integer.toString(i));
resultList[i] = bp;
}
return resultList;
}
/**
* @return list with boxplots for the tides 0 - 28
*/
public BoxPlot[] calculateResultForTide() {
final BoxPlot[] resultList = new BoxPlot[TIDE];
for (int i = 0; i < TIDE; i++) {
Long[] tides = null;
final HashMap<Date, Long> currenttide = this.tidemap.get(i);
final Date[] dates = currenttide.keySet().toArray(new Date[currenttide.size()]);
final Long[] shours = currenttide.values().toArray(new Long[currenttide.size()]);
// berechnen wie viele tatsächlich tatsächlich eingetragen werden müssen
final long tideCount = currenttide.size();
if (tideCount <= 0) {
Long[] ar = new Long[1];
ar[0] = 0L;
final BoxPlot bp = this.calcBox(ar);
bp.setName(Integer.toString(i));
resultList[i] = bp;
continue;
}
Arrays.sort(dates);
final Date firstDay = dates[0];
long diff = this.tideDiff(firstDay, this.max) + 1;
diff = diff - tideCount;
int addCounter = 0;
if (diff >= 1) {
tides = new Long[(int) (currenttide.size() + diff)];
for (int j = 0; j < diff; j++) {
tides[j] = 0L;
addCounter++;
}
}
else {
tides = new Long[currenttide.size()];
}
// vollständige liste mit allen daten und den leertagen
for (int j = 0; j < currenttide.size(); j++) {
tides[j + addCounter] = shours[j];
}
final BoxPlot bp = this.calcBox(tides);
bp.setName(Integer.toString(i));
resultList[i] = bp;
}
return resultList;
}
// berechnen der boxplot werte
private BoxPlot calcBox(final Long[] list) {
final BoxPlot result = new BoxPlot();
// ---SORTIEREN
java.util.Arrays.sort(list);
// ---MEDIAN
// gerade oder ungerade
if ((list.length % 2) == 0) {
// gerade
int uw, ow;
uw = (list.length / 2) - 1;
ow = uw + 1;
Double m = new Double((list[uw] + list[ow]));
m = m / 2;
result.setMedian(m);
}
else {
// ungerade
result.setMedian(list[(list.length / 2)].doubleValue());
}
// ---QUARTILE
// 1 & 2Quartile
long q1, q2;
if (list.length == 1) {
q1 = 1;
q2 = 1;
}
else {
q1 = Math.round(0.25 * ((list.length) + 1));
q2 = Math.round(0.75 * ((list.length) + 1));
}
final Long i1 = new Long(q1 - 1);
final Long i2 = new Long(q2 - 1);
result.setLowerQuartil(list[i1.intValue()].doubleValue());
result.setUpperQuartil(list[i2.intValue()].doubleValue());
result.setUpperWhisker(list[list.length - 1].doubleValue());
result.setLowerWhisker(list[0].doubleValue());
return result;
}
// für das aktuallisieren des min und max dates
private void addDate(final Date date) {
if (this.min == null) {
this.min = date;
}
if (this.max == null) {
this.max = date;
}
if (date.after(this.max)) {
this.max = date;
}
if (date.before(this.min)) {
this.min = date;
}
this.dates.add(date);
}
private long weekDiff(final Date first, final Date last) {
long diff = 0;
final Calendar cal1 = new GregorianCalendar();
final Calendar cal2 = new GregorianCalendar();
cal1.setTime(first);
cal2.setTime(last);
// Differenz in ms
final long time = cal2.getTime().getTime() - cal1.getTime().getTime();
// Differenz in Tagen
final long days = Math.round(time / (24. * 60. * 60. * 1000.));
diff = Math.round(days / WEEK);
return diff;
}
private long hourDiff(final Date first, final Date last) {
long diff = 0;
final Calendar cal1 = new GregorianCalendar();
final Calendar cal2 = new GregorianCalendar();
cal1.setTime(first);
cal2.setTime(last);
// Differenz in ms
final long time = cal2.getTime().getTime() - cal1.getTime().getTime();
// Differenz in Tagen
final long days = Math.round(time / (24. * 60. * 60. * 1000.));
diff = Math.round(days);
return diff;
}
private long tideDiff(final Date first, final Date last) {
long diff = 0;
final Calendar cal1 = new GregorianCalendar();
final Calendar cal2 = new GregorianCalendar();
cal1.setTime(first);
cal2.setTime(last);
// Differenz in ms
final long time = cal2.getTime().getTime() - cal1.getTime().getTime();
// Differenz in Tagen
final long days = Math.round(time / (24. * 60. * 60. * 1000.));
diff = Math.round(days / WEEK);
return diff;
}
private String getNameForDay(final int day) {
String name = "";
switch (day) {
case 0:
name = "monday";
break;
case 1:
name = "tuesday";
break;
case 2:
name = "wednesday";
break;
case 3:
name = "thursday";
break;
case 4:
name = "friday";
break;
case 5:
name = "saturday";
break;
case 6:
name = "sunday";
break;
default:
name = "";
}
return name;
}
}