/* * Copyright (C) 2015 Adrien Guille <adrien.guille@univ-lyon2.fr> * * 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 * (at your option) 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/>. */ package main.java.fr.ericlab.sondy.core.ui; import java.time.Instant; import java.util.Date; import java.util.HashMap; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.concurrent.WorkerStateEvent; import main.java.fr.ericlab.sondy.core.app.Main; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.chart.BarChart; import javafx.scene.chart.CategoryAxis; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart; import javafx.scene.control.Button; import javafx.scene.control.ChoiceBox; import javafx.scene.control.Label; import javafx.scene.control.ListView; import javafx.scene.control.Separator; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.TextFieldTableCell; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.input.ScrollEvent; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.scene.layout.VBoxBuilder; import javafx.scene.paint.Color; import javafx.scene.paint.Stop; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; import javafx.stage.Modality; import javafx.stage.Stage; import javafx.stage.StageStyle; import main.java.fr.ericlab.sondy.algo.Parameter; import main.java.fr.ericlab.sondy.algo.influenceanalysis.InfluenceAnalysisMethod; import main.java.fr.ericlab.sondy.core.app.AppParameters; import main.java.fr.ericlab.sondy.core.structures.Message; import main.java.fr.ericlab.sondy.core.utils.ArrayUtils; import main.java.fr.ericlab.sondy.core.utils.CustomSwingNode; import main.java.fr.ericlab.sondy.core.utils.UIUtils; import org.apache.commons.lang3.StringUtils; import org.graphstream.graph.Node; import org.graphstream.ui.geom.Point3; import org.graphstream.ui.swingViewer.View; import org.graphstream.ui.swingViewer.Viewer; import org.reflections.Reflections; /** * * @author Adrien GUILLE, Laboratoire ERIC, Université Lumière Lyon 2 */ public class InfluenceAnalysisUI { public GridPane grid; // Methods ListView<String> methodList; // - parameters TableView<Parameter> parameterTable; HashMap<String,String> methodMap; // - apply Label methodDescriptionLabel; Button applyButton; InfluenceAnalysisMethod selectedMethod; // Influence analysis // - network visualization CustomSwingNode swingNode; View view; Button zoomInButton; Button zoomOutButton; // - rank distribution chart CategoryAxis xAxis; NumberAxis yAxis; BarChart<String,Number> rankDistributionChart; public InfluenceAnalysisUI(){ // Initializing the main grid grid = new GridPane(); grid.setPadding(new Insets(5, 5, 5, 5)); // Adding separators grid.add(new Text("Available methods"),0,0); grid.add(new Separator(),0,1); grid.add(new Text("Network visualization"),0,3); grid.add(new Separator(),0,4); availabeMethodsUI(); rankAnalysisUI(); } public final void availabeMethodsUI(){ initializeAvailableMethodList(); methodDescriptionLabel = new Label("Selected method description"); methodDescriptionLabel.setId("smalltext"); UIUtils.setSize(methodDescriptionLabel,Main.columnWidthLEFT,24); VBox methodsLEFT = new VBox(); methodsLEFT.getChildren().addAll(methodList,new Rectangle(0,3),methodDescriptionLabel); // Right part applyButton = createApplyMethodButton(); UIUtils.setSize(applyButton, Main.columnWidthRIGHT, 24); parameterTable = new TableView<>(); UIUtils.setSize(parameterTable, Main.columnWidthRIGHT, 64); initializeParameterTable(); VBox methodsRIGHT = new VBox(); methodsRIGHT.getChildren().addAll(parameterTable,new Rectangle(0,3),applyButton); // Both parts HBox methodsBOTH = new HBox(5); methodsBOTH.getChildren().addAll(methodsLEFT,methodsRIGHT); grid.add(methodsBOTH,0,2); } public final void rankAnalysisUI(){ initializeRankDistributionChart(); HBox rankAnalysisBOTH = new HBox(0); rankAnalysisBOTH.getChildren().addAll(createNetworkVisualization(),rankDistributionChart); grid.add(rankAnalysisBOTH,0,5); } public final Button createApplyMethodButton(){ Button button = new Button("Apply method"); button.setOnAction((ActionEvent ae) -> { try { if(AppParameters.event != null){ AppParameters.disableUI(true); rankDistributionChart.getData().clear(); LogUI.addLogEntry("Running '"+selectedMethod.getName()+"'..."); final Task<String> waitingTask = new Task<String>() { @Override public String call() throws Exception { AppParameters.dataset.network.updateAuthorNetwork(); Thread job = new Thread(selectedMethod); job.start(); job.join(); return "" ; } }; waitingTask.setOnSucceeded((WorkerStateEvent event1) -> { updateRankDistributionChart(); AppParameters.disableUI(false); LogUI.addLogEntry("Done: "+selectedMethod.getLog()); }); ExecutorService executor = Executors.newSingleThreadExecutor(); executor.submit(waitingTask); }else{ LogUI.addLogEntry("Error: no event selected"); } } catch (Exception ex) { Logger.getLogger(EventDetectionUI.class.getName()).log(Level.SEVERE, null, ex); } }); return button; } public final void initializeAvailableMethodList(){ methodMap = new HashMap<>(); methodList = new ListView<>(); methodList.setOrientation(Orientation.HORIZONTAL); UIUtils.setSize(methodList,Main.columnWidthLEFT,64); updateAvailableMethods(); methodList.getSelectionModel().selectedItemProperty().addListener((ObservableValue<? extends String> ov, String old_val, String new_val) -> { try { selectedMethod = (InfluenceAnalysisMethod) Class.forName(methodMap.get(new_val)).newInstance(); methodDescriptionLabel.setText(selectedMethod.getDescription()); parameterTable.setItems(selectedMethod.parameters.list); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) { Logger.getLogger(EventDetectionUI.class.getName()).log(Level.SEVERE, null, ex); } }); } public final void initializeParameterTable(){ parameterTable.setEditable(true); TableColumn keyColumn = new TableColumn("Parameter"); keyColumn.setMinWidth(Main.columnWidthRIGHT/2-1); keyColumn.setCellValueFactory(new PropertyValueFactory<>("name")); TableColumn valueColumn = new TableColumn("Value"); valueColumn.setMinWidth(Main.columnWidthRIGHT/2-1); valueColumn.setCellFactory(TextFieldTableCell.forTableColumn()); valueColumn.setCellValueFactory(new PropertyValueFactory<>("value")); valueColumn.setOnEditCommit( new EventHandler<TableColumn.CellEditEvent<Parameter, String>>() { @Override public void handle(TableColumn.CellEditEvent<Parameter, String> t) { ((Parameter) t.getTableView().getItems().get(t.getTablePosition().getRow())).setValue(t.getNewValue()); } } ); parameterTable.getColumns().addAll(keyColumn,valueColumn); } public final VBox createNetworkVisualization(){ System.setProperty("org.graphstream.ui.renderer", "org.graphstream.ui.j2dviewer.J2DGraphRenderer"); Viewer viewer = new Viewer(AppParameters.authorNetwork, Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD); view = viewer.addDefaultView(false); viewer.enableAutoLayout(); view.resizeFrame(Main.columnWidthLEFT, 290); swingNode = new CustomSwingNode(); swingNode.setContent(view); swingNode.resize(Main.columnWidthLEFT, 290); EventHandler<MouseEvent> mouseHandlerGraphClick = new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent mouseEvent) { if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED) { if(mouseEvent.getButton() == MouseButton.SECONDARY){ Node node = (Node) view.findNodeOrSpriteAt(mouseEvent.getX(), mouseEvent.getY()); if(node != null){ userMessages(node.getId()); } } if(mouseEvent.getButton() == MouseButton.PRIMARY){ double translateCoeff = view.getCamera().getViewPercent(); Point3 center = view.getCamera().getViewCenter(); if(mouseEvent.getY()>175){ view.getCamera().setViewCenter(center.x, center.y-5*translateCoeff, center.z); }else{ view.getCamera().setViewCenter(center.x, center.y+5*translateCoeff, center.z); } if(mouseEvent.getX()>275){ view.getCamera().setViewCenter(center.x+5*translateCoeff, center.y, center.z); }else{ view.getCamera().setViewCenter(center.x-5*translateCoeff, center.y, center.z); } } if(mouseEvent.getButton() == MouseButton.MIDDLE){ view.getCamera().setViewPercent(view.getCamera().getViewPercent()/2); } } } }; EventHandler<ScrollEvent> mouseHandlerGraphScroll = new EventHandler<ScrollEvent>() { @Override public void handle(ScrollEvent event) { if(event.getDeltaY() < 0){ view.getCamera().setViewPercent(view.getCamera().getViewPercent()*2); }else{ view.getCamera().setViewPercent(view.getCamera().getViewPercent()/2); } } }; swingNode.setOnMousePressed(mouseHandlerGraphClick); swingNode.setOnScroll(mouseHandlerGraphScroll); VBox graphBox = new VBox(); graphBox.getChildren().addAll(new Rectangle(Main.columnWidthLEFT,0),swingNode); initializeNetworkVisualizationStyle(); return graphBox; } public void initializeNetworkVisualizationStyle(){ AppParameters.authorNetwork.addAttribute("event","no event"); AppParameters.authorNetwork.addAttribute("ui.quality"); AppParameters.authorNetwork.addAttribute("ui.antialias"); String css = "" + "graph { fill-mode: plain; fill-color : #f4f4f4;}" + "edge { fill-mode: plain; fill-color: rgba(0,0,0,85); size:0.25px; z-index: 1; arrow-shape: arrow; arrow-size: 4px, 2px;}" + "node { fill-mode: dyn-plain; fill-color: #4682B4,yellow,red ; size: 6px ;z-index : 2; stroke-mode: plain;}" + "node:clicked { fill-color: black;}"; AppParameters.authorNetwork.addAttribute("ui.stylesheet", css); } public final void initializeRankDistributionChart(){ xAxis = new CategoryAxis(); yAxis = new NumberAxis(); xAxis.setLabel("Rank"); xAxis.setTickLength(5); yAxis.setTickLabelsVisible(false); yAxis.setTickMarkVisible(false); rankDistributionChart = new BarChart(xAxis,yAxis); rankDistributionChart.setLegendVisible(false); UIUtils.setSize(rankDistributionChart, Main.columnWidthRIGHT+5, 300); rankDistributionChart.setCategoryGap(0); } public final void updateRankDistributionChart(){ Stop[] stops = new Stop[] { new Stop(0, Color.STEELBLUE), new Stop(0.5, Color.YELLOW), new Stop(1, Color.RED)}; rankDistributionChart.getData().clear(); int[] rankDistribution = selectedMethod.rankedUsers.extractRankDistribution(); XYChart.Series series = new XYChart.Series(); for(int i = 0; i < rankDistribution.length; i++){ series.getData().add(new XYChart.Data(i+"",rankDistribution[i])); } rankDistributionChart.getData().add(series); } public final void updateAvailableMethods(){ Reflections reflections = new Reflections("main.java.fr.ericlab.sondy.algo.influenceanalysis"); Set<Class<? extends InfluenceAnalysisMethod>> classes = reflections.getSubTypesOf(InfluenceAnalysisMethod.class); for(Class<? extends InfluenceAnalysisMethod> aClass : classes){ try { InfluenceAnalysisMethod method = (InfluenceAnalysisMethod) Class.forName(aClass.getName()).newInstance(); methodList.getItems().add(method.getName()); methodMap.put(method.getName(),aClass.getName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) { Logger.getLogger(EventDetectionUI.class.getName()).log(Level.SEVERE, null, ex); } } } public final void userMessages(String user){ final Stage stage = new Stage(); stage.initModality(Modality.APPLICATION_MODAL); stage.initStyle(StageStyle.UTILITY); stage.setTitle("Messages"); TableView<Message> messageTable = new TableView<>(); UIUtils.setSize(messageTable, Main.columnWidthLEFT, 360); TableColumn authorColumn = new TableColumn("Author"); TableColumn timeColumn = new TableColumn("Timestamp"); TableColumn textColumn = new TableColumn("Text"); authorColumn.setCellValueFactory(new PropertyValueFactory<>("author")); timeColumn.setCellValueFactory(new PropertyValueFactory<>("time")); textColumn.setCellValueFactory(new PropertyValueFactory<>("text")); messageTable.getColumns().addAll(authorColumn,timeColumn,textColumn); messageTable.setItems(AppParameters.dataset.corpus.getMessages(user)); Text tableText = new Text("User messages ("+messageTable.getItems().size()+")"); Label topicLabel = new Label(); topicLabel.setText(AppParameters.event.getTextualDescription()); Date date = AppParameters.dataset.corpus.start; Instant fromInstant = date.toInstant(); Instant toInstant = date.toInstant(); String[] interval = AppParameters.event.getTemporalDescription().split(","); fromInstant = fromInstant.plusSeconds((long)(Double.parseDouble(interval[0])*24*60*60)); toInstant = toInstant.plusSeconds((long)(Double.parseDouble(interval[1])*24*60*60)); topicLabel.setText(AppParameters.event.getTextualDescription()); Label intervalLabel = new Label(); intervalLabel.setText("From "+fromInstant+" to "+toInstant); TextField filterMessagesField1 = new TextField(""); filterMessagesField1.setPromptText("Type a list of words (separated by a space)"); UIUtils.setSize(filterMessagesField1, 2*(Main.columnWidthLEFT-5)/3, 24); ChoiceBox operatorChoiceBox = new ChoiceBox(); ObservableList<String> operatorList = FXCollections.observableArrayList(); operatorList.add("and"); operatorList.add("or"); operatorChoiceBox.setItems(operatorList); operatorChoiceBox.getSelectionModel().select(0); UIUtils.setSize(operatorChoiceBox, (Main.columnWidthLEFT-5)/3, 24); HBox filterMessagesHBox = new HBox(5); filterMessagesHBox.getChildren().add(filterMessagesField1); filterMessagesHBox.getChildren().add(operatorChoiceBox); Button filterMessagesButton = new Button("Filter messages"); UIUtils.setSize(filterMessagesButton, Main.columnWidthLEFT, 24); filterMessagesButton.setOnAction((ActionEvent ae) -> { String[] words = filterMessagesField1.getText().split(" "); ObservableList<Message> messages = FXCollections.observableArrayList(); int operator = operatorChoiceBox.getSelectionModel().getSelectedIndex(); for(Message message : messageTable.getItems()){ String text = message.getText(); short[] test = new short[words.length]; for(int j = 0; j < words.length; j++){ if(StringUtils.containsIgnoreCase(text,words[j])){ test[j] = 1; }else{ test[j] = 0; } } int testSum = ArrayUtils.sum(test, 0, test.length-1); if(operator==0 && testSum == test.length){ messages.add(message); } if(operator==1 && testSum > 0){ messages.add(message); } } messageTable.getItems().clear(); messageTable.setItems(messages); tableText.setText("User messages ("+messageTable.getItems().size()+")"); }); Scene scene = new Scene(VBoxBuilder.create().children(new Text("User"),new Separator(),new Label(user),tableText,new Separator(),messageTable,filterMessagesHBox,filterMessagesButton).alignment(Pos.CENTER).padding(new Insets(10)).spacing(3).build()); scene.getStylesheets().add("resources/fr/ericlab/sondy/css/GlobalStyle.css"); stage.setScene(scene); stage.show(); } }