org.opalj.hermes.Hermes.scala Maven / Gradle / Ivy
The newest version!
/* BSD 2-Clause License:
* Copyright (c) 2009 - 2017
* Software Technology Group
* Department of Computer Science
* Technische Universität Darmstadt
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.opalj
package hermes
import java.io.File
import java.net.URL
import java.io.FileWriter
import java.io.BufferedWriter
import java.util.prefs.Preferences
import scala.collection.mutable
import scala.concurrent.Future
import scala.concurrent.ExecutionContext
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
import org.chocosolver.solver.Model
import org.chocosolver.solver.variables.IntVar
import javafx.stage.Screen
import javafx.scene.control.TableColumn
import javafx.scene.control.SelectionMode
import javafx.scene.layout.Priority
import org.controlsfx.control.PopOver
import org.controlsfx.control.HiddenSidesPane
import org.controlsfx.control.PopOver.ArrowLocation
import scalafx.Includes._
import scalafx.collections.ObservableBuffer
import scalafx.application.JFXApp
import scalafx.application.Platform
import scalafx.application.JFXApp.PrimaryStage
import scalafx.geometry.Insets
import scalafx.geometry.Pos
import scalafx.stage.Stage
import scalafx.stage.Modality
import scalafx.stage.FileChooser
import scalafx.stage.FileChooser.ExtensionFilter
import scalafx.scene.Scene
import scalafx.scene.Group
import scalafx.scene.image.Image
import scalafx.scene.web.WebView
import scalafx.scene.input.KeyCombination
import scalafx.scene.layout.VBox
import scalafx.scene.layout.GridPane
import scalafx.scene.layout.BorderPane
import scalafx.scene.layout.HBox
import scalafx.scene.layout.StackPane
import scalafx.scene.chart.XYChart
import scalafx.scene.chart.CategoryAxis
import scalafx.scene.chart.NumberAxis
import scalafx.scene.chart.BarChart
import scalafx.scene.chart.PieChart
import scalafx.scene.control.MenuItem
import scalafx.scene.control.TextArea
import scalafx.scene.control.ProgressBar
import scalafx.scene.control.ButtonType
import scalafx.scene.control.Dialog
import scalafx.scene.control.TableCell
import scalafx.scene.control.Button
import scalafx.scene.control.Label
import scalafx.scene.control.CheckBox
import scalafx.scene.control.TableView
import scalafx.scene.control.ListView
import scalafx.scene.control.ChoiceDialog
import scalafx.scene.control.MenuBar
import scalafx.scene.control.Menu
import org.opalj.da.ClassFileReader
import org.opalj.util.Nanoseconds
/**
* Executes all analyses to determine the representativeness of the given projects.
*
* @author Michael Eichberg
* @author Christian Schaarschmidt (JavaFX Data Visualization)
*/
object Hermes extends JFXApp with HermesCore {
if (parameters.unnamed.size != 1 ||
parameters.named.size > 1 || (
parameters.named.size == 1 && parameters.named.get("csv").isEmpty
)) {
import Console.err
err.println("OPAL - Hermes")
err.println("Invalid parameters: "+parameters.named.mkString("{", ",", "}"))
err.println("The parameter has to be the configuration which lists a corpus' projects and, ")
err.println("optionally, the file to which the results should be exported ")
err.println("(\"--csv=\"). If such a file is specified the application will")
err.println("close automatically after running all analyses.")
err.println()
err.println("java org.opalj.hermes.Hermes ")
System.exit(1)
}
initialize(new File(parameters.unnamed(0)))
if (parameters.named.size == 1) {
// when the CSV was requested, we perform the analysis and then finish Hermes
analysesFinished onChange { (_, _, isFinished) ⇒
if (isFinished) {
exportCSV(new File(parameters.named("csv")))
stage.close() // <=> quit Hermes
}
}
}
// We use standard preferences for saving the application state; not for
// permanent configuration settings!
val preferences = Preferences.userRoot().node("org.opalj.hermes.Hermes")
def exportFlare(file: File): Unit = {
val writer = new BufferedWriter(new FileWriter(file))
val jsonGenerator = new JsonFactory().createGenerator(writer)
jsonGenerator.setPrettyPrinter(new DefaultPrettyPrinter())
// create root
jsonGenerator.writeStartObject()
jsonGenerator.writeStringField("name", "flare")
jsonGenerator.writeArrayFieldStart("children")
featureMatrix foreach { project ⇒
// create node for each project
jsonGenerator.writeStartObject()
jsonGenerator.writeStringField("name", project.id.value)
jsonGenerator.writeArrayFieldStart("children")
// statistics
jsonGenerator.writeStartObject()
jsonGenerator.writeStringField("name", "statistics")
jsonGenerator.writeArrayFieldStart("children")
project.projectConfiguration.statistics foreach { stat ⇒
jsonGenerator.writeStartObject()
jsonGenerator.writeStringField("name", stat.getKey)
jsonGenerator.writeNumberField("value", stat.getValue)
jsonGenerator.writeEndObject()
}
jsonGenerator.writeEndArray()
jsonGenerator.writeEndObject()
// sort features by feature groups
jsonGenerator.writeStartObject()
jsonGenerator.writeStringField("name", "features")
jsonGenerator.writeArrayFieldStart("children")
project.featureGroups foreach {
case (group, features) ⇒
jsonGenerator.writeStartObject()
jsonGenerator.writeStringField("name", group.id)
jsonGenerator.writeArrayFieldStart("children")
features foreach { f ⇒
jsonGenerator.writeStartObject()
jsonGenerator.writeStringField("name", f.value.id)
jsonGenerator.writeNumberField("value", f.value.count)
jsonGenerator.writeEndObject()
}
jsonGenerator.writeEndArray()
jsonGenerator.writeEndObject()
}
jsonGenerator.writeEndArray()
jsonGenerator.writeEndObject()
jsonGenerator.writeEndArray()
jsonGenerator.writeEndObject()
jsonGenerator.flush()
}
jsonGenerator.writeEndArray()
jsonGenerator.writeEndObject()
jsonGenerator.close()
}
override def updateProjectData(f: ⇒ Unit): Unit = Platform.runLater {
// We have to ensure that we are not calling this too often to avoid that the
// UI starts to lag
f
}
override def reportProgress(f: ⇒ Double): Unit = Platform.runLater {
val progress = f
if (progress >= 1.0d) rootPane.getChildren().remove(progressBar)
}
// Must only be called after all features were computed.
private[this] def computeCorpus(): Unit = {
// GOAL: Select projects with an overall minimal number of methods such that every
// possible feature occurs at least once.
// min sum(p_i * )
val model = new Model("Project Selection")
// CONSTRAINTS
// - [per feature f] sum(p_i which has feature f) > 0
val releventProjectFeatures =
featureMatrix.filter { pf ⇒
pf.projectConfiguration.statistics.getOrElse("ProjectMethods", 0.0d) >= 1.0d
}.toArray
val pis: Array[IntVar] = releventProjectFeatures.map(pf ⇒ model.boolVar(pf.id.value))
perFeatureCounts.iterator.zipWithIndex foreach { fCount_fIndex ⇒
val (fCount, fIndex) = fCount_fIndex
if (fCount.value > 0) {
val projectsWithFeature = pis.toIterator.zipWithIndex.collect {
case (pi, pIndex) if featureMatrix(pIndex).features(fIndex).value.count > 0 ⇒ pi
}.toArray
model.sum(projectsWithFeature, ">", 0).post()
}
}
val piSizes: Array[Int] =
releventProjectFeatures.map(_.projectConfiguration.statistics("ProjectMethods").toInt)
// OPTIMIZATION GOAL
val overallSize = model.intVar("objective", 0, IntVar.MAX_INT_BOUND /*=21474836*/ )
model.scalar(pis, piSizes, "=", overallSize).post()
model.setObjective(Model.MINIMIZE, overallSize)
val solver = model.getSolver
val solutionTextArea = new TextArea {
editable = false
prefHeight = 300
prefWidth = 450
vgrow = Priority.ALWAYS
maxWidth = Double.MaxValue
}
val solverProgressBar = new ProgressBar {
hgrow = Priority.ALWAYS
maxWidth = Double.MaxValue
}
val contentNode = new VBox(solutionTextArea, solverProgressBar) {
padding = Insets(5, 5, 5, 5)
}
val dialog = new Dialog[String]() {
initOwner(stage)
title = "Computing Optimal Project Selection"
headerText = "Computes a minimal set of projects which has every possible feature."
dialogPane().buttonTypes = Seq(ButtonType.OK)
dialogPane().content = contentNode
resultConverter = { (_) ⇒ solutionTextArea.text.value }
}
@volatile var aborted: Boolean = false
def computeSolutions(): Unit = {
implicit val ec = ExecutionContext.global
val solution: Future[Option[String]] = Future {
if (solver.solve()) {
Some(pis.filter(_.getValue == 1).map(pi ⇒ pi.getName).mkString("\n"))
} else {
None
}
}
solution onSuccess {
case Some(result) ⇒
Platform.runLater { solutionTextArea.text = result }
if (!aborted) computeSolutions()
case None ⇒
Platform.runLater { contentNode.getChildren.remove(solverProgressBar) }
}
}
computeSolutions()
dialog.showAndWait()
// Make sure that – when the dialog has been closed while we are still computing solutions -
// the computation process "finishes soon".
aborted = true
}
// ---------------------------------------------------------------------------------------------
//
//
// UI SETUP CODE
//
//
// ---------------------------------------------------------------------------------------------
private[this] val progressBar = new ProgressBar { hgrow = Priority.ALWAYS; maxWidth = Double.MaxValue }
val projectColumn = new TableColumn[ProjectFeatures[URL], String]("Project")
// TODO Make the project configuration observable to fill the statistics table automatically.
projectColumn.cellValueFactory = { p ⇒ p.getValue.id }
projectColumn.cellFactory = { (column) ⇒
new TableCell[ProjectFeatures[URL], String] {
item.onChange { (_, _, newProject) ⇒
if (newProject != null) {
val infoButton = new Button("Info")
val popOver = new PopOver()
popOver.arrowLocationProperty.value = ArrowLocation.LEFT_CENTER
popOver.setTitle(newProject)
val textArea = new TextArea("Project statistics are not (yet) available.") {
editable = false
prefWidth = 300d
prefHeight = 300d
}
popOver.contentNodeProperty.value = textArea
popOver.setDetachable(true)
popOver.setHideOnEscape(false)
popOver.headerAlwaysVisibleProperty().value = true
infoButton.onAction = handle {
if (!popOver.isShowing()) {
val projectFeatures = featureMatrix.find(_.id.value == newProject).get
val projectConfiguration = projectFeatures.projectConfiguration
val projectStatistics = projectConfiguration.statistics
val statistics = projectStatistics.map(e ⇒ f"${e._1}%-32s${e._2}%12.2f")
textArea.text = statistics.toList.sorted.mkString("\n")
popOver.show(infoButton)
}
}
val isBrokenProject = newProject.startsWith("! ︎")
graphic =
new HBox(
new Label(newProject) {
hgrow = Priority.ALWAYS
maxWidth = Double.MaxValue
padding = Insets(5, 5, 5, 5)
if (isBrokenProject)
style = "-fx-background-color: red; -fx-text-fill: white;"
},
infoButton
)
}
}
}
}
val onlyShowNotAvailableFeatures = new CheckBox("Only Show Not Available Features") {
padding = Insets(5, 5, 5, 5)
hgrow = Priority.ALWAYS
maxWidth = Double.MaxValue
alignment = Pos.Center
style = "-fx-background-color: #dddddd"
}
val showFeatureQueryResults = new GridPane {
padding = Insets(5, 5, 5, 5)
hgrow = Priority.ALWAYS
maxWidth = Double.MaxValue
alignment = Pos.Center
style = "-fx-background-color: #eeefdd"
}
var primitiveFeatureIndex = 0
val featureQueryColumns = featureQueries.zipWithIndex map { fqi ⇒
val (fq, featureQueryIndex) = fqi
val featureQueryColumn = new TableColumn[ProjectFeatures[URL], Feature[URL]]()
val showFeatureQuery = new CheckBox(fq.id) {
selected = true
disable <== onlyShowNotAvailableFeatures.selected
padding = Insets(15, 15, 15, 15)
}
onlyShowNotAvailableFeatures.selected.onChange { (_, _, isSelected) ⇒
if (!isSelected) showFeatureQuery.selected = true
}
showFeatureQueryResults.add(showFeatureQuery, featureQueryIndex % 5, featureQueryIndex / 5)
featureQueryColumn.visible <== showFeatureQuery.selected
val featureColumns = fq.featureIDs.map { name ⇒
val featureIndex = primitiveFeatureIndex
primitiveFeatureIndex += 1
val featureColumn = new TableColumn[ProjectFeatures[URL], Feature[URL]]("")
/* This creates a weird propagation problem due to a bidirectional binding..
featureColumn.visible <== scalafx.beans.binding.Bindings.createBooleanBinding(
() ⇒ {
!onlyShowNotAvailableFeatures.selected.value ||
perFeatureCounts(featureIndex).value == 0
},
onlyShowNotAvailableFeatures.selected,
perFeatureCounts(featureIndex)
)*/
perFeatureCounts(featureIndex) onChange { (_, oldCount, newCount) ⇒
if (newCount != null) {
if (oldCount.intValue == 0 &&
newCount.intValue > 0 &&
onlyShowNotAvailableFeatures.selected.value) {
featureColumn.visible = false
}
}
}
onlyShowNotAvailableFeatures.selected onChange { (_, _, isSelected) ⇒
if (isSelected && perFeatureCounts(featureIndex).value > 0) {
featureColumn.visible = false
} else {
featureColumn.visible = true
}
}
featureColumn.setPrefWidth(70.0d)
featureColumn.cellValueFactory = { p ⇒ p.getValue.features(featureIndex) }
featureColumn.cellFactory = { (_) ⇒
new TableCell[ProjectFeatures[URL], Feature[URL]] {
var currentValue = -1
item.onChange { (_, _, newFeature) ⇒
text = if (newFeature != null) newFeature.count.toString else ""
style =
if (newFeature == null)
"-fx-background-color: gray"
else {
currentValue = newFeature.count
if (newFeature.count == 0) {
if (perFeatureCounts(featureIndex).value == 0)
"-fx-background-color: #ffd0db"
else
"-fx-background-color: #ffffe0"
} else {
"-fx-background-color: #aaffaa"
}
}
}
perFeatureCounts(featureIndex).onChange { (_, oldValue, newValue) ⇒
if (oldValue.intValue != newValue.intValue && currentValue != -1) {
style =
if (newValue.intValue == 0)
"-fx-background-color: #ffd0db"
else if (currentValue == 0)
"-fx-background-color: #ffffe0"
else
"-fx-background-color: #aaffaa"
}
}
}
}
val label = new Label(name) { rotate = -90; padding = Insets(5, 5, 5, 5) }
val group = new Group(label)
val box = new VBox(group) {
vgrow = Priority.ALWAYS
maxWidth = Double.MaxValue
}
box.setAlignment(Pos.BottomLeft)
featureColumn.setGraphic(box)
featureColumn
}
featureQueryColumn.columns ++= featureColumns
val fqButton = new Button(fq.id) {
padding = Insets(0, 0, 0, 0)
hgrow = Priority.ALWAYS
maxWidth = Double.MaxValue
}
val webView = new WebView { prefHeight = 600d; prefWidth = 600d }
val webEngine = webView.engine
fq.htmlDescription match {
case Left(page) ⇒
webEngine.setUserStyleSheetLocation(FeatureQueries.MDCSS.toExternalForm)
webEngine.loadContent(page)
case Right(url) ⇒
webEngine.load(url.toExternalForm)
}
val popOver = new PopOver(webView)
popOver.arrowLocationProperty.value = ArrowLocation.TOP_CENTER
popOver.setTitle(fq.id.replaceAll("\n", " – "))
popOver.setDetachable(true)
popOver.setHeaderAlwaysVisible(true)
popOver.setHideOnEscape(false)
fqButton.onAction = handle { if (!popOver.isShowing()) popOver.show(fqButton) }
featureQueryColumn.setGraphic(fqButton.delegate)
featureQueryColumn
}
val featuresTableView = new TableView[ProjectFeatures[URL]](featureMatrix) {
columns ++= (projectColumn +: featureQueryColumns)
}
featuresTableView.getSelectionModel.setCellSelectionEnabled(true)
featuresTableView.getSelectionModel.getSelectedCells.onChange { (positions, _) ⇒
// TODO Bind the content to the selected observable feature as such
if (positions.nonEmpty) {
val position = positions.get(0)
val index = position.getColumn - 1
if (index >= 0) {
locationsView.items.value.clear()
val extensions = featureMatrix(position.getRow).features(index).value.extensions
extensions.forall(locationsView.items.value.add)
}
}
}
val locationsView = new ListView[Location[URL]] {
prefWidth = 1280d
minWidth = 150d
maxWidth = 1280d
maxHeight = Double.MaxValue
vgrow = Priority.ALWAYS
padding = Insets(5, 5, 5, 5)
}
locationsView.getSelectionModel.setSelectionMode(SelectionMode.SINGLE)
locationsView.getSelectionModel.selectedItem.onChange { (_, _, newLocation) ⇒
if (newLocation != null && newLocation.source.isDefined) {
val webView = new WebView
val stage = new Stage {
scene = new Scene {
title = newLocation.source.get.toExternalForm()
root = webView
}
width = 1024
height = 600
}
stage.show();
try {
// TODO Add support for jars in jars..
val classFile = ClassFileReader.ClassFile(
() ⇒ newLocation.source.get.openConnection().getInputStream
)(0)
webView.engine.loadContent(classFile.toXHTML(newLocation.source).toString())
} catch { case t: Throwable ⇒ t.printStackTrace() }
}
}
val mainPane = new HiddenSidesPane();
mainPane.setContent(featuresTableView);
mainPane.setRight(
new VBox(
new Label(s"Locations (at most ${Globals.MaxLocations} are shown)") {
padding = Insets(5, 5, 5, 5)
hgrow = Priority.ALWAYS
maxWidth = Double.MaxValue
alignment = Pos.Center
style = "-fx-background-color: #ccc"
},
locationsView
) {
padding = Insets(100, 0, 100, 5)
vgrow = Priority.ALWAYS
maxHeight = Double.MaxValue
}.delegate
)
mainPane.setTop(
new VBox(
new Label(s"Table Configuration") {
padding = Insets(5, 5, 5, 5)
hgrow = Priority.ALWAYS
maxWidth = Double.MaxValue
alignment = Pos.Center
style = "-fx-background-color: #ccc"
},
onlyShowNotAvailableFeatures,
showFeatureQueryResults
) {
padding = Insets(0, 250, 0, 250)
hgrow = Priority.ALWAYS
maxWidth = Double.MaxValue
}.delegate
)
def createFileMenuItems(): List[MenuItem] = {
val showConfig = new MenuItem("Show Config...") {
onAction = handle {
val configurationDetails = new TextArea(Globals.renderConfig) {
editable = false
prefHeight = 600d
}
val configurationStage = new Stage {
scene = new Scene { title = "Configuration"; root = configurationDetails }
}
configurationStage.initOwner(stage)
configurationStage.initModality(Modality.ApplicationModal);
configurationStage.showAndWait()
}
}
val showAnalysisTimes = new MenuItem("Show Analysis Times...") {
onAction = handle { analysisTimesStage.show() }
}
val showProjectStatistics = new MenuItem("Project Statistics...") {
// IMPROVE Move it to a "permanent stage" and make the project statistics observable to make it possible to react on changes and to get proper JavaFX behavior
// Container to store the stages; currently, we have to create the stages lazily
// because the statistics are not yet observable.
val pieChartStages: mutable.Map[String, Stage] = mutable.Map.empty
def createPieChartStage(statistic: String): Stage = {
val pieChartStage = new Stage {
title = statistic
scene = new Scene(950, 600) {
val pieChartData = ObservableBuffer.empty[javafx.scene.chart.PieChart.Data]
val pieChart = new PieChart {
data = pieChartData
//title = statistic
legendVisible = true
}
featureMatrix foreach { pf ⇒
val statistics = pf.projectConfiguration.statistics.get(statistic)
pieChartData.add(PieChart.Data(pf.id.value, statistics.get))
}
val legendButton = new CheckBox("Show Legend (If Place is Sufficient)")
pieChart.legendVisible <== legendButton.selected
val bp = new BorderPane {
center = pieChart
top = legendButton
}
root = bp
}
initOwner(stage)
}
pieChartStage
}
disable <== analysesFinished.not
onAction = handle {
// all project's have the same statistics, hence, it is sufficient to
// collect those of the first project
val someProjectConfiguration = featureMatrix.get(0).projectConfiguration
val projectStatisticsList = someProjectConfiguration.statistics.keySet
val statisticDialog =
new ChoiceDialog(projectStatisticsList.head, projectStatisticsList) {
initOwner(stage)
title = "Project Statistics"
headerText = "Choose the project statistic to visualize."
contentText = "Project Statistic:"
}
statisticDialog.showAndWait() foreach { choice ⇒
pieChartStages.getOrElseUpdate(choice, createPieChartStage(choice)).show()
}
}
}
val fileExport = new MenuItem("Export As...") {
disable <== analysesFinished.not
accelerator = KeyCombination.keyCombination("Ctrl +Alt +S")
onAction = handle {
val fileChooser = new FileChooser {
title = "Open Class File"
extensionFilters ++= Seq(
new ExtensionFilter("Comma Separated Values", "*.csv"),
new ExtensionFilter("Flare JSON", "*.json")
)
}
val selectedFile = fileChooser.showSaveDialog(stage)
if (selectedFile != null) {
val filename = selectedFile.getName
val extension = filename.substring(filename.lastIndexOf("."), filename.length())
if (extension.equals(".csv")) {
exportCSV(selectedFile)
} else if (extension.equals(".json")) {
exportFlare(selectedFile)
}
}
}
}
val computeProjectsForCorpus = new MenuItem("Compute Projects for Corpus...") {
disable <== analysesFinished.not
onAction = handle { computeCorpus() }
}
List(
showConfig,
showAnalysisTimes, showProjectStatistics,
fileExport,
computeProjectsForCorpus
)
}
val rootPane = new BorderPane {
top = new MenuBar {
useSystemMenuBar = true
minWidth = 100
menus = Seq(new Menu("File") { items = createFileMenuItems() })
}
center = mainPane
bottom = progressBar
}
stage = new PrimaryStage {
scene = new Scene {
stylesheets = List(getClass.getResource("Hermes.css").toExternalForm)
title = "Hermes - "+parameters.unnamed.head
root = rootPane
analyzeCorpus(runAsDaemons = true)
}
icons += new Image(getClass.getResource("OPAL-Logo-Small.png").toExternalForm)
// let's restore the primary window at its last position
val primaryScreenBounds = Screen.getPrimary().getVisualBounds()
x = preferences.getDouble("WindowX", 100)
y = preferences.getDouble("WindowY", 100)
width = preferences.getDouble("WindowWidth", primaryScreenBounds.getWidth() - 200)
height = preferences.getDouble("WindowHeight", primaryScreenBounds.getHeight() - 200)
onCloseRequest = handle {
preferences.putDouble("WindowX", stage.getX())
preferences.putDouble("WindowY", stage.getY())
preferences.putDouble("WindowWidth", stage.getWidth())
preferences.putDouble("WindowHeight", stage.getHeight())
}
}
val analysisTimesStage = new Stage {
title = "AnalysisTimes"
scene = new Scene(800, 600) {
root = new StackPane {
val xAxis = new CategoryAxis() {
label = "Feature Queries"
tickLabelsVisible = false
}
corpusAnalysisTime.onChange { (_, _, newValue) ⇒
xAxis.label = "Feature Queries ∑"+Nanoseconds(newValue.longValue).toSeconds
}
val yAxis = new NumberAxis()
val data = new ObservableBuffer[javafx.scene.chart.XYChart.Series[String, Number]]()
for (featureQuery ← featureQueries) {
val series = new XYChart.Series[String, Number] {
name = featureQuery.id
}
data.add(series)
featureQuery.accumulatedAnalysisTime.onChange { (_, _, newValue) ⇒
if (newValue.intValue != 0)
series.data = Seq(XYChart.Data[String, Number](featureQuery.id, newValue))
}
}
val barChart = BarChart(xAxis, yAxis)
barChart.animated = false
barChart.legendVisible = true
barChart.title = "Feature Execution Times"
barChart.data = data
barChart.barGap = 1
children = barChart
}
}
initOwner(stage)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy