JavaFX Charting - CategoryAixis Resize Issue - charts

I have a user-resizable chart. When the chart gets smaller the CategoryAxis rotates and you can no longer see most of the category labels. Here is a gif showing the problem:
Is there any way to stop the labels from rotating?
I know I can add a listener to the rotation property and rotate it back to 0 if the rotation changes. However, when I do that it doesn't prevent the spacing from adjusting, so the labels just get cut off (you only see the last few characters of the label).
Here is the code for the included gif, you'll see the problem as you resize the window:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
public class HorizontalBarExample extends Application {
#Override
public void start(Stage stage) {
NumberAxis xAxis = new NumberAxis();
CategoryAxis yAxis = new CategoryAxis();
BarChart<Number, String> bc = new BarChart<Number, String>(xAxis, yAxis);
bc.setBarGap(0d);
bc.setCategoryGap(0);
xAxis.setTickLabelRotation(90);
yAxis.tickLabelRotationProperty().set(0d);
XYChart.Series<Number, String> series1 = new XYChart.Series<>();
series1.setName("example");
for (int i = 0; i < 10; i++)
series1.getData().add(new XYChart.Data<Number, String>(Math.random() * 5000, "long data label number" + i));
bc.getData().add(series1);
Scene scene = new Scene(bc, 800, 600);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

This issue is not easily fixable (IMO).
What's going on
The CategoryAxis code for Java 8u60 internally maintains a private effectiveTickLabelRotation member. This member can at times, due to the internal implementation, override whatever value you set for the publicly available tickLabelRotationProperty. So you don't really have much control over the functionality.
Various (failed) attempts to fix it using public API
One way that it overrides is when the the category axis is set to autoRanging, so you can set autoRanging to false and also manually set the categories using the CategoryAxis::setCategory method. This kind of fixes the issue because then the effectiveRotation is not used when the graph gets small, it respects the rotation you want and the text stays vertical.
However, even with switching auto ranging off and manually setting categories, there are other quirks in the implementation which prevent a reasonable result. The internal layout algorithm for the CategoryAxis still thinks that the category labels have been rotated, so it does not allocate enough space for the labels when the chart gets smaller. The CategoryAxis class is final so the layout logic can't be overridden in a subclass. A quick hack would be set a minimum width for the axis, yAxis.setMinWidth(200);. Unfortunately the CategoryAxis layout algorithm does not respect the min width setting either.
Options to get this functionality
In short, it's all kind of broken ... you either:
Accept the default behavior as is, OR
You log a bug requesting a fix, OR
You copy the CategoryAxis code to a new class, e.g. HorizontallyLabelledCategoryAxis, make some modifications to allow the layout algorithm to work as you wish and use that new class in place of the original CategoryAxis.
Option 3 is a bit tricky, but doable.
Suggested Work-around
All that said, the approach (e.g. work-around) I'd recommend, if it is acceptable for your UI, is simply not to let the chart get small enough that its layout screws up.
VBox chartHolder = new VBox(bc);
VBox.setVgrow(bc, Priority.ALWAYS);
bc.setMinHeight(300);
Scene scene = new Scene(chartHolder, 800, 600);

Related

Adding label to PolylineConnection in Draw2D

I'm trying to add a label to a PolylineConnection in Draw2d. I'm using the example in java2s as a basis. The problem is that even if I can create the text by using graphics.drawText() on the paintFigure method from the PathFigure object (that extends PolylineConnection), the label is cut out most of the time, as shown in these captures:
To me, it looks like the bounds of the figure are leaving part of the text outside from the paint area, as it does indeed paint correctly in diagonal arrows, which have bigger bounds.
I have tried to set explicitly the bounds of the object, both in constructor and paint methods, but it seems like the PolylineConnection is ignoring them. Any idea of how to solve this or if there is another way of achieving this kind of label?
Please use below figure for your connection figure.
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.MidpointLocator;
import org.eclipse.draw2d.PolygonDecoration;
import org.eclipse.draw2d.PolylineConnection;
public class LabelConnectionFigure extends PolylineConnection {
protected Label label;
public LabelConnectionFigure() {
setTargetDecoration(new PolygonDecoration());
MidpointLocator labelLocator = new MidpointLocator(this, 0);
label = new Label("1");
label.setOpaque(true);
add(label, labelLocator);
}
public void setLabelText(String labelText) {
label.setText(labelText);
}
public String getLabelText() {
return label.getText();
}
}

Gtk - draw event fired for wrong widget, and widget not redrawn

I'm trying to create a custom scrollable text area. I created a DrawingArea and a ScrollBar inside a Grid. I have attached the draw event of DrawingArea to this.on_draw method which simply looks at ScrollBar's value and moves the Cairo.Context appropriately before drawing the Pango.Layout.
The first problem is that this.on_draw is getting invoked whenever the ScrollBar is touched even though I have not registered any events with ScrollBar. How do I prevent this, or check this?
The second problem is that even though this.on_draw is invoked, the changes made to the Context is not displayed unless the ScrollBar value is near 0 or 100 (100 is the upper value of Adjustment). Why is this happening?
I did find out that if I connect the value_changed event of ScrollBar to a method that calls queue_redraw of DrawingArea, it would invoke this.on_draw and display it properly after it. But due to the second problem, I think this.on_draw is getting invoked too many times unnecessarily. So, what is the "proper" way of accomplishing this?
using Cairo;
using Gdk;
using Gtk;
using Pango;
public class Texter : Gtk.Window {
private Gtk.DrawingArea darea;
private Gtk.Scrollbar scroll;
private string text = "Hello\nWorld!";
public Texter () {
GLib.Object (type: Gtk.WindowType.TOPLEVEL);
Gtk.Grid grid = new Gtk.Grid();
this.add (grid);
var drawing_area = new Gtk.DrawingArea ();
drawing_area.set_size_request (200, 200);
drawing_area.expand = true;
drawing_area.draw.connect (this.on_draw);
grid.attach (drawing_area, 0, 0);
var scrollbar = new Gtk.Scrollbar (Gtk.Orientation.VERTICAL,
new Gtk.Adjustment(0, 0, 100, 0, 0, 1));
grid.attach (scrollbar, 1, 0);
this.darea = drawing_area;
this.scroll = scrollbar;
this.destroy.connect (Gtk.main_quit);
}
private bool on_draw (Gtk.Widget sender, Cairo.Context ctx) {
ctx.set_source_rgb (0.9, 0.9, 0.9);
ctx.paint ();
var y_offset = this.scroll.get_value();
stdout.printf("%f\n", y_offset);
ctx.set_source_rgb (0.25, 0.25, 0.25);
ctx.move_to(0, 100 - y_offset);
var layout = Pango.cairo_create_layout(ctx);
layout.set_font_description(Pango.FontDescription.from_string("Sans 12"));
layout.set_auto_dir(false);
layout.set_text(this.text, this.text.length);
Pango.cairo_show_layout(ctx, layout);
return false;
}
static int main (string[] args) {
Gtk.init (ref args);
var window = new Texter ();
window.show_all ();
Gtk.main ();
return 0;
}
}
Also, please point out any (possibly unrelated) mistake if you find one in the above code.
The part that you are missing is that a draw signal does not mean "redraw everything". Instead, GTK+ sets the clip region of the cairo context to the part that needs to be redrawn, so everything else you do doesn't have any effect. The cairo function cairo_clip_extents() will tell you what that region is. The queue_draw_area() method on GtkWidget will allow you to explicitly mark a certain area for drawing, instead of the entire widget.
But your approach to scrollbars is wrong anyway: you're trying to build the entire infrastructure from scratch! Consider using a GtkScrolledWindow instead. This automatically takes care of all the details of scrolling for you, and will give you the overlay scrollbars I mentioned. All you need to do is set the size of the GtkDrawingArea to the size you want it to be, and GtkScrolledWindow will do the rest. The best way to do this is to subclass GtkDrawingArea and override the get_preferred_height() and/or get_preferred_width() virtual functions (being sure to set both minimum and natural sizes to the sizes you want for that particular dimension). If you ever need to change this size later, call the queue_resize() method of GtkWidget. (You probably could get away with just using set_size_request(), but what I described is the preferred way of doing this.) Doing this also gives you the advantage of not having to worry about transforming your cairo coordinates; GtkScrolledWindow does this for you.

How to set multiple Renderers on a single XYDataSet in jaspersoft reports customizer for jFreeChart

We have a jfreechart in jaspersoft reports community edition where we want to apply two renderers to the same DataSet. The approach we are currently using is not working as expected.
Our current approach is as follows where we attempt to copy the DataSet from index 0 into index 1 and then set a renderer for each index.
xyplot.setDataset( 1, xyplot.getDataset(0) );
xyplot.setRenderer( 1, XYLineAndShapeRenderer_DashedLines );
xyplot.setRenderer( 0, xYDifferenceRenderer_GrayBand );
We don't get any errors, but the line is not dashed and we do get the gray band but it is not drawn correctly.
However when we comment out one or the other, they work fine on their own.
It kinda feels like the second one overwrites the first one.
Is this the right approach to setting multiple renderers on a single DataSet and if so what are we doing wrong?
Or should a different approach be taken and if so what is it?
For the renderers to work correctly you need 2 different dataset (2:nd needs to be another object in your case a clone not a pointer) and 2 different renderer's (seems that you already have this).
XYDataset xyDataset1 = .... //I'm a dataset
XYDataset xyDataset2 = .... //I'm a another dataset if same values I need to be a clone
//you can't do xyDataset2 = xyDataset1 since like this I become xyDataset1
XYPlot plot = chart.getXYPlot();
plot.setDataset(0, xyDataset1);
plot.setDataset(1, xyDataset2);
XYLineAndShapeRenderer renderer0 = new XYLineAndShapeRenderer();
//do you personalizzation code
XYLineAndShapeRenderer renderer1 = new XYLineAndShapeRenderer();
//do you personalizzation code
plot.setRenderer(0, renderer0);
plot.setRenderer(1, renderer1);
Ok, so here is what finally solved it. I was attempting to use two renderers, one for the grey band and one for the dashed lines, but I only needed to use one.
So the final code ended up being:
package gprCustomizer;
import org.jfree.chart.JFreeChart;
import net.sf.jasperreports.engine.JRChart;
import net.sf.jasperreports.engine.JRChartCustomizer;
import org.jfree.chart.renderer.xy.XYDifferenceRenderer;
import java.awt.BasicStroke;
import org.jfree.chart.plot.XYPlot;
import java.awt.Color;
public class GPRCustomizations implements JRChartCustomizer {
public void customize(JFreeChart chart, JRChart jrChart) {
// Get Plot
XYPlot plot = (XYPlot)chart.getPlot();
// Apply Gray Band Style
XYDifferenceRenderer xYDifRnd_GrayBand = new XYDifferenceRenderer();
xYDifRnd_GrayBand.setNegativePaint(Color.lightGray);
xYDifRnd_GrayBand.setPositivePaint(Color.lightGray);
xYDifRnd_GrayBand.setShapesVisible(false);
xYDifRnd_GrayBand.setRoundXCoordinates(true);
// Apply Dashed Style to Series 0,1
xYDifRnd_GrayBand.setSeriesStroke(0,
new BasicStroke(
2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
1.0f, new float[] {6.0f, 6.0f}, 0.0f
)
);
xYDifRnd_GrayBand.setSeriesStroke(1,
new BasicStroke(
2.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND,
1.0f, new float[] {6.0f, 6.0f}, 0.0f
)
);
plot.setRenderer(xYDifRnd_GrayBand);
// Remove Borders from Legend
if(chart.getLegend() != null)
{
chart.getLegend().setBorder(0.0, 0.0, 0.0, 0.0);
}
}
}
Which produced the expected outcome of the grey band and the dashed lines either side:

How to create a GEF figure with separate label?

I've been trying to create a Draw2D Figure that consists of two parts - a central resizeable shape, such as a circle or rectangle, and an editable label for the bottom part. An example of this type of figure is the icon/label you see on a computer's Desktop.
The first attempt was to create a parent container figure with two child sub-figures - a shape figure placed centrally and a label placed at the bottom. It also implemented HandleBounds so that selection and resizing occurs only on the upper shape sub-figure. This turned out not to be a working solution because as the label gets wider with more text so does the main parent figure and consequently the central shape figure. In other words the overall parent figure is as wide as the child label figure.
What I'm seeking is a Figure that maintains the size of the shape figure but allows the width of the label figure to grow independently. Exactly the same behaviour as a desktop icon.
Ok I get your question right now. It's impossible to do what you want:
The parent figure can't be smaller than one of its children or this child will not be visible !!!
You have to create a container figure as you mentioned with an XYLayout and "manually" place and "size" the 2 (the shape and the label) children figure inside this layout using the IFigure.add(IFigure child, Object constraint) method with a Constraint of type Rectangle (Draw2d)
Edit with code sample
Here is an example of what your figure class could look like:
package draw2dtest.views;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Ellipse;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FigureListener;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Rectangle;
public class LabeledFigure extends Figure {
private final Figure shapeFigure;
private final Label labelFigure;
private Rectangle customShapeConstraint;
public LabeledFigure(String label) {
setLayoutManager(new XYLayout());
setBackgroundColor(ColorConstants.lightGray);
setOpaque(true);
shapeFigure = new Ellipse();
this.add(shapeFigure);
shapeFigure.setBackgroundColor(ColorConstants.yellow);
shapeFigure.addMouseListener(new MouseListener.Stub() {
#Override
public void mousePressed(MouseEvent me) {
customShapeConstraint = new Rectangle(
(Rectangle) LabeledFigure.this.getLayoutManager()
.getConstraint(shapeFigure));
customShapeConstraint.width -= 6;
customShapeConstraint.x += 3;
LabeledFigure.this.getLayoutManager().setConstraint(
shapeFigure, customShapeConstraint);
LabeledFigure.this.revalidate();
}
});
labelFigure = new Label(label);
labelFigure.setOpaque(true);
labelFigure.setBackgroundColor(ColorConstants.green);
labelFigure.addMouseListener(new MouseListener.Stub() {
#Override
public void mousePressed(MouseEvent me) {
Rectangle shapeFigureConstraint = new Rectangle(0, 0,
bounds.width, bounds.height - 15);
LabeledFigure.this.getLayoutManager().setConstraint(
shapeFigure, shapeFigureConstraint);
LabeledFigure.this.revalidate();
}
});
this.add(labelFigure);
this.addFigureListener(new FigureListener() {
#Override
public void figureMoved(IFigure source) {
Rectangle bounds = LabeledFigure.this.getBounds();
Rectangle shapeFigureConstraint = new Rectangle(0, 0,
bounds.width, bounds.height - 15);
LabeledFigure.this.getLayoutManager().setConstraint(
shapeFigure, shapeFigureConstraint);
Rectangle labelFigureConstraint = new Rectangle(0,
bounds.height - 15, bounds.width, 15);
if (customShapeConstraint != null) {
labelFigureConstraint = customShapeConstraint;
}
LabeledFigure.this.getLayoutManager().setConstraint(
labelFigure, labelFigureConstraint);
}
});
}
}
This is not a clean class but it should be a good entry to show you how to achieve your goal. This is an example based on pure Draw2d without any Gef code, thus the resizing of the shape is done by clicking in the yellow Ellipse (the size is decreased) and on the green label (the initial size is restored)
To test this class I created a simple Eclipse view as following:
#Override
public void createPartControl(Composite parent) {
FigureCanvas fc = new FigureCanvas(parent, SWT.DOUBLE_BUFFERED);
fc.setBackground(ColorConstants.red);
Panel panel = new Panel();
panel.setLayoutManager(new XYLayout());
LabeledFigure labeledFigure = new LabeledFigure("This is the label");
fc.setContents(panel);
panel.add(labeledFigure, new Rectangle(10,10, 200,100));
}
Hope this can help,
Manu

I need to know when a VerticalPanel changes size

I'm using gwt-dnd to implement drag-and-drop functionality in my GWT program. To get scrolling to work right, I need
<ScrollPanel>
<AbsolutePanel>
<VerticalPanel>
<!-- lots of draggable widgets -->
</VerticalPanel>
</AbsolutePanel>
</ScrollPanel>
I have to manually set the size of the AbsolutePanel to be large enough to contain the VerticalPanel. When I add widgets to the VerticalPanel, though, the size reported by VerticalPanel.getOffsetHeight() isn't immediately updated - I guess it has to be rendered by the browser first. So I can't immediately update the AbsolutePanel's size, and it ends up being too small. Argh!
My stop-gap solution is to set up a timer to resize the panel 500ms later. By then, getOffsetHeight will usually be returning the updated values. Is there any way to immediately preview the size change, or anything? Or, alternatively, can I force a render loop immediately so that I can get the new size without setting up a timer that's bound to be error-prone?
This is a common problem with DOM manipulations. The offsetHeight doesn't update until a short time after components are added. I like to handle this using a recursive timer until a pre-condition is violated. E.g. In your case let there be a function which adds components and will be defined as below:
public void addComponent(Widget w)
{
final int verticalPanelHeight = verticalPanel.getOffsetHeight();
verticalPanel.add(w);
final Timer t = new Timer(){
public void run()
{
if(verticalPanelHeight != verticalPanel.getOffsetHeight())
absolutePanel.setHeight(verticalPanel.getOffsetHeight() + 10 + "px");
else
this.schedule(100);
}
};
t.schedule(100);
}