new numass viewer components

This commit is contained in:
Alexander Nozik 2017-10-18 22:03:04 +03:00
parent 755a901d2e
commit 4904825bf2
8 changed files with 511 additions and 83 deletions

View File

@ -151,6 +151,7 @@ public class TimeAnalyzer extends AbstractAnalyzer {
}
lastEvent.set(event);
//TODO remove autoboxing somehow
return new Pair<>(event, res);
});
}

View File

@ -0,0 +1,123 @@
package inr.numass.viewer
import hep.dataforge.kodex.configure
import hep.dataforge.kodex.fx.dfIcon
import hep.dataforge.kodex.fx.plots.PlotContainer
import hep.dataforge.meta.Meta
import hep.dataforge.plots.PlotFrame
import hep.dataforge.plots.data.DataPlot
import hep.dataforge.plots.jfreechart.JFreeChartFrame
import hep.dataforge.tables.Table
import hep.dataforge.tables.XYAdapter
import inr.numass.data.NumassDataUtils
import inr.numass.data.analyzers.SimpleAnalyzer
import inr.numass.data.api.NumassAnalyzer
import inr.numass.data.api.NumassPoint
import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleObjectProperty
import javafx.collections.FXCollections
import javafx.scene.control.CheckBox
import javafx.scene.control.ChoiceBox
import javafx.scene.image.ImageView
import tornadofx.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
class AmplitudeView(
private val analyzer: NumassAnalyzer = SimpleAnalyzer(),
private val cache: MutableMap<NumassPoint, Table> = ConcurrentHashMap()
) : View(title = "Numass amplitude spectrum plot", icon = ImageView(dfIcon)) {
private val frame: PlotFrame = JFreeChartFrame().configure {
"title" to "Detector response plot"
node("xAxis") {
"axisTitle" to "ADC"
"axisUnits" to "channels"
}
node("yAxis") {
"axisTitle" to "count rate"
"axisUnits" to "Hz"
}
"legend.show" to false
}
val binningProperty = SimpleObjectProperty<Int>(20)
var binning by binningProperty
val normalizeProperty = SimpleBooleanProperty(true)
var normalize by normalizeProperty
private val container = PlotContainer(frame).apply {
val binnintSelector: ChoiceBox<Int> = ChoiceBox(FXCollections.observableArrayList(1, 2, 5, 10, 20, 50)).apply {
minWidth = 0.0
selectionModel.selectLast()
binningProperty.bind(this.selectionModel.selectedItemProperty())
}
val normalizeSwitch: CheckBox = CheckBox("Normalize").apply {
minWidth = 0.0
this.selectedProperty().bindBidirectional(normalizeProperty)
}
addToSideBar(0, binnintSelector, normalizeSwitch)
}
private val data: MutableMap<String, NumassPoint> = HashMap();
override val root = borderpane {
center = container.root
}
private fun getSpectrum(point: NumassPoint): Table {
return cache.computeIfAbsent(point) { analyzer.getSpectrum(point, Meta.empty()) }
}
private fun updateView() {
val valueAxis = if (normalize) {
NumassAnalyzer.COUNT_RATE_KEY
} else {
NumassAnalyzer.COUNT_KEY
}
val progress = AtomicInteger(0);
runLater { container.progress = 0.0 }
runAsync {
val totalCount = data.size
data.map { entry ->
val seriesName = String.format("%s: %.2f", entry.key, entry.value.voltage)
DataPlot.plot(
seriesName,
XYAdapter(NumassAnalyzer.CHANNEL_KEY, valueAxis),
NumassDataUtils.spectrumWithBinning(getSpectrum(entry.value), binning)
).configure {
"connectionType" to "step"
"thickness" to 2
"showLine" to true
"showSymbol" to false
"showErrors" to false
"JFreeChart.cache" to true
}.also {
runLater { container.progress = progress.incrementAndGet().toDouble() / data.size }
}
}
} ui { plots ->
frame.setAll(plots)
//detectorDataExportButton.isDisable = false
}
}
fun update(map: Map<String, NumassPoint>) {
synchronized(data) {
//Remove obsolete keys
data.keys.filter { !map.containsKey(it) }.forEach {
data.remove(it)
frame.remove(it);
}
this.data.putAll(map);
updateView()
}
}
}

View File

@ -0,0 +1,66 @@
package inr.numass.viewer
import hep.dataforge.kodex.configure
import hep.dataforge.kodex.fx.dfIcon
import hep.dataforge.kodex.fx.plots.PlotContainer
import hep.dataforge.plots.PlotFrame
import hep.dataforge.plots.data.TimePlot
import hep.dataforge.plots.jfreechart.JFreeChartFrame
import inr.numass.data.api.NumassSet
import javafx.scene.image.ImageView
import tornadofx.*
import java.util.concurrent.atomic.AtomicInteger
/**
* View for hv
*/
class HVView : View(title = "High voltage time plot", icon = ImageView(dfIcon)) {
private val frame: PlotFrame = JFreeChartFrame().configure {
"xAxis.axisTitle" to "time"
"xAxis.type" to "time"
"yAxis.axisTitle" to "HV"
}
private val container = PlotContainer(frame);
override val root = borderpane {
center = PlotContainer(frame).root
}
fun update(vararg sets: NumassSet) {
frame.plots.clear()
container.sideBarExpanded = false
val progress = AtomicInteger(0);
runLater { container.progress = -1.0 }
sets.forEach { data ->
runAsync {
val res = data.hvData
runLater { container.progress = progress.incrementAndGet().toDouble() / sets.size }
res
} ui { hvData ->
hvData.ifPresent {
for (dp in it) {
val blockName = dp.getString("block", "default").replace(".", "_");
//val opt = frame.opt(blockName)
val plot = frame.opt(blockName).orElseGet {
TimePlot(blockName).configure {
"connectionType" to "step"
"thickness" to 2
"showLine" to true
"showSymbol" to false
"showErrors" to false
}
.apply { frame.add(this) }
} as TimePlot;
plot.put(dp.getValue("timestamp").timeValue(), dp.getValue("value"))
}
}
container.progress = 1.0;
}
}
}
}

View File

@ -7,10 +7,10 @@ import hep.dataforge.kodex.buildMeta
import hep.dataforge.kodex.fx.plots.PlotContainer
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.plots.PlotGroup
import hep.dataforge.plots.XYPlotFrame
import hep.dataforge.plots.data.DataPlot
import hep.dataforge.plots.data.PlotDataUtils
import hep.dataforge.plots.data.PlottableGroup
import hep.dataforge.plots.data.DataPlotUtils
import hep.dataforge.plots.data.TimePlot
import hep.dataforge.plots.jfreechart.JFreeChartFrame
import hep.dataforge.storage.commons.JSONMetaWriter
@ -45,7 +45,7 @@ import java.util.concurrent.atomic.AtomicInteger
import java.util.logging.Level
import java.util.stream.Collectors
@Suppress("UNNECESSARY_LATEINIT")
/**
* Numass loader view
*
@ -84,7 +84,7 @@ class NumassLoaderView : View() {
private val spectra = HashMap<Double, Table>();//spectra cache
val spectrumData = DataPlot("spectrum")
val hvPlotData = PlottableGroup<TimePlot>()
val hvPlotData = PlotGroup("hv")
//private var points = FXCollections.observableArrayList<NumassPoint>()
val detectorPlotFrame = JFreeChartFrame(
@ -224,7 +224,7 @@ class NumassLoaderView : View() {
}
} else {
spectrumData.clear()
hvPlotData.forEach { it.clear() }
hvPlotData.clear()
}
}
@ -246,7 +246,7 @@ class NumassLoaderView : View() {
}
private fun updateHV(data: NumassSet) {
hvPlotData.forEach { it.clear() }
hvPlotData.clear()
runAsync {
data.hvData
} ui { hvData ->
@ -256,9 +256,10 @@ class NumassLoaderView : View() {
if (!hvPlotData.has(block)) {
hvPlotData.add(TimePlot(block))
}
hvPlotData.get(block).put(dp.getValue("timestamp").timeValue(), dp.getValue("value"))
(hvPlotData.opt(block).orElseThrow{RuntimeException()} as TimePlot)
.put(dp.getValue("timestamp").timeValue(), dp.getValue("value"))
}
hvPlot.plot.addAll(hvPlotData)
hvPlot.plot.add(hvPlotData)
}
}
@ -400,7 +401,7 @@ class NumassLoaderView : View() {
fileChooser.initialFileName = data!!.name + "_detector.out"
val destination = fileChooser.showSaveDialog(detectorPlotPane.scene.window)
if (destination != null) {
val detectorData = PlotDataUtils.collectXYDataFromPlot(detectorPlot.plot as XYPlotFrame, true)
val detectorData = DataPlotUtils.collectXYDataFromPlot(detectorPlot.plot as XYPlotFrame, true)
try {
ColumnedDataWriter.writeTable(
destination,

View File

@ -1,7 +1,7 @@
package inr.numass.viewer
import hep.dataforge.kodex.buildMeta
import hep.dataforge.kodex.configure
import hep.dataforge.kodex.fx.dfIcon
import hep.dataforge.kodex.fx.plots.PlotContainer
import hep.dataforge.meta.Meta
import hep.dataforge.plots.Plot
@ -13,23 +13,24 @@ import hep.dataforge.tables.ListTable
import hep.dataforge.tables.Table
import hep.dataforge.tables.XYAdapter
import hep.dataforge.values.Values
import javafx.scene.image.ImageView
import tornadofx.*
/**
* Created by darksnake on 18.06.2017.
*/
class SlowControlView : View("My View") {
private val plotMeta = buildMeta("plot") {
class SlowControlView : View(title = "Numass slow control view", icon = ImageView(dfIcon)) {
private val plot = JFreeChartFrame().configure {
"xAxis.type" to "time"
"yAxis.type" to "log"
}
val plot = JFreeChartFrame(plotMeta)
override val root = borderpane {
center = PlotContainer(plot).root
}
//TODO add multiple loaders
fun load(loader: PointLoader) {
runAsync {
val data = getData(loader)

View File

@ -0,0 +1,177 @@
package inr.numass.viewer
import hep.dataforge.kodex.configure
import hep.dataforge.kodex.fx.dfIcon
import hep.dataforge.kodex.fx.plots.PlotContainer
import hep.dataforge.meta.Meta
import hep.dataforge.plots.PlotFrame
import hep.dataforge.plots.data.DataPlot
import hep.dataforge.plots.jfreechart.JFreeChartFrame
import hep.dataforge.tables.Table
import hep.dataforge.tables.XYAdapter
import inr.numass.data.analyzers.SimpleAnalyzer
import inr.numass.data.api.NumassAnalyzer
import inr.numass.data.api.NumassPoint
import inr.numass.data.api.NumassSet
import javafx.beans.property.SimpleIntegerProperty
import javafx.geometry.Orientation
import javafx.scene.image.ImageView
import javafx.util.converter.IntegerStringConverter
import org.controlsfx.control.RangeSlider
import tornadofx.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import java.util.stream.Collectors
/**
* View for energy spectrum
* @param analyzer
* @param cache - optional global point cache
*/
class SpectrumView(
val analyzer: NumassAnalyzer = SimpleAnalyzer(),
val cache: MutableMap<NumassPoint, Table> = ConcurrentHashMap()
) : View(title = "Numass spectrum plot", icon = ImageView(dfIcon)) {
private val frame: PlotFrame = JFreeChartFrame().configure {
"xAxis.axisTitle" to "U"
"xAxis.axisUnits" to "V"
"yAxis.axisTitle" to "count rate"
"yAxis.axisUnits" to "Hz"
//"legend.show" to false
}
private val container = PlotContainer(frame);
private val loChannelProperty = SimpleIntegerProperty(0).apply {
addListener { _ -> updateView() }
}
private var loChannel by loChannelProperty
private val upChannelProperty = SimpleIntegerProperty(4000).apply {
addListener { _ -> updateView() }
}
private var upChannel by upChannelProperty
private val data: MutableMap<String, NumassSet> = HashMap()
/*
<BorderPane fx:id="spectrumPlotPane" prefHeight="200.0" prefWidth="200.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<top>
<ToolBar prefHeight="40.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<VBox>
<Label text="Lo channel"/>
<TextField fx:id="lowChannelField" prefWidth="60.0"/>
</VBox>
<RangeSlider fx:id="channelSlider" accessibleRole="SLIDER"
highValue="1900.0" lowValue="300.0" majorTickUnit="500.0"
max="4000.0" minorTickCount="5" prefHeight="38.0"
prefWidth="276.0" showTickLabels="true" showTickMarks="true">
<padding>
<Insets left="10.0" right="10.0"/>
</padding>
</RangeSlider>
<VBox>
<Label text="Up channel"/>
<TextField fx:id="upChannelField" prefWidth="60.0"/>
</VBox>
<Separator orientation="VERTICAL"/>
<VBox>
<Label text="Dead time (us)"/>
<TextField fx:id="dTimeField" prefHeight="25.0" prefWidth="0.0"
text="7.2"/>
</VBox>
<Separator orientation="VERTICAL"/>
<Pane minWidth="0.0" HBox.hgrow="ALWAYS"/>
<Button fx:id="spectrumExportButton" mnemonicParsing="false" text="Export"/>
</ToolBar>
</top>
</BorderPane>
*/
override val root = borderpane {
top {
toolbar {
prefHeight = 40.0
vbox {
label("Lo channel")
textfield {
textProperty().bindBidirectional(loChannelProperty.asObject(), IntegerStringConverter())
}
}
items += RangeSlider().apply {
highValueProperty().bindBidirectional(upChannelProperty)
lowValueProperty().bindBidirectional(loChannelProperty)
majorTickUnit = 500.0
max = 4000.0
minorTickCount = 5
prefHeight = 38.0
isShowTickLabels = true
isShowTickMarks = true
}
vbox {
label("Up channel")
textfield {
textProperty().bindBidirectional(upChannelProperty.asObject(), IntegerStringConverter())
}
}
separator(Orientation.VERTICAL)
}
}
center = container.root
}
private fun getSpectrum(point: NumassPoint): Table {
return cache.computeIfAbsent(point) { analyzer.getSpectrum(point, Meta.empty()) }
}
private fun updateView() {
runLater { container.progress = 0.0 }
val progress = AtomicInteger(0)
val totalProgress = data.values.stream().mapToLong() { it.points.count() }.sum()
data.forEach { name, set ->
val plot = frame.opt(name).orElseGet {
DataPlot(name).apply {
frame.add(this)
}
} as DataPlot
runAsync {
set.points.map { point ->
val count = NumassAnalyzer.countInWindow(getSpectrum(point), loChannel.toShort(), upChannel.toShort());
val seconds = point.length.toMillis() / 1000.0;
runLater {
container.progress = progress.incrementAndGet().toDouble() / totalProgress
}
XYAdapter.DEFAULT_ADAPTER.buildXYDataPoint(
point.voltage,
(count / seconds),
Math.sqrt(count.toDouble()) / seconds
)
}.collect(Collectors.toList())
} ui { points ->
plot.fillData(points)
container.progress = 1.0
//spectrumExportButton.isDisable = false
}
}
}
fun update(map: Map<String, NumassSet>) {
synchronized(data) {
//Remove obsolete keys
data.keys.filter { !map.containsKey(it) }.forEach {
data.remove(it)
frame.remove(it);
}
this.data.putAll(map.mapValues { NumassDataCache(it.value) });
updateView()
}
}
}

View File

@ -0,0 +1,71 @@
package inr.numass.viewer.test
import hep.dataforge.kodex.fx.dfIcon
import hep.dataforge.tables.Table
import inr.numass.data.api.NumassPoint
import inr.numass.data.api.NumassSet
import inr.numass.data.storage.NumassStorageFactory
import inr.numass.viewer.AmplitudeView
import inr.numass.viewer.HVView
import inr.numass.viewer.SpectrumView
import javafx.application.Application
import javafx.scene.image.ImageView
import tornadofx.*
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import java.util.stream.Collectors
class ViewerTestApp : App(ViewerTest::class)
class ViewerTest : View(title = "Numass viewer test", icon = ImageView(dfIcon)) {
//val rootDir = File("D:\\Work\\Numass\\data\\2017_05\\Fill_2")
//val set: NumassSet = NumassStorageFactory.buildLocal(rootDir).provide("loader::set_8", NumassSet::class.java).orElseThrow { RuntimeException("err") }
private val cache: MutableMap<NumassPoint, Table> = ConcurrentHashMap();
val amp = AmplitudeView(cache = cache)
val sp = SpectrumView(cache = cache)
val hv = HVView()
override val root = borderpane {
top {
button("Click me!") {
action {
runAsync {
val rootDir = File("D:\\Work\\Numass\\data\\2017_05\\Fill_2")
val set: NumassSet = NumassStorageFactory.buildLocal(rootDir).provide("loader::set_3", NumassSet::class.java)
.orElseThrow { RuntimeException("err") }
update(set);
}
}
}
}
center {
tabpane {
tab("amplitude") {
content = amp.root
}
tab("spectrum") {
content = sp.root
}
tab("hv") {
content = hv.root
}
}
}
}
fun update(set: NumassSet) {
amp.update(set.points.filter { it.voltage != 16000.0 }.collect(Collectors.toMap({ "point_${it.voltage}" }, { it })));
sp.update(mapOf("test" to set));
hv.update(set)
}
}
fun main(args: Array<String>) {
Application.launch(ViewerTestApp::class.java, *args);
}

View File

@ -20,73 +20,61 @@ limitations under the License.
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import org.controlsfx.control.RangeSlider?>
<AnchorPane id="AnchorPane" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1">
<children>
<TabPane fx:id="tabPane" layoutX="200.0" layoutY="100.0" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" tabClosingPolicy="UNAVAILABLE" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<tabs>
<Tab text="Info">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<TextArea fx:id="infoTextBox" layoutY="30.0" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="30.0" />
</children>
</AnchorPane>
</content>
</Tab>
<Tab fx:id="detectorTab" text="Detector">
<content>
<BorderPane fx:id="detectorPlotPane" minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0" />
</content>
</Tab>
<Tab fx:id="hvTab" text="HV">
<content>
<BorderPane fx:id="hvPane" minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0" />
</content>
</Tab>
<Tab fx:id="spectrumTab" text="Spectrum">
<content>
<AnchorPane minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0">
<children>
<BorderPane fx:id="spectrumPlotPane" prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<top>
<ToolBar prefHeight="40.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<items>
<VBox>
<children>
<Label text="Lo channel" />
<TextField fx:id="lowChannelField" prefWidth="60.0" />
</children>
</VBox>
<RangeSlider fx:id="channelSlider" accessibleRole="SLIDER" highValue="1900.0" lowValue="300.0" majorTickUnit="500.0" max="4000.0" minorTickCount="5" prefHeight="38.0" prefWidth="276.0" showTickLabels="true" showTickMarks="true">
<padding>
<Insets left="10.0" right="10.0" />
</padding>
</RangeSlider>
<VBox>
<children>
<Label text="Up channel" />
<TextField fx:id="upChannelField" prefWidth="60.0" />
</children>
</VBox>
<Separator orientation="VERTICAL" />
<VBox>
<children>
<Label text="Dead time (us)" />
<TextField fx:id="dTimeField" prefHeight="25.0" prefWidth="0.0" text="7.2" />
</children>
</VBox>
<Separator orientation="VERTICAL" />
<Pane minWidth="0.0" HBox.hgrow="ALWAYS" />
<Button fx:id="spectrumExportButton" mnemonicParsing="false" text="Export" />
</items>
</ToolBar>
</top>
</BorderPane>
</children>
</AnchorPane>
</content>
</Tab>
</tabs>
</TabPane>
</children>
<AnchorPane id="AnchorPane" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0"
xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1">
<TabPane fx:id="tabPane" layoutX="200.0" layoutY="100.0" minHeight="-Infinity" minWidth="-Infinity"
prefHeight="400.0" prefWidth="600.0" tabClosingPolicy="UNAVAILABLE" AnchorPane.bottomAnchor="0.0"
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<Tab text="Info">
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<TextArea fx:id="infoTextBox" layoutY="30.0" prefHeight="200.0" prefWidth="200.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="30.0"/>
</AnchorPane>
</Tab>
<Tab fx:id="detectorTab" text="Detector">
<BorderPane fx:id="detectorPlotPane" minHeight="0.0" minWidth="0.0" prefHeight="180.0"
prefWidth="200.0"/>
</Tab>
<Tab fx:id="hvTab" text="HV">
<BorderPane fx:id="hvPane" minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0"/>
</Tab>
<Tab fx:id="spectrumTab" text="Spectrum">
<AnchorPane minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0">
<BorderPane fx:id="spectrumPlotPane" prefHeight="200.0" prefWidth="200.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<top>
<ToolBar prefHeight="40.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<VBox>
<Label text="Lo channel"/>
<TextField fx:id="lowChannelField" prefWidth="60.0"/>
</VBox>
<RangeSlider fx:id="channelSlider" accessibleRole="SLIDER"
highValue="1900.0" lowValue="300.0" majorTickUnit="500.0"
max="4000.0" minorTickCount="5" prefHeight="38.0"
prefWidth="276.0" showTickLabels="true" showTickMarks="true">
<padding>
<Insets left="10.0" right="10.0"/>
</padding>
</RangeSlider>
<VBox>
<Label text="Up channel"/>
<TextField fx:id="upChannelField" prefWidth="60.0"/>
</VBox>
<Separator orientation="VERTICAL"/>
<VBox>
<Label text="Dead time (us)"/>
<TextField fx:id="dTimeField" prefHeight="25.0" prefWidth="0.0"
text="7.2"/>
</VBox>
<Separator orientation="VERTICAL"/>
<Pane minWidth="0.0" HBox.hgrow="ALWAYS"/>
<Button fx:id="spectrumExportButton" mnemonicParsing="false" text="Export"/>
</ToolBar>
</top>
</BorderPane>
</AnchorPane>
</Tab>
</TabPane>
</AnchorPane>