在ScalaFX中更改散点图绘图点的大小和符号

时间:2019-02-09 14:36:31

标签: scala scalafx

我想制作一个线性回归程序,以可视化方式向用户显示数据。我将EJML用于计算,将ScalaFX用于前端。一切都很好,但是当我使用散点图绘制数据时,从数据绘制的线被设置为覆盖原始数据点的矩形。我想知道如何更改打印点的大小,形状和透明度等。

关于JavaFX的几乎所有指南都说,我应该修改CSS文件(该文件不会自动存在)以设置图表样式。我不知道如何在ScalaFX中做到这一点,或者甚至有可能做到这一点。我搜索所有可能的教程的结果都是徒劳的。

import scalafx.application.JFXApp
import scalafx.scene.Scene
import scalafx.scene.chart.ScatterChart
import scalafx.collections.ObservableBuffer
import scalafx.scene.chart.NumberAxis
import scalafx.scene.chart.XYChart
import scalafx.scene.shape.Line
import org.ejml.simple.SimpleMatrix
import scala.math.pow
import scala.collection.mutable.Buffer


object Plotting extends JFXApp {
  /*
   * Below are some arbitrary x and y values for a regression line
   */
  val xValues = Array(Array(1.0, 1.0, 1.0, 1.0, 1.0, 1.0), Array(14.0, 19.0, 22.0, 26.0, 31.0, 43.0))
  val yValues = Array(Array(51.0, 57.0, 66.0, 71.0, 72.0, 84.0))
  val temp    = yValues.flatten
  val wrapper = xValues(1).zip(temp)
  /*
   * In the lines before stage what happens is that matrices for the x and y values are created, coefficients
   * for the regression line are calculated with matrix operations and (x, y) points are calculated for the 
   * regression line.
   */
  val X       = new SimpleMatrix(xValues).transpose
  val Y       = new SimpleMatrix(yValues).transpose
  val secondX = new SimpleMatrix(xValues(0).size, 2)
  for (i <- 0 until xValues(0).size) {
    secondX.set(i, 0, xValues(0)(i))
    secondX.set(i, 1, xValues(1)(i))
  }
  val invertedSecondX = secondX.pseudoInverse()
  val B = invertedSecondX.mult(Y)
  val graphPoints = Buffer[(Double, Double)]()
  for (i <- 0 to xValues(1).max.toInt) {
    graphPoints.append((i.toDouble, B.get(0, 0) + i * B.get(1, 0)))
  }

  stage = new JFXApp.PrimaryStage {
    title = "Demo"
    scene = new Scene(400, 400) {
      val xAxis = NumberAxis()
      val yAxis = NumberAxis()
      val pData = XYChart.Series[Number, Number](
        "Data",
        ObservableBuffer(wrapper.map(z => XYChart.Data[Number, Number](z._1, z._2)): _*))
      val graph = XYChart.Series[Number, Number](
        "RegressionLine",
        ObservableBuffer(graphPoints.map(z => XYChart.Data[Number, Number](z._1, z._2)): _*))
      val plot = new ScatterChart(xAxis, yAxis, ObservableBuffer(graph, pData))
      root     = plot
    }
  }
}

2 个答案:

答案 0 :(得分:2)

当然,记录的不如可能...:-(

样式表通常放置在项目的资源目录中。如果您使用的是 SBT (推荐),则为src/main/resources

在此示例中,我向此目录添加了名为MyCharts.css的样式表,其内容如下:

/* Blue semi-transparent 4-pointed star, using SVG path. */
.default-color0.chart-symbol {
    -fx-background-color: blue;
    -fx-scale-shape: true;
    -fx-shape: "M 0.0 10.0 L 3.0 3.0 L 10.0 0.0 L 3.0 -3.0 L 0.0 -10.0 L -3.0 -3.0 L -10.0 0.0 L -3.0 3.0 Z ";
    -fx-opacity: 0.5;
}

/* Default shape is a rectangle. Here, we round it to become a red circle with a white
 * center. Change the radius to control the size.
 */
.default-color1.chart-symbol {
    -fx-background-color: red, white;
    -fx-background-insets: 0, 2;
    -fx-background-radius: 3px;
    -fx-padding: 3px;
}

color0将用于第一个数据系列(回归线),color1将用于第二个数据系列(您的散点数据)。其他所有系列均使用默认的 JavaFX 样式。

(有关使用可缩放矢量图形 SVG )路径定义自定义形状的更多信息,请参见relevant section of the SVG specification。)

要让 ScalaFX JavaFX )使用此样式表,您可以选择。要使它们全局应用,请将其添加到主场景(这是我在下面所做的)。或者,如果每个图表需要不同的样式,则可以向特定的图表添加不同的样式表。 (顺便说一句,我还添加了包含导入的标准,因为这可以防止许多 JavaFX - ScalaFX 元素转换问题;否则,我没有对您的来源进行任何更改。)

import scalafx.Includes._
import scalafx.application.JFXApp
import scalafx.scene.Scene
import scalafx.scene.chart.ScatterChart
import scalafx.collections.ObservableBuffer
import scalafx.scene.chart.NumberAxis
import scalafx.scene.chart.XYChart
import scalafx.scene.shape.Line
import org.ejml.simple.SimpleMatrix
import scala.math.pow
import scala.collection.mutable.Buffer

object Plotting extends JFXApp {
  /*
   * Below are some arbitrary x and y values for a regression line
   */
  val xValues = Array(Array(1.0, 1.0, 1.0, 1.0, 1.0, 1.0), Array(14.0, 19.0, 22.0, 26.0, 31.0, 43.0))
  val yValues = Array(Array(51.0, 57.0, 66.0, 71.0, 72.0, 84.0))
  val temp    = yValues.flatten
  val wrapper = xValues(1).zip(temp)
  /*
   * In the lines before stage what happens is that matrices for the x and y values are created, coefficients
   * for the regression line are calculated with matrix operations and (x, y) points are calculated for the 
   * regression line.
   */
  val X       = new SimpleMatrix(xValues).transpose
  val Y       = new SimpleMatrix(yValues).transpose
  val secondX = new SimpleMatrix(xValues(0).size, 2)
  for (i <- 0 until xValues(0).size) {
    secondX.set(i, 0, xValues(0)(i))
    secondX.set(i, 1, xValues(1)(i))
  }
  val invertedSecondX = secondX.pseudoInverse()
  val B = invertedSecondX.mult(Y)
  val graphPoints = Buffer[(Double, Double)]()
  for (i <- 0 to xValues(1).max.toInt) {
    graphPoints.append((i.toDouble, B.get(0, 0) + i * B.get(1, 0)))
  }

  stage = new JFXApp.PrimaryStage {
    title = "Demo"
    scene = new Scene(400, 400) {

      // Add our stylesheet.
      stylesheets.add("MyCharts.css")

      val xAxis = NumberAxis()
      val yAxis = NumberAxis()
      val pData = XYChart.Series[Number, Number](
        "Data",
        ObservableBuffer(wrapper.map(z => XYChart.Data[Number, Number](z._1, z._2)): _*))
      val graph = XYChart.Series[Number, Number](
        "RegressionLine",
        ObservableBuffer(graphPoints.map(z => XYChart.Data[Number, Number](z._1, z._2)): _*))
      val plot = new ScatterChart(xAxis, yAxis, ObservableBuffer(graph, pData))
      root     = plot
    }
  }
}

有关可用的CSS格式选项(更改形状,颜色,透明度等)的更多信息,请参见JavaFX CSS Reference Guide

结果如下所示: Chart image, with customized points.

答案 1 :(得分:0)

我几乎不敢在Mike Allens解决方案中添加somethig(一如既往地非常好),但是这对我来说不起作用,因为我无法让scala查找和/或处理.css。文件。 如果可能的话,我会以这种方式完成的,但我只是无法使其正常工作。 这是我想出的:

假设我要显示一些数据:

val xyExampleData: ObservableBuffer[(Double, Double)] = ObservableBuffer(Seq(
    1 -> 1,
    2 -> 4,
    3 -> 9))

然后将其转换为LineChart的系列:

val DataPoints = ObservableBuffer(xyExampleData map { case (x, y) => XYChart.Data[Number, Number](x, y) })
val PointsToDisplay = XYChart.Series[Number, Number]("Points", DataPoints)

现在我将其再次放入Buffer中,也许还有其他不同系列的数据。

 val lineChartBuffer = ObservableBuffer(PointsToDisplay, ...)

最后,我创建了lineChart,直到我称之为(缺乏创造力)lineChart:

val lineChart = new LineChart(xAxis, yAxis, lineChartBuffer) {...}

现在可以轻松地为数据点之间的线重新着色:

lineChart.lookup(".default-color0.chart-series-line").setStyle("-fx-stroke: blue;") 

这将更改LineChartBuffer中FIRST数据集的线条颜色。 如果要更改线路属性,请致电

lineChart.lookup(".default-color1.chart-series-line")...

还有“ -fx-stroke-width:3px;”设置行的。

“-fx-opacity:0.1;”

“-fx-stroke-dash-array:10;”

-fx-fill:蓝色;”

也是有用的,但是不要重复调用上面的行,因为第二个调用将覆盖第一个调用。 而是将所有字符串合并为一个:

lineChart.lookup(".default-color0.chart-series-line").setStyle("-fx-stroke: blue;-fx-opacity: 0.1;-fx-stroke-dash-array: 10;-fx-fill: blue;")

现在在每个数据点格式化符号:

不幸的是,除了单独格式化每个符号外,似乎别无他法:

lineChart.lookupAll(".default-color0.chart-line-symbol").asScala foreach { node => node.setStyle("-fx-background-color: blue, white;") }

要运行此程序,您需要import scala.collection.JavaConverters._ 从Java设置转换为Scala设置。

一个人也可以仅隐藏一个数据集中的所有数据点,例如:

lineChart.lookupAll(".default-color1.chart-line-symbol").asScala foreach { node => node.setVisible(false) }

要说这是一个很好的解决方案,将被夸大。 它有一个很大的缺点,那就是在向LineChartBuffer中的一个序列添加新的Datapoint之后,必须重新着色或重新格式化每个Symbol。否则,新的符号将具有标准的颜色和设置。 线条留着,它们重新着色了,我不能说为什么。

但是,好的一面是,之后总是可以在折线图中重新格式化曲线!