/** * Copyright (c) 2011-2014, OpenIoT * * This file is part of OpenIoT. * * OpenIoT is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, version 3 of the License. * * OpenIoT 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with OpenIoT. If not, see <http://www.gnu.org/licenses/>. * * Contact: OpenIoT mailto: info@openiot.eu */ package org.openiot.ui.request.presentation.web.model.nodes.impl; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.logging.Level; import javax.faces.application.Application; import javax.faces.component.html.HtmlOutputText; import javax.faces.context.FacesContext; import javax.faces.event.AbortProcessingException; import javax.faces.event.ActionEvent; import javax.faces.event.ActionListener; import org.openiot.commons.osdspec.model.PresentationAttr; import org.openiot.commons.sdum.serviceresultset.model.SdumServiceResultSet; import org.openiot.commons.sparql.protocoltypes.model.QueryResult; import org.openiot.commons.sparql.result.model.Binding; import org.openiot.commons.sparql.result.model.Result; import org.openiot.ui.request.commons.logging.LoggerService; import org.openiot.ui.request.presentation.web.model.nodes.interfaces.VisualizationWidget; import org.openiot.ui.request.presentation.web.util.FaceletLocalization; import org.primefaces.component.commandlink.CommandLink; import org.primefaces.component.panel.Panel; import org.primefaces.context.RequestContext; import org.primefaces.model.chart.CartesianChartModel; import org.primefaces.model.chart.ChartSeries; public class LineChart implements VisualizationWidget { private enum XAxisType { Number, DateFromResultSet, DateFromObservation }; private org.primefaces.component.chart.line.LineChart widget; private HtmlOutputText emptyMessage; private Panel panel; private CartesianChartModel model; private String title; private String xAxisLabel; private XAxisType xAxisType; private String yAxisLabel; private int numSeries; private String[] seriesLabels; @Override public Panel createWidget(String serviceId, List<PresentationAttr> presentationAttributes) { FacesContext fc = FacesContext.getCurrentInstance(); Application application = fc.getApplication(); parseAttributes(presentationAttributes); // Instanciate linechart widget widget = (org.primefaces.component.chart.line.LineChart) application.createComponent(fc, "org.primefaces.component.chart.LineChart", "org.primefaces.component.chart.LineChartRenderer"); widget.setId("lineChart_" + System.nanoTime()); widget.setStyleClass("line-chart"); widget.setXaxisLabel(xAxisLabel); widget.setYaxisLabel(yAxisLabel); widget.setXaxisAngle(90); widget.setShowMarkers(true); widget.setLegendPosition("s"); if( ! xAxisType.Number.equals(xAxisType)){ widget.setExtender("lineChartExtender"); } widget.setRendered(false); // Instanciate a panel to host the widget panel = (Panel) application.createComponent(fc, "org.primefaces.component.Panel", "org.primefaces.component.PanelRenderer"); panel.setId("widget_panel_" + System.nanoTime()); panel.setHeader(title != null ? title : ""); panel.setClosable(false); panel.setToggleable(false); panel.setStyleClass("widget service_" + serviceId); panel.getChildren().add(widget); // Generate clear data link CommandLink clearLink = (CommandLink) application.createComponent(fc, "org.primefaces.component.CommandLink", "org.primefaces.component.CommandLinkRenderer"); clearLink.setAjax(true); clearLink.setOnclick("windowBlockUI.block();"); clearLink.setOncomplete("windowBlockUI.unblock();"); clearLink.setProcess("@this"); clearLink.setStyleClass("ui-panel-titlebar-icon ui-corner-all ui-state-default"); HtmlOutputText btn = new HtmlOutputText(); btn.setStyleClass("ui-icon ui-icon-arrowrefresh-1-w"); clearLink.getChildren().add(btn); clearLink.addActionListener(new ActionListener() { @Override public void processAction(ActionEvent arg0) throws AbortProcessingException { clearData(); } }); panel.getFacets().put("actions", clearLink); // Generate empty message emptyMessage = new HtmlOutputText(); emptyMessage.setValue(FaceletLocalization.getLocalizedResourceBundle().getString("UI_WIDGET_NO_DATA")); emptyMessage.setStyleClass("no-data"); emptyMessage.setRendered(true); panel.getChildren().add(emptyMessage); // Setup model model = new CartesianChartModel(); for (String seriesLabel : seriesLabels) { model.addSeries(new ChartSeries(seriesLabel)); } widget.setValue(model); return panel; } private boolean processGrouppedData(SdumServiceResultSet resultSet) { boolean triggerUpdate = true; Calendar cal = GregorianCalendar.getInstance(); @SuppressWarnings("unchecked") Map<Object, Number>[] seriesMaps = new Map[numSeries]; SimpleDateFormat[] seriesFormatters = new SimpleDateFormat[numSeries]; for (int i = 0; i < numSeries; i++) { seriesMaps[i] = new TreeMap<Object, Number>(); } for (QueryResult resultBlock : resultSet.getQueryResult()) { for (Result result : resultBlock.getSparql().getResults().getResult()) { Object xValue = null; Number yValue = null; Integer seriesIndex = null; boolean hasDay = false; boolean hasMonth = false; boolean hasYear = false; boolean hasHour = false; boolean hasMin = false; boolean hasSec = false; // Parse data for (Binding binding : result.getBinding()) { if (binding.getName().startsWith("x") && binding.getName().indexOf('_') != -1) { String dateComp = binding.getName().split("_")[1]; int dateCompValue = Integer.valueOf(binding.getLiteral().getContent()); if ("day".equals(dateComp)) { cal.set(Calendar.DAY_OF_MONTH, dateCompValue); hasDay = true; } else if ("month".equals(dateComp)) { // Note: Month field is 0-based cal.set(Calendar.MONTH, dateCompValue + 1); hasMonth = true; } else if ("year".equals(dateComp)) { cal.set(Calendar.YEAR, dateCompValue); hasYear = true; } else if ("hours".equals(dateComp)) { cal.set(Calendar.HOUR_OF_DAY, dateCompValue); hasHour = true; } else if ("minutes".equals(dateComp)) { cal.set(Calendar.MINUTE, dateCompValue); hasMin = true; } else if ("seconds".equals(dateComp)) { cal.set(Calendar.SECOND, dateCompValue); hasSec = true; } } else if (binding.getName().startsWith("y")) { // y values start at index 1 (y1, y2 e.t.c) seriesIndex = Integer.valueOf(binding.getName().substring(1)) - 1; yValue = Double.valueOf(binding.getLiteral().getContent()); } } xValue = cal.getTime(); // Update series if (seriesIndex == null || xValue == null || yValue == null) { continue; } // Update formatter if (seriesFormatters[seriesIndex] == null) { String dateFormat = ""; String timeFormat = ""; if (hasDay) { dateFormat += "dd"; } if (hasMonth) { dateFormat += (dateFormat.isEmpty() ? "" : "/") + "MM"; } if (hasYear) { dateFormat += (dateFormat.isEmpty() ? "" : "/") + "yyyy"; } if (hasHour) { timeFormat += "HH"; } if (hasMin) { timeFormat += (timeFormat.isEmpty() ? "" : ":") + "mm"; } if (hasSec) { timeFormat += (timeFormat.isEmpty() ? "" : ":") + "ss"; } dateFormat += " " + timeFormat; seriesFormatters[seriesIndex] = new SimpleDateFormat(dateFormat.trim()); } seriesMaps[seriesIndex].put(xValue, yValue); } } // Update series for (int i = 0; i < numSeries; i++) { ChartSeries series = model.getSeries().get(i); series.getData().clear(); for (Map.Entry<Object, Number> entry : seriesMaps[i].entrySet()) { series.getData().put(seriesFormatters[i].format(entry.getKey()), entry.getValue()); } } return triggerUpdate; } public boolean processUngrouppedData(SdumServiceResultSet resultSet) { boolean triggerUpdate = false; for (QueryResult resultBlock : resultSet.getQueryResult()) { for (Result result : resultBlock.getSparql().getResults().getResult()) { Object xValue = null; Number yValue = null; Integer seriesIndex = null; // Parse data for (Binding binding : result.getBinding()) { if ("x".equals(binding.getName())) { xValue = Double.valueOf(binding.getLiteral().getContent()); } else if (binding.getName().startsWith("y")) { // y values start at index 1 (y1, y2 e.t.c) seriesIndex = Integer.valueOf(binding.getName().substring(1)) - 1; yValue = Double.valueOf(binding.getLiteral().getContent()); } } if (XAxisType.DateFromResultSet.equals(xAxisType)) { xValue = new Date(); } // Update series if (seriesIndex == null || xValue == null || yValue == null) { continue; } ChartSeries series = model.getSeries().get(seriesIndex); series.getData().put(xValue, yValue); triggerUpdate = true; } } return triggerUpdate; } @Override public void processData(SdumServiceResultSet resultSet) { boolean triggerUpdate = false; if (XAxisType.DateFromObservation.equals(xAxisType)) { triggerUpdate = processGrouppedData(resultSet); } else { triggerUpdate = processUngrouppedData(resultSet); } if (triggerUpdate) { widget.setRendered(true); emptyMessage.setRendered(false); RequestContext requestContext = RequestContext.getCurrentInstance(); if (requestContext != null) { requestContext.update(panel.getClientId()); } } } private void parseAttributes(List<PresentationAttr> presentationAttributes) { seriesLabels = null; numSeries = 0; // Figure out number of series for (PresentationAttr attr : presentationAttributes) { if ("SERIES".equals(attr.getName())) { numSeries = Integer.valueOf(attr.getValue()); seriesLabels = new String[numSeries]; break; } } // for (PresentationAttr attr : presentationAttributes) { if ("TITLE".equals(attr.getName())) { title = attr.getValue(); } else if ("X_AXIS_LABEL".equals(attr.getName())) { xAxisLabel = attr.getValue(); } else if ("X_AXIS_TYPE".equals(attr.getName())) { if ("Date (result set)".equals(attr.getValue())) { xAxisType = XAxisType.DateFromResultSet; } else if ("Date (observation)".equals(attr.getValue())) { xAxisType = XAxisType.DateFromObservation; } else { xAxisType = XAxisType.Number; } } else if ("Y_AXIS_LABEL".equals(attr.getName())) { yAxisLabel = attr.getValue(); } else if (attr.getName().startsWith("SERIES_")) { int seriesIndex = Integer.valueOf(attr.getName().split("_")[1]); seriesLabels[seriesIndex] = attr.getValue(); } } } @Override public void clearData() { for (ChartSeries series : model.getSeries()) { series.getData().clear(); } widget.setRendered(false); emptyMessage.setRendered(true); RequestContext requestContext = RequestContext.getCurrentInstance(); if (requestContext != null) { requestContext.update(panel.getClientId()); } } }