I want to show values on top of the linechart. I've seen this answer which is quite helpful but it changes the linechart nodes. What I want is same idea but showing values not on nodes, but near them (maybe right and above of them) something like:
53
O
/ \
/ \
/42 \ 21 21
O O------------O
EDIT:
I've tried to code below but sadly only lines appear, doesnt show nodes.
for (int index = 0; index < value.getData().size(); index++) {
XYChart.Data dataPoint = value.getData().get(index);
Node lineSymbol = dataPoint.getNode().lookup(".chart-line-symbol");
lineSymbol.setStyle("-fx-background-color: " + definedColor + " , white;");
Label label = new Label(value.getName());
label.toFront();
Pane nodeWithText = new Pane();
label.setStyle("-fx-font-size: 20; -fx-font-weight: bold;");
StackPane stackPane = new StackPane();
Group group = new Group(label);
group.getChildren().add(lineSymbol);
group.toFront();
group.setLayoutX(-10);
group.setLayoutY(-30);
StackPane.setAlignment(group, Pos.TOP_RIGHT);
StackPane.setMargin(group,new Insets(0,0,5,0));
nodeWithText.getChildren().add(group);
nodeWithText.getChildren().add(lineSymbol);
dataPoint.setNode(nodeWithText);
}
One option is to set a custom Node for each Data in your chart. Here's a crude example:
import javafx.application.Application;
import javafx.beans.binding.ObjectExpression;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
var chart = new LineChart<>(new NumberAxis(), new NumberAxis());
chart.getData().add(new Series<>(createData()));
primaryStage.setScene(new Scene(chart, 600, 400));
primaryStage.show();
}
private static ObservableList<Data<Number, Number>> createData() {
var list = FXCollections.<Data<Number, Number>>observableArrayList();
for (int x = 0; x < 10; x++) {
var data = new Data<Number, Number>(x, Math.pow(x, 2));
data.setNode(createDataNode(data.YValueProperty()));
list.add(data);
}
return list;
}
private static Node createDataNode(ObjectExpression<Number> value) {
var label = new Label();
label.textProperty().bind(value.asString("%,.2f"));
var pane = new Pane(label);
pane.setShape(new Circle(6.0));
pane.setScaleShape(false);
label.translateYProperty().bind(label.heightProperty().divide(-1.5));
return pane;
}
}
The above doesn't do anything complex when it comes to positioning the text. For instance, it won't take into account where the line is nor whether or not some of the text is cut off by the chart's clip. Basically, it's a proof-of-concept; the result looks like:
Some other possible options for adding text to the chart include:
Put the LineChart in a Group or Pane where the chart is the first child. Then, for each Data in your chart, you'd add a Label or Text to the parent and position it relative to the position of the Data's node (when it becomes available). You can calculate these positions using methods such as Node#localToScene and Node#sceneToLocal.
Subclass LineChart and add the Text or Label to the chart directly. As I've never subclassed a chart before, I'm not sure how to implement this option.
Something else I'm not thinking of.
Note that, no matter what you do, if your chart has a lot of data points very close to each other then displaying all the text in a visually pleasing way will be difficult—if not impossible.
Related
I have an application that displays several Line Charts with several Series like this:
I'd like to change the color of each Series but haven't found a way to achieve this. The only thing I found is how to change the default colors but that doesn't solve my problem.
Is there really now way to achieve individual colors for chart series?
The JavaFX CSS Reference Guide says the following for LineChart:
Style class
Comments
Properties
"chart-series-line series<i> default-color<j>"
Where <i> is the index of the series and <j> is the series’ color index
Node
"chart-line-symbol series<i> data<j> default-color<k>"
Where <i> is the index of the series, <j> is the index of the data within the series, and <k> is the series’ color index
Node
"chart-line-symbol series<i> default-color<j>"
Where <i> is the index of the series and <j> is the series’ color index
LegendItem
Note: Although the line is only documented as a Node, by default it is actually a javafx.scene.shape.Path.
If you want to target a specific series' line, use .chart-series-line.series<i>, where <i> is replaced with the index of the series in the chart's data. And if you want to give a series of a specific chart a certain color, then simply give the chart an ID and use that in the CSS selector.
Here's an example. It uses so-called looked-up colors, which makes the CSS a little more scalable and organized. Also, they can have their values changed via the setStyle method in code, allowing you to dynamically change the color programmatically. Another approach for that is to use a "data URL", showcased in this other Stack Overflow answer.
style.css:
/*
* The '-fx-stroke' is used to set the color of the line. The line is
* targeted by the '.chart-series-line.series<i>' selectors.
*
* The '-fx-background-color' is used to set the color of the legend
* symbol so it matches the line. This would also color the symbols
* on the line if they were shown. These symbol nodes are targeted
* by the '.chart-line-symbol.series<i>' selectors.
*
* Both the '-fx-series0-color' and '-fx-series1-color' "properties"
* are looked-up colors. You can change the value of a looked-up color
* in code by calling 'setStyle(...)' on the appropriate node.
*/
#firstChart {
-fx-series0-color: magenta;
-fx-series1-color: dodgerblue;
}
#secondChart {
-fx-series0-color: red;
-fx-series1-color: black;
}
#firstChart .chart-series-line.series0,
#firstChart .chart-line-symbol.series0 {
-fx-stroke: -fx-series0-color;
-fx-background-color: -fx-series0-color;
}
#firstChart .chart-series-line.series1,
#firstChart .chart-line-symbol.series1 {
-fx-stroke: -fx-series1-color;
-fx-background-color: -fx-series1-color;
}
#secondChart .chart-series-line.series0,
#secondChart .chart-line-symbol.series0 {
-fx-stroke: -fx-series0-color;
-fx-background-color: -fx-series0-color;
}
#secondChart .chart-series-line.series1,
#secondChart .chart-line-symbol.series1 {
-fx-stroke: -fx-series1-color;
-fx-background-color: -fx-series1-color;
}
Main.java:
import java.util.function.UnaryOperator;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
var chart1 = createLineChart("Chart 1");
chart1.setId("firstChart");
chart1.getData().add(createSeries("f(x) = x", x -> x));
chart1.getData().add(createSeries("f(x) = 2x", x -> 2 * x));
/*
* Uncomment the line of code below to demonstrate dynamically changing
* the value of a looked-up color.
*/
// chart1.setStyle("-fx-series0-color: goldenrod;");
var chart2 = createLineChart("Chart 2");
chart2.setId("secondChart");
chart2.getData().add(createSeries("f(x) = x^2", x -> x * x));
chart2.getData().add(createSeries("f(x) = x^3", x -> x * x * x));
var root = new VBox(10, chart1, chart2);
root.setPadding(new Insets(10));
primaryStage.setScene(new Scene(root, 1000, 720));
primaryStage.getScene()
.getStylesheets()
.add(Main.class.getResource("/style.css").toString());
primaryStage.show();
}
private LineChart<Number, Number> createLineChart(String title) {
var chart = new LineChart<>(new NumberAxis(), new NumberAxis());
chart.setTitle(title);
chart.getXAxis().setLabel("x");
chart.getYAxis().setLabel("f(x)");
chart.setCreateSymbols(false);
return chart;
}
private XYChart.Series<Number, Number> createSeries(
String name,
UnaryOperator<Double> func) {
var series = new XYChart.Series<Number, Number>();
series.setName(name);
for (int x = 0; x < 20; x++) {
series.getData().add(new XYChart.Data<>(x, func.apply((double) x)));
}
return series;
}
}
I'm currently working on a form that features validation upon changing focus from one node to another and wish to display a tooltip centered above a node containing an error if one exists. I'm using the tooltip's show(Node ownerNode, double anchorX, double anchorY) method signature to specify which node I would like to attach it to and where to position it.
I've tried the following code:
Tooltip tooltip = new Tooltip("Error");
tooltip.show(node, 0, 0);
double tooltipMiddle = tooltip.getWidth() / 2;
tooltip.hide();
double nodeMiddle = node.getWidth() / 2;
//Gets the node's x and y position within the window
Bounds bounds = node.localToScene(node.getBoundsInLocal());
//Tooltip's bottom-right corner is set to the anchor points, so I set x to
//the node's x coordinate plus half the width of the node plus half the width
//of the tooltip
tooltip.show(node,
bounds.getMinX() + nodeMiddle + tooltipMiddle, bounds.getMinY());
This has gotten me very close to the center, but it's still off. I've been all over the internet trying to find help, but I'm just not finding any, so I figured I'd ask here.
Any chance I could get some insight into why I'm not able to get this working how I'd like it?
Code which I bring provides correct positioning of tooltip but is far from being perfect. It would take a lot of work to bring comprehensive solution (if you want we can discuss it).
Going to the bottom I think you have a math problem and Tooltip class may not be the best option.
import javafx.application.Application;
import javafx.geometry.Bounds;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TooltipApp extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) throws Exception {
TextField textField = new TextField();
textField.setPrefWidth(100.);
Button button = new Button("Tooltip");
HBox hBox = new HBox(textField, button);
hBox.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
Label label = new Label("Empty!");
label.setVisible(false);
Pane tooltipPane = new Pane(label);
tooltipPane.setMouseTransparent(true);
StackPane stackPane = new StackPane(hBox, tooltipPane);
stackPane.setPrefSize(600., 400.);
Scene scene = new Scene(stackPane);
stage.setScene(scene);
stage.show();
button.setOnMouseClicked(event -> {
if (label.isVisible()) {
label.setVisible(false);
} else {
Bounds sceneBounds = textField.localToScene(textField.getBoundsInLocal());
label.setLayoutX((sceneBounds.getMinX() + (sceneBounds.getWidth() / 2)) - (label.getWidth() / 2));
label.setLayoutY(sceneBounds.getMinY() - label.getHeight());
label.setVisible(true);
}
});
}
}
First you should use Bounds bounds = node.localToScreen(node.getBoundsInLocal()); as it might not be clear what the relevant Scene is.
Then calling tooltip.show(node, bounds.getMinX(), bounds.getMinY()); should give you a tooltip with an upper left corner identical to the upper upper left corner of node.
Now, tooltip.show(node, bounds.getMinX() + nodeMiddle - tooltipMiddle, bounds.getMinY() - tooltipHeight); should produce the desired result and it does for
double tooltipMiddle = (tooltip.getWidth()-18) / 2;
double tooltipHeight = tooltip.getHeight()-18;
double nodeMiddle = getWidth() / 2;
The 18 came from a little experimenting and I'm not sure why the width and height are off by that amount, but it seems to be indpendent of the length or number of lines of the text in the tooltip.
I have a question concerning a Uni-project I'm working on.
We have to create a game, where we have a board that consists of tiles and you should be able to rotate these tiles with clicks. The Tiles should always fit in the Grid and the Grid should resize with the Window.
Now I made a class "TileView" which extends ImageView to get the pictures matching the pattern of the tile. Then I have TileGrid, which extends GridPane, which should consist of width*height tileviews. And this GridPane is in a BorderPane and this makes the scene.
Unfortunately I can't really put Code in here, because we have strict rules of plagiarism (and if someone sees my code and copies it, I'm going down too).
So, I make 2 for loops for the width and height and create a new TileView and set PreserveRatio on true and then do this:
setRowIndex(temp, i);
setColumnIndex(temp, j);
super.getChildren().add(temp);
this is in the class TileGrid.
Then I add it to the BorderPane with margin insets 100 and this is what happens:
https://imgur.com/a/OYSwER2
https://imgur.com/a/YUsZX09
But now I have the problem of fitting the TileViews to the size of the grid and make them resize with the window. I tried ("temp" is the just created ImageView as I put them in the TileGrid):
temp.fitWidthProperty().bind(prefWidthProperty());
temp.fitHeightProperty().bind(prefHeightProperty());
but then my single ImageViews are gigantic.
https://imgur.com/a/zaCz3OB
This is filling my whole screen.
I've tried numerous things, searched for hours and never achieved what I wanted so I really hope someone can help me or give me a tip even without my code.
Thanks in advance!
Here is a MCVE that demos what you need.
Centers the GridPane content
centerRoot.setAlignment(Pos.CENTER);
Binds the ImageViews fitHeight and fitWidth properties with the GridPanes height and width properties - 100(due to setMargins) / 10(the number of rows/columns)
for (int i = 0; i < 10; i++) {
for (int t = 0; t < 10; t++) {
Image image = new Image("https://s3.amazonaws.com/media.eremedia.com/uploads/2012/08/24111405/stackoverflow-logo-700x467.png");
ImageView imageView = new ImageView(image);
imageView.fitHeightProperty().bind(centerRoot.heightProperty().subtract(100).divide(10));
imageView.fitWidthProperty().bind(centerRoot.widthProperty().subtract(100).divide(10));
imageView.setPreserveRatio(false);
centerRoot.add(imageView, i, t);
}
}
Full Code:
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication241 extends Application
{
#Override
public void start(Stage primaryStage)
{
BorderPane root = new BorderPane();
GridPane centerRoot = new GridPane();
centerRoot.setAlignment(Pos.CENTER);
GridPane.setMargin(centerRoot, new Insets(50, 50, 50, 50));
root.setCenter(centerRoot);
Scene scene = new Scene(root, 500, 500);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
for (int i = 0; i < 10; i++) {
for (int t = 0; t < 10; t++) {
Image image = new Image("https://s3.amazonaws.com/media.eremedia.com/uploads/2012/08/24111405/stackoverflow-logo-700x467.png");
ImageView imageView = new ImageView(image);
imageView.fitHeightProperty().bind(centerRoot.heightProperty().subtract(100).divide(10));
imageView.fitWidthProperty().bind(centerRoot.widthProperty().subtract(100).divide(10));
imageView.setPreserveRatio(false);
centerRoot.add(imageView, i, t);
}
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
As you can't provide any code I'll try to give you steps to work with I hope it might help
First as the BorderPane and GridPane will adjust their sizes to the window you have one problem which is adjusting the sizes of the ImageViews to make it resizable you have to bind their sizes to the size of the scene and take into consideration the number of the rows and columns you wanted for the grid
let's say you have 2 columns so image is 50% of the scene
imageView.fitWidthProperty().bind(scene.getWidthProperty().divide(2));
Note: I'm not sure about the syntax of this line I kinda busy sorry
First time asking something here, sorry for my broken English + I'm here because I'm getting upset by my code's behavior, which won't help my english.
Using javaFX (with java only, no FXML) I am trying to make a Node looking like this
(So multiples HBox in 2 VBox in one HBox)
And whenever I move it (on Drag), it moves on the screen, but if I use ChangeListener on the child's properties I can see they are not moving at all in reality (layoutX or translateX stay at starting values), which cause me a lot of trouble since I want to connect the nodes's cell (and clicking on things not there is quite the fun).
And when I tried to force it by binding the translateXProperty and all those things my Node's content juste goes away and the code tell me it is where it's supposed to be, but on screen it's juste gone (see pic)
(There's only an offset on the X value because I used bind() only on translateX to show the problem).
Sorry this is some bad paint right there but I don't know how to show otherwise, and I don't think giving my code would be relevant since I respected the first picture organization. I have tried using Group and Pane and subclasses but it doesn't work, and the HBox + VBox system organize my content just how I want it to be.
I've been looking at the doc and because of my English there must be something I didn't understand at all but well, I don't know, so if you have any idea, if possible where I can keep this organization, thanks for your help.
EDIT :
As some people asked here is what it's looks like in code, with some Listener to see that the content doesn't move, and because I need to be able to make a link between the HBox's circles I need them to have the right translate values.
import javafx.application.Application;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Main extends Application {
private double mouseX;
private double mouseY;
private double sourceX;
private double sourceY;
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception
{
HBox root = new HBox(10);
// Making the first col (see first image)
VBox left = new VBox(5
, new HBox(10, new Circle(10, Color.WHEAT), new Rectangle(50, 20, Color.CORAL))
, new HBox(10, new Circle(10, Color.WHEAT), new Rectangle(50, 20, Color.CORAL))
);
// Making the second col (see first image)
VBox right = new VBox(5);
right.translateXProperty().addListener((observable, oldValue, newValue) -> System.out.println("right VBox " +
"translate to " + newValue)); // Isn't called
root.getChildren().add(right);
// Making the first row of the right col (see first image)
HBox row0Right = new HBox(10);
row0Right.translateXProperty().addListener((observable, oldValue, newValue) -> System.out.println("row0Right " +
"HBox translate to " + newValue)); // Isn't called
row0Right.getChildren().add(new Rectangle(50, 20, Color.CYAN));
// Making a Circle with a translate Listener
Circle circle = new Circle(10, Color.CRIMSON);
circle.translateXProperty().addListener((observable, oldValue, newValue) -> System.out.println("circle " +
"row0col1 translate to " + newValue)); // Isn't called
row0Right.getChildren().add(circle);
// Making the second row of the right col (see first image)
HBox row1Right = new HBox(10);
row1Right.translateXProperty().addListener((observable, oldValue, newValue) -> System.out.println("row1Right " +
"HBox translate to " + newValue)); // Isn't called
row1Right.getChildren().add(new Rectangle(50, 20, Color.CYAN));
row1Right.getChildren().add(new Circle(10, Color.CRIMSON));
right.getChildren().addAll(row0Right, row1Right);
root.setOnMousePressed(event ->
{
mouseX = event.getSceneX();
mouseY = event.getSceneY();
sourceX = ((Node) (event.getSource())).getTranslateX();
sourceY = ((Node) (event.getSource())).getTranslateY();
});
root.setOnMouseDragged(event ->
{
double newX = sourceX + event.getSceneX() - mouseX;
double newY = sourceY + event.getSceneY() - mouseY;
((Node) (event.getSource())).setTranslateX(newX);
((Node) (event.getSource())).setTranslateY(newY);
});
root.translateXProperty().addListener((observable, oldValue, newValue) -> System.out.println("root translate " +
"to " + newValue)); // Is the only one called
primaryStage.setTitle("Hello World");
primaryStage.setScene(new Scene(root, 720, 720));
primaryStage.show();
}
}
EDIT 2 :
I noticed that if I use a Rectangle as the moving root, and bind shapes translates to it, everything is fine, but I prefer the way the HBox/VBox place the children as I don't want to set every single px in x, y, width and height for every shapes bind to my core rectangle (which might become a lot of shapes) when I can use those Box pattern.
I'm hoping someone can help me with setting a custom label for the domain axis tick labels within a jFreeChart being created by Jasper Reports. I've tried everything that I've found online and still no dice. Here's my code:
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.util.List;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.SymbolAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.labels.CategoryItemLabelGenerator;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.data.Range;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.text.TextBlock;
import org.jfree.text.TextUtilities;
import org.jfree.ui.RectangleEdge;
import net.sf.jasperreports.engine.JRChart;
import net.sf.jasperreports.engine.JRChartCustomizer;
public class ChartCustomizer implements JRChartCustomizer{
public class CustomColorRenderer extends BarRenderer {
private static final long serialVersionUID = -9045170581109026224L;
#Override
public Paint getItemPaint(int row, int col) {
CategoryDataset currentDataset = getPlot().getDataset();
String columnKey = (String) currentDataset.getColumnKey(col);
String[] columnKeyValues = columnKey.split(":");
if(columnKeyValues.length < 2) return getSeriesPaint(row);
String columnActualEstimated = columnKeyValues[2];
if(columnActualEstimated.equals("A")) {
return Color.RED;
} else if(columnActualEstimated.equals("E")) {
return Color.BLUE;
}
return getSeriesPaint(row);
}
}
public void customize(JFreeChart chart, JRChart jasperChart)
{
if(jasperChart.getChartType() == JRChart.CHART_TYPE_BAR) {
CategoryPlot plot = chart.getCategoryPlot();
CategoryDataset currentDataset = plot.getDataset();
double maxValue = Double.MIN_VALUE;
// Scan to get total max value for the chart in order to set chart height appropriately
for(int i = 0; i < currentDataset.getRowCount(); i++) {
//System.out.println(i);
for(int j = 0; j < currentDataset.getColumnCount(); j++) {
Number numberValue = currentDataset.getValue(i, j);
//System.out.println("Column " + j + " key: " + currentDataset.getColumnKey(j));
double value = numberValue == null ? Double.NaN : numberValue.doubleValue();
if(value > maxValue) {
maxValue = value;
}
}
}
// Add 10% to top margin
double tenPercent = maxValue * 0.1;
maxValue = (Math.round((maxValue * 1.1) / tenPercent) * tenPercent) + tenPercent;
// Set max bar height to max value
ValueAxis yAxis = plot.getRangeAxis();
yAxis.setAutoRange(false);
yAxis.setRange(0, maxValue);
CategoryAxis xAxis = plot.getDomainAxis();
// Set label font size
xAxis.setTickLabelFont(new Font("Arial", Font.PLAIN, 4));
// Will set single bar colors by value with a custom renderer
CustomColorRenderer customRenderer = new CustomColorRenderer();
// Set the chart to apply the custom renderer
plot.setRenderer(customRenderer);
}
}
}
Here is what my chart looks like currently:
Note that the domain axis is displaying keys such as "1:N:A". In this case, 1 refers to the order, N refers to November, A refers to the value being "Actual" vs. "Estimated" which are the two series. All I'd like to do is change the visible tick label to "Nov" for the "1:N:A" example. Things like custom label generators change the labels for other parts of the chart and not the tick labels. I can set the tick label fonts successfully but just cannot seem to get the labels themselves to change.
Edit: The other tricky part about this situation is that the requirement is to display 13 months comprising the previous 11, current, and the upcoming. The upcoming month is always an estimated value, hence the "A" and "E" series). This makes it painful since that means there's always a duplicate month therefore columns that will want to merge.
Any help would be appreciated. Let me know if more info is needed.
Crossposted to http://www.jfree.org/forum/viewtopic.php?f=3&t=117811
A custom CategoryItemLabelGenerator, which is typically used to label the bars, is probably not the right choice for this. As shown here, a CategoryAxis obtains the text of the category labels from the column keys of the CategoryDataset via the plot's getCategoriesForAxis() method. You can specify the desired keys when you create the dataset.