Index: hci/trunk/eneraptor-web-app/target/eneraptor-web-app-0.4.war
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: hci/trunk/eneraptor-web-app/web-app/css/style.css
===================================================================
--- hci/trunk/eneraptor-web-app/web-app/css/style.css	(revision 62)
+++ hci/trunk/eneraptor-web-app/web-app/css/style.css	(revision 63)
@@ -413,7 +413,8 @@
 
 .statistics-main{background:transparent url(../img/icons/eye.png) no-repeat left;}
 .statistics-graphs{background:transparent url(../img/icons/chart_curve.png) no-repeat left;}
-.statistics-newGraph{background:transparent url(../img/icons/chart_curve_add.png) no-repeat left;}
+.statistics-newGraph{background:transparent url(../img/icons/chart_curve_single_add.png) no-repeat left;}
+.statistics-newCombGraph{background:transparent url(../img/icons/chart_curve_add.png) no-repeat left;}
 .statistics-newReport{background:transparent url(../img/icons/report.png) no-repeat left;}
 
 .actions-main{background:transparent url(../img/icons/eye.png) no-repeat left;}
Index: hci/trunk/eneraptor-web-app/web-app/img/icons/chart_curve_single.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: hci/trunk/eneraptor-web-app/web-app/img/icons/chart_curve_single.png
===================================================================
--- hci/trunk/eneraptor-web-app/web-app/img/icons/chart_curve_single.png	(revision 0)
+++ hci/trunk/eneraptor-web-app/web-app/img/icons/chart_curve_single.png	(revision 63)

Property changes on: hci/trunk/eneraptor-web-app/web-app/img/icons/chart_curve_single.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: hci/trunk/eneraptor-web-app/web-app/img/icons/chart_curve_single_add.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: hci/trunk/eneraptor-web-app/web-app/img/icons/chart_curve_single_add.png
===================================================================
--- hci/trunk/eneraptor-web-app/web-app/img/icons/chart_curve_single_add.png	(revision 0)
+++ hci/trunk/eneraptor-web-app/web-app/img/icons/chart_curve_single_add.png	(revision 63)

Property changes on: hci/trunk/eneraptor-web-app/web-app/img/icons/chart_curve_single_add.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: hci/trunk/eneraptor-web-app/web-app/img/icons/blank.png
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Index: hci/trunk/eneraptor-web-app/web-app/img/icons/blank.png
===================================================================
--- hci/trunk/eneraptor-web-app/web-app/img/icons/blank.png	(revision 0)
+++ hci/trunk/eneraptor-web-app/web-app/img/icons/blank.png	(revision 63)

Property changes on: hci/trunk/eneraptor-web-app/web-app/img/icons/blank.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: hci/trunk/eneraptor-web-app/grails-app/services/com/eneraptor/hci/HciLogicCommService.groovy
===================================================================
--- hci/trunk/eneraptor-web-app/grails-app/services/com/eneraptor/hci/HciLogicCommService.groovy	(revision 62)
+++ hci/trunk/eneraptor-web-app/grails-app/services/com/eneraptor/hci/HciLogicCommService.groovy	(revision 63)
@@ -24,10 +24,16 @@
 			reply['status'] = "error"
 			reply['content'] = "Unknown host: "
 			reply['content'] += e.getMessage()
+			fromLogic?.close()
+			toLogic?.close()
+			logicSocket?.close()
 			return reply
 		} catch (IOException e) {
 			reply['status'] = "error"
 			reply['content'] += e.getMessage()
+			fromLogic?.close()
+			toLogic?.close()
+			logicSocket?.close()
 			return reply
 		}
 		
@@ -40,6 +46,10 @@
 			reply['content'] += it.toString() + "\n"
 		}
 		
+		fromLogic.close()
+		toLogic.close()
+		logicSocket.close()
+		
 		return reply
 
     }
Index: hci/trunk/eneraptor-web-app/grails-app/services/com/eneraptor/hci/MainMenuService.groovy
===================================================================
--- hci/trunk/eneraptor-web-app/grails-app/services/com/eneraptor/hci/MainMenuService.groovy	(revision 62)
+++ hci/trunk/eneraptor-web-app/grails-app/services/com/eneraptor/hci/MainMenuService.groovy	(revision 63)
@@ -44,7 +44,8 @@
 		statisticsSubMenu = new ArrayList()
 		statisticsSubMenu.add(new MenuItem("Overview","statistics","main"))
 		statisticsSubMenu.add(new MenuItem("Saved graphs","statistics","graphs"))
-		statisticsSubMenu.add(new MenuItem("Create a new graph","statistics","newGraph"))
+		statisticsSubMenu.add(new MenuItem("Create a new single device graph","statistics","newGraph"))
+		statisticsSubMenu.add(new MenuItem("Create a new combined graph","statistics","newCombGraph"))
 		statisticsSubMenu.add(new MenuItem("Make reports","statistics","newReport"))
 		
 		actionsSubMenu = new ArrayList()
Index: hci/trunk/eneraptor-web-app/grails-app/services/com/eneraptor/hci/GraphDataService.groovy
===================================================================
--- hci/trunk/eneraptor-web-app/grails-app/services/com/eneraptor/hci/GraphDataService.groovy	(revision 0)
+++ hci/trunk/eneraptor-web-app/grails-app/services/com/eneraptor/hci/GraphDataService.groovy	(revision 63)
@@ -0,0 +1,55 @@
+package com.eneraptor.hci
+
+import groovy.sql.Sql
+
+class GraphDataService {
+
+    static transactional = true
+	
+	def dataSource
+
+    List getGraphData(EneGraph gr) {
+		
+		EneGraph graphToShow = gr
+		String query
+		
+		List data = new ArrayList()
+		def results
+		def db_sql = new Sql(dataSource)
+		
+		if(graphToShow.timeFrameType == "day") {
+			db_sql.eachRow "select date_trunc('day',ld.date_recieved) dt, " + graphToShow['type'] + "(to_number(ld.reported_data,'9999999999D99999')) vl from logged_data as ld where (device_id = '" + graphToShow['deviceId'] + "' and date_trunc('day',ld.date_recieved) >= '" + graphToShow.timeFrameStart + "' and date_trunc('day',ld.date_recieved) <= '" + graphToShow.timeFrameEnd + "' and date_part('hour',ld.date_recieved) >= " + graphToShow.advHoursTimeFrameStart + " and date_part('hour',ld.date_recieved) <= " + graphToShow.advHoursTimeFrameEnd + " and date_part('day',ld.date_recieved) >= " + graphToShow.advDayTimeFrameStart + " and date_part('day',ld.date_recieved) <= " + graphToShow.advDayTimeFrameEnd + " and date_part('month',ld.date_recieved) >= " + graphToShow.advMonthTimeFrameStart + " and date_part('month',ld.date_recieved) <= " + graphToShow.advMonthTimeFrameEnd + ") group by date_trunc('day',ld.date_recieved) order by date_trunc('day',ld.date_recieved) asc", {
+				data << [it.dt.getTime(), it.vl]
+			}
+		} else if (graphToShow.timeFrameType == "month") {
+			db_sql.eachRow "select date_trunc('month',ld.date_recieved) dt, " + graphToShow['type'] + "(to_number(ld.reported_data,'9999999999D99999')) vl from logged_data as ld where (device_id = '" + graphToShow['deviceId'] + "' and date_trunc('month',ld.date_recieved) >= '" + graphToShow.timeFrameStart + "' and date_trunc('month',ld.date_recieved) <= '" + graphToShow.timeFrameEnd + "' and date_part('hour',ld.date_recieved) >= " + graphToShow.advHoursTimeFrameStart + " and date_part('hour',ld.date_recieved) <= " + graphToShow.advHoursTimeFrameEnd + " and date_part('day',ld.date_recieved) >= " + graphToShow.advDayTimeFrameStart + " and date_part('day',ld.date_recieved) <= " + graphToShow.advDayTimeFrameEnd + " and date_part('month',ld.date_recieved) >= " + graphToShow.advMonthTimeFrameStart + " and date_part('month',ld.date_recieved) <= " + graphToShow.advMonthTimeFrameEnd + ") group by date_trunc('month',ld.date_recieved) order by date_trunc('month',ld.date_recieved) asc", {
+				data << [it.dt.getTime(), it.vl]
+			}
+		} else if (graphToShow.timeFrameType == "year") {
+			db_sql.eachRow "select date_trunc('year',ld.date_recieved) dt, " + graphToShow['type'] + "(to_number(ld.reported_data,'9999999999D99999')) vl from logged_data as ld where (device_id = '" + graphToShow['deviceId'] + "' and date_trunc('year',ld.date_recieved) >= '" + graphToShow.timeFrameStart + "' and date_trunc('year',ld.date_recieved) <= '" + graphToShow.timeFrameEnd + "' and date_part('hour',ld.date_recieved) >= " + graphToShow.advHoursTimeFrameStart + " and date_part('hour',ld.date_recieved) <= " + graphToShow.advHoursTimeFrameEnd + " and date_part('day',ld.date_recieved) >= " + graphToShow.advDayTimeFrameStart + " and date_part('day',ld.date_recieved) <= " + graphToShow.advDayTimeFrameEnd + " and date_part('month',ld.date_recieved) >= " + graphToShow.advMonthTimeFrameStart + " and date_part('month',ld.date_recieved) <= " + graphToShow.advMonthTimeFrameEnd + ") group by date_trunc('year',ld.date_recieved) order by date_trunc('year',ld.date_recieved) asc", {
+				data << [it.dt.getTime(), it.vl]
+			}
+		} else if (graphToShow.timeFrameType == "custom") {
+			def innerTimeFrame = (long)((graphToShow.timeFrameEnd.getTime() - graphToShow.timeFrameStart.getTime()) / graphToShow.innerSections)
+			query = ""
+			for(int i = 0; i < graphToShow.innerSections;i++) {
+				def currDateMillis = graphToShow.timeFrameStart.getTime() + (i*innerTimeFrame) + innerTimeFrame/2
+				def currDate = new Date((long)currDateMillis)
+				def currDateStartMillis = graphToShow.timeFrameStart.getTime() + (i*innerTimeFrame)
+				def currDateStart = new Date((long)currDateStartMillis)
+				def currDateStopMillis = graphToShow.timeFrameStart.getTime() + ((i+1)*innerTimeFrame)
+				def currDateStop = new Date((long)currDateStopMillis)
+				query += "(select timestamp without time zone '" + currDate.toTimestamp() + "' dt, " + graphToShow['type'] + "(to_number(ld.reported_data,'9999999999D99999')) vl from logged_data as ld where (device_id = '" + graphToShow['deviceId'] + "' and ld.date_recieved >= '" + currDateStart.toTimestamp() + "' and ld.date_recieved <= '" + currDateStop.toTimestamp() + "' and date_part('hour',ld.date_recieved) >= " + graphToShow.advHoursTimeFrameStart + " and date_part('hour',ld.date_recieved) <= " + graphToShow.advHoursTimeFrameEnd + " and date_part('day',ld.date_recieved) >= " + graphToShow.advDayTimeFrameStart + " and date_part('day',ld.date_recieved) <= " + graphToShow.advDayTimeFrameEnd + " and date_part('month',ld.date_recieved) >= " + graphToShow.advMonthTimeFrameStart + " and date_part('month',ld.date_recieved) <= " + graphToShow.advMonthTimeFrameEnd + ")) "
+				if (i < (graphToShow.innerSections-1) ) query += "union "
+			}
+			query += "order by dt asc"
+			db_sql.eachRow(query) {
+				data << [it.dt.getTime(), it.vl]
+			}
+		}
+		
+		return data
+		
+    }
+	
+}
Index: hci/trunk/eneraptor-web-app/grails-app/controllers/com/eneraptor/hci/StatisticsController.groovy
===================================================================
--- hci/trunk/eneraptor-web-app/grails-app/controllers/com/eneraptor/hci/StatisticsController.groovy	(revision 62)
+++ hci/trunk/eneraptor-web-app/grails-app/controllers/com/eneraptor/hci/StatisticsController.groovy	(revision 63)
@@ -6,6 +6,8 @@
 class StatisticsController {
 
 	def dataSource
+	
+	def graphDataService
 	
     def main = { }
 	
@@ -12,10 +14,12 @@
 	def graphs = {
 	
 		List savedGraphs = new ArrayList()
+		List combinedGraphs = new ArrayList()
 		
 		savedGraphs = EneGraph.list()
+		combinedGraphs = CombGraph.list()
 			
-		[savedGraphs: savedGraphs]
+		[savedGraphs: savedGraphs, combinedGraphs:combinedGraphs]
 			
 	}
 	
@@ -98,40 +102,8 @@
 		String query
 		
 		List data = new ArrayList()
-		def results
-		def db_sql = new Sql(dataSource)
+		data = graphDataService.getGraphData(graphToShow)
 		
-		if(graphToShow.timeFrameType == "day") {
-			db_sql.eachRow "select date_trunc('day',ld.date_recieved) dt, " + graphToShow['type'] + "(to_number(ld.reported_data,'9999999999D99999')) vl from logged_data as ld where (device_id = '" + graphToShow['deviceId'] + "' and date_trunc('day',ld.date_recieved) >= '" + graphToShow.timeFrameStart + "' and date_trunc('day',ld.date_recieved) <= '" + graphToShow.timeFrameEnd + "' and date_part('hour',ld.date_recieved) >= " + graphToShow.advHoursTimeFrameStart + " and date_part('hour',ld.date_recieved) <= " + graphToShow.advHoursTimeFrameEnd + " and date_part('day',ld.date_recieved) >= " + graphToShow.advDayTimeFrameStart + " and date_part('day',ld.date_recieved) <= " + graphToShow.advDayTimeFrameEnd + " and date_part('month',ld.date_recieved) >= " + graphToShow.advMonthTimeFrameStart + " and date_part('month',ld.date_recieved) <= " + graphToShow.advMonthTimeFrameEnd + ") group by date_trunc('day',ld.date_recieved) order by date_trunc('day',ld.date_recieved) asc", {
-				data << [it.dt.getTime(), it.vl]
-			}
-		} else if (graphToShow.timeFrameType == "month") {
-			db_sql.eachRow "select date_trunc('month',ld.date_recieved) dt, " + graphToShow['type'] + "(to_number(ld.reported_data,'9999999999D99999')) vl from logged_data as ld where (device_id = '" + graphToShow['deviceId'] + "' and date_trunc('month',ld.date_recieved) >= '" + graphToShow.timeFrameStart + "' and date_trunc('month',ld.date_recieved) <= '" + graphToShow.timeFrameEnd + "' and date_part('hour',ld.date_recieved) >= " + graphToShow.advHoursTimeFrameStart + " and date_part('hour',ld.date_recieved) <= " + graphToShow.advHoursTimeFrameEnd + " and date_part('day',ld.date_recieved) >= " + graphToShow.advDayTimeFrameStart + " and date_part('day',ld.date_recieved) <= " + graphToShow.advDayTimeFrameEnd + " and date_part('month',ld.date_recieved) >= " + graphToShow.advMonthTimeFrameStart + " and date_part('month',ld.date_recieved) <= " + graphToShow.advMonthTimeFrameEnd + ") group by date_trunc('month',ld.date_recieved) order by date_trunc('month',ld.date_recieved) asc", {
-				data << [it.dt.getTime(), it.vl]
-			}
-		} else if (graphToShow.timeFrameType == "year") {
-			db_sql.eachRow "select date_trunc('year',ld.date_recieved) dt, " + graphToShow['type'] + "(to_number(ld.reported_data,'9999999999D99999')) vl from logged_data as ld where (device_id = '" + graphToShow['deviceId'] + "' and date_trunc('year',ld.date_recieved) >= '" + graphToShow.timeFrameStart + "' and date_trunc('year',ld.date_recieved) <= '" + graphToShow.timeFrameEnd + "' and date_part('hour',ld.date_recieved) >= " + graphToShow.advHoursTimeFrameStart + " and date_part('hour',ld.date_recieved) <= " + graphToShow.advHoursTimeFrameEnd + " and date_part('day',ld.date_recieved) >= " + graphToShow.advDayTimeFrameStart + " and date_part('day',ld.date_recieved) <= " + graphToShow.advDayTimeFrameEnd + " and date_part('month',ld.date_recieved) >= " + graphToShow.advMonthTimeFrameStart + " and date_part('month',ld.date_recieved) <= " + graphToShow.advMonthTimeFrameEnd + ") group by date_trunc('year',ld.date_recieved) order by date_trunc('year',ld.date_recieved) asc", {
-				data << [it.dt.getTime(), it.vl]
-			}
-		} else if (graphToShow.timeFrameType == "custom") {
-			def innerTimeFrame = (long)((graphToShow.timeFrameEnd.getTime() - graphToShow.timeFrameStart.getTime()) / graphToShow.innerSections)
-			query = ""
-			for(int i = 0; i < graphToShow.innerSections;i++) {
-				def currDateMillis = graphToShow.timeFrameStart.getTime() + (i*innerTimeFrame) + innerTimeFrame/2
-				def currDate = new Date((long)currDateMillis)
-				def currDateStartMillis = graphToShow.timeFrameStart.getTime() + (i*innerTimeFrame)
-				def currDateStart = new Date((long)currDateStartMillis)
-				def currDateStopMillis = graphToShow.timeFrameStart.getTime() + ((i+1)*innerTimeFrame)
-				def currDateStop = new Date((long)currDateStopMillis)
-				query += "(select timestamp without time zone '" + currDate.toTimestamp() + "' dt, " + graphToShow['type'] + "(to_number(ld.reported_data,'9999999999D99999')) vl from logged_data as ld where (device_id = '" + graphToShow['deviceId'] + "' and ld.date_recieved >= '" + currDateStart.toTimestamp() + "' and ld.date_recieved <= '" + currDateStop.toTimestamp() + "' and date_part('hour',ld.date_recieved) >= " + graphToShow.advHoursTimeFrameStart + " and date_part('hour',ld.date_recieved) <= " + graphToShow.advHoursTimeFrameEnd + " and date_part('day',ld.date_recieved) >= " + graphToShow.advDayTimeFrameStart + " and date_part('day',ld.date_recieved) <= " + graphToShow.advDayTimeFrameEnd + " and date_part('month',ld.date_recieved) >= " + graphToShow.advMonthTimeFrameStart + " and date_part('month',ld.date_recieved) <= " + graphToShow.advMonthTimeFrameEnd + ")) "
-				if (i < (graphToShow.innerSections-1) ) query += "union "
-			}
-			query += "order by dt asc"
-			db_sql.eachRow(query) {
-				data << [it.dt.getTime(), it.vl]
-			}
-		}
-		
 		[data:data]
 		
 	}
@@ -178,7 +150,7 @@
 		if(!graphToEdit.validate()) {
 			errors += "<p>Graph could not be modified - there were some input errors. Please check if all input values are valid.</p>"
 			flash['errors'] = errors
-			redirect(action:'editGraph')
+			redirect(action:'editGraph',params: [id: params.graphId])
 		} else {
 			if(graphToEdit.save()){
 				flash['confirms'] = "<p>Graph modified succesfully!</p>"
@@ -185,7 +157,7 @@
 				redirect(action:'graphs')
 			} else {
 				flash['errors'] = "<p>There was an error while modifying graph.</p>"
-				redirect(action:'editGraph')
+				redirect(action:'editGraph',params: [id: params.graphId])
 			}
 		}
 		
@@ -448,6 +420,79 @@
 			def graphToDelete = EneGraph.get(params['id'])
 			if(graphToDelete) {
 				
+				if(graphToDelete.delete()) {
+					flash['confirms'] = "<p>Graph deleted successfully.</p>"
+					redirect(action:'graphs')
+					return true
+				} else {
+					flash['errors'] = "<p>Cannot remove this graph. This graph is part of a <b>combined</b> graph.</p><p>You should remove the <b>combined</b> graph first.</p>"
+					redirect(action:'graphs')
+					return false
+				}
+				
+			} else {
+				flash['errors'] = "<p>No graph was deleted. Graph ID invalid!</p>"
+				redirect(action:'graphs')
+				return false
+			}
+			
+		} else {
+		
+			flash['errors'] = "<p>No graph was deleted. Graph ID not specified!</p>"
+			redirect(action:'graphs')
+			return false
+		
+		}	
+		
+	}
+	
+	def newCombGraph = {
+		
+		def graphs = EneGraph.list()
+		
+		[graphs:graphs]
+		
+	}
+	
+	def newCombGraphDo = {
+		
+		List selgrph = new ArrayList()
+		selgrph = params.list('graphsToCombine')
+		
+		if(selgrph.size() < 2) {
+			flash.errors = "<p>You have to select at least 2 single device graphs to combine!</p>"
+			redirect(action:'newCombGraph')
+			return false
+		}
+		
+		CombGraph newOne = new CombGraph(
+			name: params.newCombGraphName
+		)
+		
+		selgrph.each {
+			newOne.addToGraphs(EneGraph.get(it))
+		}
+		
+		if(newOne.validate()) {
+			newOne.save()
+			flash.confirms = "<p>New combined graph added succesfully!</p>"
+			redirect(action:'graphs')
+			return true
+		} else {
+			flash.errors = "<p>New graph could not be created. Check if all fields are filled and if values are valid.</p>"
+			redirect(action:'newCombGraph')
+			return false
+		}
+		
+	}
+	
+	def deleteCombGraph = {
+		
+		if(params['id']) {
+			
+			def graphToDelete = CombGraph.get(params['id'])
+			if(graphToDelete) {
+				
 				graphToDelete.delete()
 				flash['confirms'] = "<p>Graph deleted successfully.</p>"
 				redirect(action:'graphs')
@@ -465,8 +510,103 @@
 			redirect(action:'graphs')
 			return false
 		
-		}	
+		}
 		
 	}
+	
+	def editCombGraph = {
+		
+		CombGraph chosenGraph = CombGraph.get(params['id'])
+		List allGraphs = new ArrayList()
+		EneGraph.list().each {
+			if(chosenGraph.graphs.contains(it)) {
+				allGraphs << [gr: it, sl: true]
+			} else {
+				allGraphs << [gr: it, sl: false]
+			}
+		}
+		
+		[chosenGraph: chosenGraph, graphs: allGraphs]
+		
+	}
+	
+	def editCombGraphDo = {
+		
+		CombGraph chosenGraph = CombGraph.get(params['graphId'])
+		
+		chosenGraph.name = params['editCombGraphName']
+		List newGraphs = new ArrayList()
+		newGraphs = params.list('graphsToCombine')
+		
+		if(newGraphs.size() < 2) {
+			flash.errors = "<p>You have to select at least 2 single device graphs to combine!</p>"
+			redirect(action:'editCombGraph',params:[id: params.graphId])
+			return false
+		}
+		
+		List graphsToRemove = new ArrayList()
+		
+		chosenGraph.graphs.each {
+			graphsToRemove << it
+		}
+		
+		graphsToRemove.each {
+			chosenGraph.removeFromGraphs(it)
+		}
+		
+		newGraphs.each {
+			chosenGraph.addToGraphs(EneGraph.get(it))
+		}
+		
+		if(chosenGraph.validate()) {
+			if(chosenGraph.save(flush:true)) {
+				flash.confirms = "<p>Graph edited succesfully!</p>"
+				redirect(action:'graphs')
+				return true
+			} else {
+				flash.errors = "<p>Graph could not be edited. Check if all fields are filled and if values are valid.</p>"
+				redirect(action:'editCombGraph', params:[id: params.graphId])
+				return false
+			}
+		} else {
+			flash.errors = "<p>Graph could not be edited. Check if all fields are filled and if values are valid.</p>"
+			redirect(action:'editCombGraph', params:[id: params.graphId])
+			return false
+		}
+		
+	}
+	
+	def showCombGraph = {
+		
+		if(params.id) {
+			CombGraph chGrph = CombGraph.get(params.id)
+			if(chGrph) {
+				
+				List datas = new ArrayList()
+				
+				chGrph.graphs.each {
+					datas << graphDataService.getGraphData(it)
+				}
+				
+				return [data: datas]
+				
+			} else {
+				flash.errors = "<p>Invalid graph ID!</p>"
+				redirect(action:'graphs')
+				return false
+			}
+		} else {
+			flash.errors = "<p>Invalid graph ID!</p>"
+			redirect(action:'graphs')
+			return false
+		}
+		
+	}
+	
+	def makeCombReport = {
+		
+		
+		
+	}
 	
 }
Index: hci/trunk/eneraptor-web-app/grails-app/domain/com/eneraptor/hci/CombGraph.groovy
===================================================================
--- hci/trunk/eneraptor-web-app/grails-app/domain/com/eneraptor/hci/CombGraph.groovy	(revision 0)
+++ hci/trunk/eneraptor-web-app/grails-app/domain/com/eneraptor/hci/CombGraph.groovy	(revision 63)
@@ -0,0 +1,14 @@
+package com.eneraptor.hci
+
+class CombGraph {
+	
+	String name
+	static hasMany = [graphs:EneGraph]
+
+    static constraints = {
+		
+		name(blank:false, unique:true)
+		
+    }
+	
+}
Index: hci/trunk/eneraptor-web-app/grails-app/views/statistics/editCombGraph.gsp
===================================================================
--- hci/trunk/eneraptor-web-app/grails-app/views/statistics/editCombGraph.gsp	(revision 0)
+++ hci/trunk/eneraptor-web-app/grails-app/views/statistics/editCombGraph.gsp	(revision 63)
@@ -0,0 +1,49 @@
+<html>
+
+<head>
+	<title>Statistics</title>
+	<meta name="layout" content="main" />
+</head>
+
+<body>
+	
+	<erptr:box title="Edit combined graph">
+		
+		<g:form method="POST" name="editCombGraphForm" action="editCombGraphDo">
+		
+			<fieldset>
+				<legend>Basic info</legend>
+				<label for="editCombGraphName">Name (short description)</label>
+				<g:textField name="editCombGraphName" style="width:600px" value="${chosenGraph.name}" />
+				<br />
+			</fieldset>
+		
+			<fieldset>
+				<legend>Graphs</legend>
+				<p>Select which single device graphs to combine</p>
+				<p>Select by clicking on the grpah's name while holding CTRL key on your keyboard.</p>
+				<br />
+				<select multiple="multiple" name="graphsToCombine" id="graphsToCombine" size="10" style="width:820px;">
+					<g:each in="${graphs}" var="graph">
+						<g:if test="${graph.sl}">
+							<option value="${graph.gr.id}" selected="selected">(${graph.gr.type}) ${graph.gr.name}</option>
+						</g:if>
+						<g:else>
+							<option value="${graph.gr.id}">(${graph.gr.type}) ${graph.gr.name}</option>
+						</g:else>
+					</g:each>
+				</select>
+				<br /><br />
+			</fieldset>
+			
+			<g:hiddenField name="graphId" value="${chosenGraph.id}" />
+		
+			<g:submitButton name="editCombGraphSubmit" value="Save changes" />
+		
+		</g:form>
+		
+	</erptr:box>
+
+</body>
+
+</html>
\ No newline at end of file
Index: hci/trunk/eneraptor-web-app/grails-app/views/statistics/newCombGraph.gsp
===================================================================
--- hci/trunk/eneraptor-web-app/grails-app/views/statistics/newCombGraph.gsp	(revision 0)
+++ hci/trunk/eneraptor-web-app/grails-app/views/statistics/newCombGraph.gsp	(revision 63)
@@ -0,0 +1,42 @@
+<html>
+
+<head>
+	<title>Statistics</title>
+	<meta name="layout" content="main" />
+</head>
+
+<body>
+	
+	<erptr:box title="New combined graph">
+		
+		<form name="newCombGraphForm" action="newCombGraphDo">
+		
+			<fieldset>
+				<legend>Basic info</legend>
+				<label for="newCombGraphName">Name (short description)</label>
+				<g:textField name="newCombGraphName" style="width:600px" />
+				<br />
+			</fieldset>
+		
+			<fieldset>
+				<legend>Graphs</legend>
+				<p>Select which single device graphs to combine</p>
+				<p>Select by clicking on the grpah's name while holding CTRL key on your keyboard.</p>
+				<br />
+				<select multiple="multiple" name="graphsToCombine" id="graphsToCombine" size="10" style="width:820px;">
+					<g:each in="${graphs}" var="graph">
+						<option value="${graph.id}">(${graph.type}) ${graph.name}</option>
+					</g:each>
+				</select>
+				<br /><br />
+			</fieldset>
+		
+			<g:submitButton name="newCombGraphSubmit" value="Create new graph" />
+		
+		</form>
+		
+	</erptr:box>
+
+</body>
+
+</html>
\ No newline at end of file
Index: hci/trunk/eneraptor-web-app/grails-app/views/statistics/showCombGraph.gsp
===================================================================
--- hci/trunk/eneraptor-web-app/grails-app/views/statistics/showCombGraph.gsp	(revision 0)
+++ hci/trunk/eneraptor-web-app/grails-app/views/statistics/showCombGraph.gsp	(revision 63)
@@ -0,0 +1,27 @@
+<html>
+
+<head>
+	<title>Statistics &raquo; Graph view</title>
+	<meta name="layout" content="main" />
+	<flot:resources />
+</head>
+
+<body>
+
+	<erptr:box title="Graph">
+		<g:javascript>
+			var data = ${data};
+			var options = {
+				lines: { show: true },
+				points: { show: true },
+				xaxis: { mode: "time", timeformat: "%y/%m/%d %H:%M" }
+			};
+		</g:javascript>
+		<div class="chart">
+			<flot:plot id="placeholder" style="width: 900px; height: 300px; padding:5px;" data="data" options="options" />
+		</div>
+	</erptr:box>
+
+</body>
+
+</html>
\ No newline at end of file
Index: hci/trunk/eneraptor-web-app/grails-app/views/statistics/graphs.gsp
===================================================================
--- hci/trunk/eneraptor-web-app/grails-app/views/statistics/graphs.gsp	(revision 62)
+++ hci/trunk/eneraptor-web-app/grails-app/views/statistics/graphs.gsp	(revision 63)
@@ -9,6 +9,8 @@
 	
 	<erptr:box title="Saved graphs">
 		
+		<p>Single device graphs:</p>
+		
 		<table>
 			<thead>
 				<tr>
@@ -21,11 +23,11 @@
 			<tbody>
 				<g:each in="${savedGraphs}" var="savedGraph">
 					<tr>
-						<td><p><erptr:icon w="chart_curve" /><g:link action="showGraph" id="${savedGraph.id}" >${savedGraph.name}</g:link></p></td>
+						<td><p><erptr:icon w="chart_curve_single" /><g:link action="showGraph" id="${savedGraph.id}" >${savedGraph.name}</g:link></p></td>
 						<td><p>${savedGraph.deviceId}</p></td>
 						<td><p>${savedGraph.type}</p></td>
 						<td><p>
-							<g:link action="showGraph" id="${savedGraph.id}" title="View graph only" ><erptr:icon w="chart_line" /></g:link>
+							<g:link action="showGraph" id="${savedGraph.id}" title="View graph only" ><erptr:icon w="magnifier" /></g:link>
 							<g:link action="makeReport" id="${savedGraph.id}" title="Make report" ><erptr:icon w="report" /></g:link>
 							<g:link action="editGraph" id="${savedGraph.id}" title="Edit" ><erptr:icon w="pencil" /></g:link>
 							<g:link action="deleteGraph" id="${savedGraph.id}" title="Delete" ><erptr:icon w="delete" /></g:link>
@@ -37,10 +39,37 @@
 		<g:if test="${savedGraphs.size == 0}">
 			<center><p><i>There are no saved graphs.</i></p></center>
 		</g:if>
+		
+		<p>Combined graphs:</p>
+		
+		<table>
+			<thead>
+				<tr>
+					<th><p>Graph name (short description)</p></th>
+					<th style="width:78px;"><p>Options</p></th>
+				</tr>
+			</thead>
+			<tbody>
+				<g:each in="${combinedGraphs}" var="combGraph">
+					<tr>
+						<td><p><erptr:icon w="chart_curve" /><g:link action="showCombGraph" id="${combGraph.id}" >${combGraph.name}</g:link></p></td>
+						<td><p>
+							<g:link action="showCombGraph" id="${combGraph.id}" title="View graph only" ><erptr:icon w="magnifier" /></g:link>
+							<a><erptr:icon w="blank" /></a>
+							<g:link action="editCombGraph" id="${combGraph.id}" title="Edit" ><erptr:icon w="pencil" /></g:link>
+							<g:link action="deleteCombGraph" id="${combGraph.id}" title="Delete" ><erptr:icon w="delete" /></g:link>
+						</p></td>
+					</tr>
+				</g:each>
+			</tbody>
+		</table>
+		<g:if test="${combinedGraphs.size == 0}">
+			<center><p><i>There are no saved graphs.</i></p></center>
+		</g:if>
 
 	</erptr:box>
 	
-	<erptr:box title="Create a new graph">
+	<erptr:box title="Create a new single device graph">
 		<p>To create a new graph, select one of the graph types below:</p>
 			<table style="text-align:center;border:none;">
 				<tr>
@@ -57,6 +86,11 @@
 				</tr>
 			</table>
 	</erptr:box>
+	
+	<erptr:box title="Create a new combined graph">
+		<p>Combined graph is simply a set of single device graphs displayed as a single one.</p>
+		<p><g:link action="newCombGraph"><erptr:icon w="chart_curve_add" />Create a new combined graph</g:link></p>
+	</erptr:box>
 
 </body>
 
Index: hci/trunk/eneraptor-web-app/test/unit/com/eneraptor/GraphDataServiceTests.groovy
===================================================================
--- hci/trunk/eneraptor-web-app/test/unit/com/eneraptor/GraphDataServiceTests.groovy	(revision 0)
+++ hci/trunk/eneraptor-web-app/test/unit/com/eneraptor/GraphDataServiceTests.groovy	(revision 63)
@@ -0,0 +1,17 @@
+package com.eneraptor
+
+import grails.test.*
+
+class GraphDataServiceTests extends GrailsUnitTestCase {
+    protected void setUp() {
+        super.setUp()
+    }
+
+    protected void tearDown() {
+        super.tearDown()
+    }
+
+    void testSomething() {
+
+    }
+}
Index: hci/trunk/eneraptor-web-app/test/unit/com/eneraptor/hci/CombGraphTests.groovy
===================================================================
--- hci/trunk/eneraptor-web-app/test/unit/com/eneraptor/hci/CombGraphTests.groovy	(revision 0)
+++ hci/trunk/eneraptor-web-app/test/unit/com/eneraptor/hci/CombGraphTests.groovy	(revision 63)
@@ -0,0 +1,17 @@
+package com.eneraptor.hci
+
+import grails.test.*
+
+class CombGraphTests extends GrailsUnitTestCase {
+    protected void setUp() {
+        super.setUp()
+    }
+
+    protected void tearDown() {
+        super.tearDown()
+    }
+
+    void testSomething() {
+
+    }
+}
Index: hci/trunk/eneraptor-web-app/test/unit/com/eneraptor/hci/GraphDataServiceTests.groovy
===================================================================
--- hci/trunk/eneraptor-web-app/test/unit/com/eneraptor/hci/GraphDataServiceTests.groovy	(revision 0)
+++ hci/trunk/eneraptor-web-app/test/unit/com/eneraptor/hci/GraphDataServiceTests.groovy	(revision 63)
@@ -0,0 +1,17 @@
+package com.eneraptor.hci
+
+import grails.test.*
+
+class GraphDataServiceTests extends GrailsUnitTestCase {
+    protected void setUp() {
+        super.setUp()
+    }
+
+    protected void tearDown() {
+        super.tearDown()
+    }
+
+    void testSomething() {
+
+    }
+}
Index: hci/trunk/eneraptor-web-app/test/unit/com/eneraptor/hci/MultiselectTagLibTests.groovy
===================================================================
--- hci/trunk/eneraptor-web-app/test/unit/com/eneraptor/hci/MultiselectTagLibTests.groovy	(revision 0)
+++ hci/trunk/eneraptor-web-app/test/unit/com/eneraptor/hci/MultiselectTagLibTests.groovy	(revision 63)
@@ -0,0 +1,17 @@
+package com.eneraptor.hci
+
+import grails.test.*
+
+class MultiselectTagLibTests extends TagLibUnitTestCase {
+    protected void setUp() {
+        super.setUp()
+    }
+
+    protected void tearDown() {
+        super.tearDown()
+    }
+
+    void testSomething() {
+
+    }
+}
