package sushi.application.pages.monitoring.eventviews; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.tools.ant.util.DateUtils; import sushi.event.SushiEvent; import sushi.event.SushiEventType; import sushi.process.SushiProcessInstance; import sushi.visualisation.SushiEventView; import sushi.visualisation.SushiChartConfiguration; import com.googlecode.wickedcharts.highcharts.options.Axis; import com.googlecode.wickedcharts.highcharts.options.AxisType; import com.googlecode.wickedcharts.highcharts.options.ChartOptions; import com.googlecode.wickedcharts.highcharts.options.DateTimeLabelFormat; import com.googlecode.wickedcharts.highcharts.options.DateTimeLabelFormat.DateTimeProperties; import com.googlecode.wickedcharts.highcharts.options.Function; import com.googlecode.wickedcharts.highcharts.options.Options; import com.googlecode.wickedcharts.highcharts.options.SeriesType; import com.googlecode.wickedcharts.highcharts.options.Title; import com.googlecode.wickedcharts.highcharts.options.Tooltip; import com.googlecode.wickedcharts.highcharts.options.ZoomType; import com.googlecode.wickedcharts.highcharts.options.color.RgbaColor; import com.googlecode.wickedcharts.highcharts.options.series.Coordinate; import com.googlecode.wickedcharts.highcharts.options.series.CustomCoordinatesSeries; /** * This class prepares a splatter diagram that illustrates the occurence of events of certain eventtypes * This object can be used to create a chart-object with the wicked chart framework. */ public class EventViewOptions extends Options{ public SushiEventView eventView; private List<Coordinate<String, Number>> eventsWithoutProcessInstance = new ArrayList<Coordinate<String, Number>>(); private HashMap<SushiProcessInstance, List<Coordinate<String, Number>>> processSeriesData = new HashMap<SushiProcessInstance, List<Coordinate<String, Number>>>(); private HashMap<SushiEventType, Integer> mappingTypeToInt = new HashMap<SushiEventType, Integer>(); private static final long serialVersionUID = 1L; public EventViewOptions(SushiEventView configuration) { eventView = configuration; ChartOptions chartOptions = new ChartOptions(); chartOptions.setType(SeriesType.SCATTER); //enable zooming chartOptions.setZoomType(ZoomType.X); this.setChartOptions(chartOptions); this.setTitle(new Title("EventView (" + eventView.getTimePeriod().toString() + ")")); //X-Achse Axis xAxis = new Axis(); xAxis.setType(AxisType.DATETIME); DateTimeLabelFormat dateTimeLabelFormat = new DateTimeLabelFormat() .setProperty(DateTimeProperties.DAY, "%e.%m.%Y") .setProperty(DateTimeProperties.MONTH, "%m/%Y") .setProperty(DateTimeProperties.YEAR, "%Y"); xAxis.setDateTimeLabelFormats(dateTimeLabelFormat); this.setxAxis(xAxis); //Y-Achse Axis yAxis = new Axis(); yAxis.setTitle(new Title("EventTypes")); yAxis.setType(AxisType.LINEAR); //disable Decimals, because integers represent event types, decimals make no sense here yAxis.setAllowDecimals(false); yAxis.setMax(eventView.getEventTypes().size()); setyAxis(yAxis); //Tooltip Tooltip tooltip = new Tooltip(); tooltip.setFormatter(new Function( "return '<b>'+ this.series.name +'</b><br/>'+Highcharts.dateFormat('%e.%m.%Y', this.x);")); this.setTooltip(tooltip); //create a mapping from eventtypes to integer values, because in a splatter chart strings cannot be used in the y-axis generateMappingForEventTypes(); for (SushiEventType type : eventView.getEventTypes()) { sortEventsForEventType(type); } //add series for each process instance to diagram for (Entry<SushiProcessInstance, List<Coordinate<String, Number>>> seriesTuple : processSeriesData.entrySet()) { List<Coordinate<String, Number>> seriesData = seriesTuple.getValue(); CustomCoordinatesSeries<String, Number> series = new CustomCoordinatesSeries<String, Number>(); series.setName(seriesTuple.getKey().toString()); series.setData(seriesData); addSeries(series); } //add series for uncorrelated events CustomCoordinatesSeries<String, Number> series = new CustomCoordinatesSeries<String, Number>(); series.setName("uncorrelated"); series.setData(eventsWithoutProcessInstance); addSeries(series); }; private void generateMappingForEventTypes() { //save an integer value for each event type for (int i= 1; i <= eventView.getEventTypes().size(); i++) { mappingTypeToInt.put(eventView.getEventTypes().get(i-1), i); } } private void sortEventsForEventType(SushiEventType eventType){ for (SushiEvent event : SushiEvent.findByEventTypeAndTime(eventType, eventView.getTimePeriod())) { List<SushiProcessInstance> processInstances = event.getProcessInstances(); if (processInstances.isEmpty()) { //no process instance eventsWithoutProcessInstance.add(getCoordinate(event, eventType)); continue; } for (SushiProcessInstance instance : processInstances) { List<Coordinate<String, Number>> seriesData = processSeriesData.get(instance); if (seriesData.equals(null)) { //create new seriesDatea seriesData = new ArrayList<Coordinate<String, Number>>(); } seriesData.add(getCoordinate(event, eventType)); processSeriesData.put(instance, seriesData); } } } private Coordinate<String, Number> getCoordinate(SushiEvent event, SushiEventType eventType) { return new Coordinate<String, Number> (DateUtils.format(event.getTimestamp(), "'Date.UTC('yyyy, M, d, h, m, s')'"), mappingTypeToInt.get(eventType)); } /** * creates an explanation string that translates the mapping from integer values to event types * @return explanation string */ public String getExplanationString() { String explanation = ""; //sort event types Map<Integer, SushiEventType> intToEvent = invert(mappingTypeToInt); for (int i = 1; i <= intToEvent.size(); i++) { explanation += (i) + " : " + intToEvent.get(i).getTypeName() + "\t"; } return explanation; } /** * inverts a map, so that the former values become keys * @param map * @return inverted map */ private static <V, K> Map<V, K> invert(Map<K, V> map) { Map<V, K> inv = new HashMap<V, K>(); for (Entry<K, V> entry : map.entrySet()) inv.put(entry.getValue(), entry.getKey()); return inv; } }