I am trying to make an XYChart that is scrollable (actually pannable, but since ScrollPane offers a setPannable(boolean pannable) function, they are really equivalent). The type of XYChart I am using is a candle-stick chart, that is essentially identical to the code released by rterp # Github which is available here:
StockChartsFX
Which is itself seemingly derived mostly from Ensemble8, I discovered.
As it was originally setup:
CandleStickChart extends XYChart extends Chart
but then I realized that XYChart does not expose enough data to be able to really mess around with how the chart functions, so I created a ScrollableXYChart class and, since I had already gone that far, decided to create a MultiChart class as well just in case I needed to go up to that level (it is not only scrolling that I am implementing, I am also implementing toolbars and other such things, but do not need assistance with that at the moment). ScrollableXYChart and MultiChart were just copy-pasted from XYChart and Chart respectively (from Java 8u40 ea 23).
In order to make the chart content scrollable, I did the following:
private ScrollPane plotAreaScrollPane = new ScrollPane();
public ScrollableXYChart(Axis<X> xAxis, Axis<Y> yAxis)
{
...
plotAreaScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
plotAreaScrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
plotAreaScrollPane.setPannable(true);
plotAreaScrollPane.setPrefSize(800, 600);
plotAreaScrollPane.setContent(plotArea);
getChartChildren().addAll(plotBackground, plotAreaScrollPane, xAxis, yAxis);
...
}
After doing that the chart content was inside a ScrollPane and the width looked correctly scaled to the chart, the only problem is that, when scrolling to the right, candles are not rendered. I noticed that resizing the window of the application will force more candles to be drawn, so I thought that maybe if I set the width inside of layoutChartChildren(...) that would force more candles to be drawn. It does appear to do that, but doing so makes the ScrollPane unscrollable (the horizontal thumb of the scrollbar becomes the full width of the bar).
After looking into the layoutPlotChildren(...) method of the CandleStickChart I realized that it was iterating over the data like so:
Iterator<Data<String, Number>> iter = getDisplayedDataIterator(series);
while (iter.hasNext())
{
// draw candle
}
So just in case that was cutting off the number of candles drawn, I replaced it with:
int candleIndex = 0;
for (Data<String, Number> datum: getData().get(seriesIndex).getData())
{
// draw candle
}
But that did not change anything.
Here are some screenshots (and yes there is more data than is being shown.. I tried with 5000+ candles) to be sure:
Thanks very much.
Update:
After some more thought and research, I changed the CandleStickChart from an XYChart<String, Number> to an XYChart<Number, Number> Doing so allowed me to set the bounds of the x-axis, via setLowerBound(...) and setUpperBound(...) methods in ValueAxis<Number>. With access to these new methods, I atttempted to add the following change listener on the hvalueProperty of the ScrollPane:
private double lastHoffset = 0d;
...
plotAreaScrollPane.hvalueProperty().addListener((observableValue, oldValue, newValue) -> {
double hmin = plotAreaScrollPane.getHmin();
double hmax = plotAreaScrollPane.getHmax();
double hvalue = plotAreaScrollPane.getHvalue();
double contentWidth = plotAreaScrollPane.getContent().getLayoutBounds().getWidth();
double viewportWidth = plotAreaScrollPane.getViewportBounds().getWidth();
double hoffset = Math.max(0, contentWidth - viewportWidth) * (hvalue - hmin) / (hmax - hmin);
double dHoffset = hoffset - lastHoffset;
lastHoffset = hoffset;
xAxis.setAutoRanging(false);
xAxis.setAnimated(false);
getXAxis().setLowerBound(getxAxis().getLowerBound() + hoffset);
getXAxis().setUpperBound(getxAxis().getUpperBound() + hoffset);
});
Hopeful, I tried seeing what happened. However, no (visible) change.
Update:
I was able to get it working by tinkering with almost every possible setting...I still am not quite sure what combination of settings made it work, but it involved commenting out plotArea.setClip(plotAreaClip); and setting:
plotArea.setAutoSizeChildren(false);
plotContent.setAutoSizeChildren(false);
to:
plotArea.setAutoSizeChildren(true);
plotContent.setAutoSizeChildren(true);
and
plotContent.setManaged(false);
plotArea.setManaged(false);
to:
plotContent.setManaged(true);
plotArea.setManaged(true);
Somehow this makes it work. Someone much smarter than I am could probably explain why, and which settings are actually truly necessary, and what are the (potentially negative) implications of changing these settings, as I am sure the author put them there for good reasons.
Related
As succinctly as I can manage: Given that I need the layout information of a node (the actual height/width of a node as rendered) to compute my animation, how can I get that information before javafx draws a frame with it?
A little bit longer explanation:
I've got a TreeItem that has child items appearing in it (at the front). What I'd like to have is an animation to cause all existing children to slide down to make room for the new item which would slide in. Each child tree-items contents are different and only known at run-time, meaning the height of each child tree item cannot be expressed as a constant.
This got me writing code along these lines:
groupController.groupTreeItem.getChildren().addListener(
new ListChangeListener<TreeItem<Node>>() {
#Override public void onChanged(Change<? extends TreeItem<Node>> c) {
while(c.next()){
if ( ! c.wasAdded()){
continue;
}
TreeItem newItem = c.getAddedSublist().get(0)
new Timeline(
new KeyFrame(
seconds(0),
new KeyValue(view.translateYProperty(), -1 * newItem.getHeight())
),
new KeyFrame(
seconds(1),
new KeyValue(view.translateYProperty(), 0)
)
);
}
}
}
);
the issue here is that as when a treeItem is added to another, its components aren't laid out by the time the invalidation event is fired, meaning newItem.view.getHeight() returns 0.
My next thought was to then have the animation performed as a reaction to both a change in the list content and a sequential change to the height property, (which got me to write some really hideous code that I'd rather not share --listeners adding listeners is not something I really want to write). This almost works, except that javaFX will draw a single frame with the height property set but without the animations translation applied. I could hack down this road further and try to work something out with opacity being toggled and jobs being enqueued for later, but I figured that this would be the path to madness.
I'm wondering if there's some pseudo-class foo or some clever use of a layout property I could use to help me here. I've been poking around at various combinations of various properties, and haven't gotten anywhere. It seems that as soon as the component has a height, it is rendered, regardless of any listeners you put in or around that height assignment.
Any help much appreciated!
have you tried, overriding this
#Override
protected void updateBounds() {
super.updateBounds();
}
How can I remove the y labels from a JFreeChart chart? I use a NumberAxis for my y axis.
I can't seem to find a simple method for this anywhere.
I would like something similar to the remove legend syntax:
// Remove the legend
chart.removeLegend();
Note that I do want to define the title in the NumberAxis:
NumberAxis axis1 = new NumberAxis("A random title");
I simply don't want it to show up in the final chart.
I think that you mean that you want to hide the tick labels for the Y axis, but still want to see the label for the axis itself. Am I correct?
You can do that with:
axis1.setTickLabelsVisible(false);
Okay, if you want to:
hide the label in the chart
but still have it in the NumberAxis
Then there is one solution, that isn't perfect either, that you could use. If you set the "attributed label" (a label with extra font markup attributes), it will draw the attributed label instead.
You can set it to a single space (a zero-length string doesn't work - the font rendering code doesn't allow that).
rangeAxis.setAttributedLabel(" ");
At least axis1.getLabel() will still return your old label, but that's the only benefit of this that I can see.
Otherwise, you can subclass NumberAxis and override the method drawLabel in the subclass to do nothing:
protected AxisState drawLabel(String label, Graphics2D g2,
Rectangle2D plotArea, Rectangle2D dataArea, RectangleEdge edge,
AxisState state) {
return state;
}
My best solution so far is:
axis1.setLabel(null);
But this is just overwriting the original label (so not really a good solution).
This is a simple UI I made to learn the charts API in JavaFX. The AreaChart looks great however, I was wondering if it is possible to hide the tiny dots that signify the plotted values ?
The reason is that as the dots come closer, when the value of X axis increases, they become smaller and harder to comprehend. Sometimes they overlap. In such a situation, the graph would be more legible without the dots.
There is a method to hide the dots (or any other graphical representation of a x/y point).
final LineChart<Number,Number> lineChart =
new LineChart<Number,Number>(xAxis,yAxis);
//here be code...
lineChart.setCreateSymbols(false); //hide dots
Call setNode(...) on the XYChart.Data objects and pass in something invisible.
For example:
XYChart.Data data = new XYChart.Data(x,y);
Rectangle rect = new Rectangle(0, 0);
rect.setVisible(false);
data.setNode(rect);
I've created a XYChart with numerical values different (for example temperatue with pressure) so I want to draw my own axeS just beside my chart. To do the following I've to unshow the YAxis, how should I do that ?
By using a trick: The Chart needs the Y Axis to remain in place so it knows where to render your content. You can, however, hide it. Hide the tick labels and set the axis' opacity to 0 using this code:
chart.getYAxis().setTickLabelsVisible(false);
chart.getYAxis().setOpacity(0);
The axis will still be there, but not shown.
I found that if I hid the chart using the following code:
chart.getXAxis().setTickLabelsVisible(false);
chart.getXAxis().setTickMarkVisible(false);
((Path)chart.getXAxis().lookup(".axis-minor-tick-mark")).setVisible(false);
Then I get about ~10 pixels less blank space on the bottom. IF the space was an issue for your application then you could use css offsets to correct it. This solution may have more predictable offsets.
SOLVED: I got this to work for sharing a common x-axis for two charts stacked vertically:
Create two charts, each with their own identical copy of the x-axis object, setting identical upper and lower bounds (optionally by binding).
Then hide the x-axis in the second chart like this:
chart = new LineChart<Number,Number>(xaxis2,yaxis2) {
{// hide xAxis in constructor, since not public
getChartChildren().remove(getXAxis());
// not getPlotChildren()
}
};
You'll want to set the widths of your y-axes to be the identical, e.g.
int w = 60;
yaxis.setMaxWidth(w);
yaxis.setMinWidth(w);
yaxis.setPrefWidth(w);
yaxis2.setMaxWidth(w);
yaxis2.setMinWidth(w);
yaxis2.setPrefWidth(w);
How to place LayoutPanel in the middle center of parent LayoutPanel (RootLayoutPanel, screen, browser page)? Assuming the inner panel is of fixed size, outer panel is flexible and does resize (with the browser window).
In Vaadin it is one line command:
(VerticalLayout)outerPane).setComponentAlignment(innerPane,
Alignment.MIDDLE_CENTER);
What is the GWT equivalent?
The GWT Layout.Alignment enum provides only BEGIN END STRETCH.
Inadequate. Screen centered placement is essential for displaying smaller forms, dialog boxes, warning messages, login screens etc.
Details on my post on the GWT forum
Thank you
--- UPDATE (27.2.2012) ---------------
To use suggested technique - using setWidgetLeftRight() and setWidgetTopBottom() - I need to compute in fact NW position (x,y) of inner component relative to outer component and do it after every resize of outer container or inner container (content height).
So if GWT does not support centering, and it seems that even in the latest version it does not, there is more coding involved:
outerComponent.setWidgetLeftRight(innerComponent, x, PX, x, PX);
outerComponent.setWidgetTopBottom(innerComponent, y, PX, y, PX);
x,y to be computed as:
x = (w1 – w2) / 2
y = (h1 – h2) / 2
where:
w1 - outer component width (variable, easy to obtain?)
h1 - outer component height (variable, easy to obtain?)
w2 - inner centered component width (fixed)
h2 - inner centered component height (variable, depending on content, potentially tricky to obtain)
All preferably in pixels (PX?) or EM. When mixed with %, more conversion would have to take place.
With every resize event happens the coordinates has to be recalculated (w1, h1 change).
With every change of the internal content the coordinates has to be recalculated (h2 changes).
It is pretty much absolute layout (AbsolutePanel) in disguise.
CSS solution? I found several examples on this server, but none marked as solved. None with both vertical and horizontal centering and flexible inner component height.
Why I don't prefer CSS based layout solution:
Prone to cross browser issues. GWT doesn't help you here.
I consider mixing layout in code and in CSS a bad design decision. CSS is best to be left for “decorative” declarations (font sizes and colors, margins, paddings).
My own bad experience with CSS layouts; easy to break, difficult to debug, lot's of headaches before you put everything together. Best to avoid. I had great hopes towards GWT.
I am thinking about this code based solution:
new subclass of Widget? Panel? Layout? Component? (the inner component)
implement RequiresResize expect, implement onResize() callback method
obtain actual values for variable dimensions (w1, h1, h2). How?
set new coordinates for the inner component. How?
Points to anyone providing solution based on these premises :)
Well you could use a VerticalPanel and set the HorizontalAlignment field or in case you want to use LayoutPanels you can adapt the solution in the GWT docs which you posted in the google groups thread.
LayoutPanel p = new LayoutPanel();
Widget centercontent;
p.add(centercontent);
p.setWidgetLeftRight(centercontent, 5, EM, 5, EM); // Center panel
p.setWidgetTopBottom(centercontent, 5, EM, 5, EM);
This will create a a center panel with 5 em margins left, right, top and bottom like below (only the white panel in the middle will be visible). EM is a fixed size similar to pixel but depends on the base font size. If your base font size (the one defined for your body) is set to 16 px then 1 EM = 16 px (see here for conversion infos).
You could also use relative values for the margins:
p.setWidgetLeftRight(centercontent, 5, PCT, 5, PCT); //5 % left and right margin
p.setWidgetTopBottom(centercontent, 5, PCT, 5, PCT); // 5 % top and bottom margin.
However this way you wont be able to solve your problem entirely (different form sizes -> see screenshots in the google thread) because the center panel will always have fixed margins (5 EM or 5 %) independent of the vertical size of the center panel.
Furthermore the center panel's content will be clipped if it is taller than the available screen size and will be resized when you resize your browser.
So I think the better approach is to put a FlowPanel/HTMLPanel inside a LayoutPanel and apply CSS styles to allow natural resizing.
What if you added a ResizeListener to your center panel (I'm not sure this works, will try it now). Basically, whenever the window resizes, the panel centers itself (by calculating it's height relative to the height of its parent).
EDIT
I ended up getting what I wanted by letting my panel implement ResizeHandler and having the following:
#Override public void onResize(ResizeEvent event) {
// for my case, this is the first parent element with the full height.
Widget parent = getParent().getParent();
int height = parent.getOffsetHeight();
// could make this more exact by including height of this panel.
getElement().getStyle().setPaddingTop((int)((height - 80)/2), Unit.PX);
}
// my panel is not initially visible
#Override public void onAttach() {
super.onAttach();
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
#Override public void execute() {
onResize(null);
}
});
}