我想制作一个线性回归程序,向用户显示数据。我正在使用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
}
}
}
这当然没有记录得那么好...... :-(
样式表通常放在项目的资源目录中。如果您使用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。
我几乎不敢为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。如果不这样做,新符号将具有标准颜色和设置。线路停留,它们被重新燃烧,我不能说为什么。
但是好的一面,人们总是可以像这样在折线图中重新格式化曲线!