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

问题描述 投票:2回答:2

我想制作一个线性回归程序,向用户显示数据。我正在使用EJML进行计算,使用ScalaFX进行前端计算。一切都很顺利但是当我使用Scatter Chart绘制数据时,从数据绘制的线被设置为覆盖原始数据点的矩形。我想知道如何改变绘制点的大小,形状和透明度等。

几乎所有围绕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
    }
  }
}
scala scalafx
2个回答
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.


0
投票

我几乎不敢为Mike Allens解决方案添加一些东西(这是非常好的,一如既往),但这对我来说没有用,因为我无法让我的scala找到和/或处理.css文件。如果可能的话,我会这样做,但我无法让它工作。这是我想出的:

假设我要显示一些数据:

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

然后我将其转换为LineChart的Series:

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

现在我把它再次放入缓冲区,也许还有来自不同系列的其他数据。

 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:blue;“

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

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。如果不这样做,新符号将具有标准颜色和设置。线路停留,它们被重新燃烧,我不能说为什么。

但是好的一面,人们总是可以像这样在折线图中重新格式化曲线!

© www.soinside.com 2019 - 2024. All rights reserved.