How to make button from an Image in Processing - user-interface

The code I wrote is below. I want to make the REGION_1 and REGION_2 buttons from images(or shapes).
I have 2 questions:
I couldn't see a function for addButton that has the image feature. Is there a way to use an image directly as the button itself?
Is there a way to make the buttons in the shape of rings? (No filled circles)
Here is the piece of my code and the screenshot from the UI:
Group RegionGroup = cp5.addGroup("REGIONS")
.setPosition(30,200)
.setWidth(150)
.setHeight(30)
.setFont(font2)
.moveTo(SetupGroup);
background(0);
noStroke();
;
cp5.addButton("REGION_1") // The button
.setPosition(40,10) // x and y relative to the group
.setSize(90, 50) // (width, height)
.setFont(font)
.moveTo(RegionGroup); // add it to the group
loadImage("button1.png"); //????
;
cp5.addButton("REGION_2") // The button
.setPosition(40,70) // x and y relative to the group
.setSize(90, 50) // (width, height)
.setFont(font)
.moveTo(RegionGroup); // add it to the group
loadImage("button2.png"); //?????
;

You should be able to call setImage() on the button instance, for example:
cp5.addButton("REGION_1") // The button
.setPosition(40,10) // x and y relative to the group
.setSize(90, 50) // (width, height)
.setFont(font)
.moveTo(RegionGroup)
.setImage(loadImage("button1.png")); // add it to the group
cp5.addButton("REGION_2") // The button
.setPosition(40,70) // x and y relative to the group
.setSize(90, 50) // (width, height)
.setFont(font)
.moveTo(RegionGroup)
.setImage(loadImage("button2.png")); // add it to the group
If you have four images representing the four button states you could also do something like .setImages(yourPImageArrayHere);
Regarding making the buttons in the shape of a circle, that would be possible via custom views, though the code would slightly more complex. You can use the ControlP5customView example as a starting point. Here's a modified version that displays the rings with no fills:
/**
* ControlP5 Custom View
*
*
* find a list of public methods available for the ControllerDisplay Controller
* at the bottom of this sketch.
*
* by Andreas Schlegel, 2012
* www.sojamo.de/libraries/controlp5
*
*/
import controlP5.*;
ControlP5 cp5;
void setup() {
size(400, 400);
smooth();
cp5 = new ControlP5(this);
cp5.addButton("hello")
.setPosition(50, 100)
.setSize(150,150)
.setView(new CircularButton())
;
cp5.addButton("world")
.setPosition(250, 100)
.setSize(50,50)
.setView(new CircularButton())
;
}
void draw() {
background(ControlP5.BLACK);
}
public void hello(int theValue) {
println("Hello pressed");
}
public void world(int theValue) {
println("World pressed");
}
/**
* to define a custom View for a controller use the ContollerView<T> interface
* T here must be replace by the name of the Controller class your custom View will be
* applied to. In our example T is replace by Button since we are aplpying the View
* to the Button instance create in setup. The ControllerView interface requires
* you to implement the display(PApplet, T) method. Same here, T must be replaced by
* the Controller class you are designing the custom view for, for us this is the
* Button class.
*/
class CircularButton implements ControllerView<Button> {
public void display(PGraphics theApplet, Button theButton) {
theApplet.pushMatrix();
theApplet.noFill();
theApplet.strokeWeight(9);
if (theButton.isInside()) {
if (theButton.isPressed()) { // button is pressed
theApplet.stroke(ControlP5.LIME);
} else { // mouse hovers the button
theApplet.stroke(ControlP5.YELLOW);
}
} else { // the mouse is located outside the button area
theApplet.stroke(ControlP5.GREEN);
}
theApplet.ellipse(0, 0, theButton.getWidth(), theButton.getHeight());
// center the caption label
int x = theButton.getWidth()/2 - theButton.getCaptionLabel().getWidth()/2;
int y = theButton.getHeight()/2 - theButton.getCaptionLabel().getHeight()/2;
translate(x, y);
theButton.getCaptionLabel().draw(theApplet);
theApplet.popMatrix();
}
}
/*
a list of all methods available for the ControllerView Controller
use ControlP5.printPublicMethodsFor(ControllerView.class);
to print the following list into the console.
You can find further details about class ControllerView in the javadoc.
Format:
ClassName : returnType methodName(parameter type)
controlP5.ControllerView : void display(PApplet, T)
*/
This is more complex but also flexible. If this is not something worthwhile you could probably get away with making rings with no fills as transparent png skins for the button (using setImage() / setImages())

Related

Can't Trigger MouseEntered on NSButton in Xamarin.Mac

I'm trying to programmatically add a MouseEntered event to a custom NSButton class, and I can't seem to get it to fire. I'm writing a Mac OS application in Visual Studio for Mac, using Xamarin.Mac. I need to add the event in code because I'm creating the buttons dynamically.
Here's my ViewController where these buttons are being created. They're instantiated in the DrawHexMap method near the bottom.
public partial class MapController : NSViewController
{
public MapController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
DrawHexMap();
}
partial void GoToHex(Foundation.NSObject sender)
{
string[] coordinateStrings = ((NSButton)sender).Title.Split(',');
Hex chosenHex = HexRepo.GetHex(coordinateStrings[0], coordinateStrings[1]);
HexService.currentHex = chosenHex;
NSTabViewController tp = (NSTabViewController)ParentViewController;
tp.TabView.SelectAt(1);
}
private void DrawHexMap()
{
double height = 60;
for (int x = 0; x < 17; x++) {
for (int y = 0; y < 13; y++) {
HexButton button = new HexButton(x, y, height);
var handle = ObjCRuntime.Selector.GetHandle("GoToHex:");
button.Action = ObjCRuntime.Selector.FromHandle(handle);
button.Target = this;
View.AddSubview(button);
}
}
}
}
And here's the custom button class.
public class HexButton : NSButton
{
public NSTrackingArea _trackingArea;
public HexButton(int x, int y, double height)
{
double width = height/Math.Sqrt(3);
double doubleWidth = width * 2;
double halfHeight = height/2;
double columnNudge = decimal.Remainder(x, 2) == 0 ? 0 : halfHeight;
Title = x + "," + y;
//Bordered = false;
//ShowsBorderOnlyWhileMouseInside = true;
SetFrameSize(new CGSize(width, height));
SetFrameOrigin(new CGPoint(width + (x * doubleWidth), (y * height) + columnNudge));
_trackingArea = new NSTrackingArea(Frame, NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited, this, null);
AddTrackingArea(_trackingArea);
}
public override void MouseEntered(NSEvent theEvent)
{
base.MouseEntered(theEvent);
Console.WriteLine("mouse enter");
}
}
So as you can see, I'm creating a tracking area for the button and adding it in the constructor. Yet I can't seem to get a MouseEntered to fire. I know the MouseEntered override in this class works, because when I call button.MouseEntered() directly from my code, the method fires.
A few other things I've tried include: Commenting out the lines that set the Action and Target in the ViewController, in case those were overriding the MouseEntered handler somehow. Setting those values inside the HexButton constructor so that the Target was the button instead of the ViewController. Putting the MouseEntered override in the ViewController instead of the button class. Creating the tracking area after the button was added as a subview to the ViewController. None of these made a difference.
Any help would be much appreciated! It's quite difficult to find documentation for Xamarin.Mac...
Thanks!
You are adding the Frame as the region tracked, but you are attaching the tracking area to the view that you are creating the region from, thus you need to track the Bounds coords.
_trackingArea = new NSTrackingArea(Bounds, NSTrackingAreaOptions.ActiveInKeyWindow | NSTrackingAreaOptions.MouseEnteredAndExited, this, null);
Note: If you were tracking from the view that you are adding the buttons to, then you would need to track the "Frame" as it is the tracking region is relative to the view being tracked, not its children.

How can I convert mouse pointer coordinates to a MapPoint using Gluon Maps 1.0.1?

Using Gluon Mobile 4 and Gluon Maps 1.0.1, I am displaying a map with a layer showing foot steps. When the users double clicks the mouse button, a new foot step is shown. This works great, but I currently need a workaround to convert from pointer coordinates (where the user clicked) to MapPoints (needed for the layer).
Here is how the mouse click is obtained:
public MainView(String name) {
super(name);
MapView mapView = new MapView();
mapView.setZoom(18f);
mapView.setCenter(NUREMBERG);
layer = new FootStepsLayer();
mapView.addLayer(layer);
setCenter(mapView);
layer.addPoint(NUREMBERG);
setOnMouseClicked(event -> {
if ((event.getButton() == MouseButton.PRIMARY)
&& (event.getClickCount() == 2)) {
double x = event.getX();
double y = event.getY();
layer.addPoint(x, y);
}
});
}
Currently my layer implementation looks like this:
public class FootStepsLayer extends MapLayer {
private static final Image FOOTSTEPS
= new Image(FootStepsLayer.class.getResourceAsStream("/footsteps.png"),
32, 32, true, true);
private final ObservableList<Pair<MapPoint, Node>> points
= FXCollections.observableArrayList();
public void addPoint(MapPoint mapPoint) {
Node node = new ImageView(FOOTSTEPS);
Pair<MapPoint, Node> pair = new Pair<>(mapPoint, node);
points.add(pair);
getChildren().add(node);
markDirty();
}
public void addPoint(double x, double y) {
Bounds bounds = baseMap.getParent().getLayoutBounds();
baseMap.moveX(x - bounds.getWidth() / 2);
baseMap.moveY(y - bounds.getHeight() / 2);
addPoint(new MapPoint(baseMap.centerLat().get(),
baseMap.centerLon().get()));
}
#Override
protected void layoutLayer() {
// Warning: suggested conversion to functional style crashed app on BlueStacks
for (Pair<MapPoint, Node> element : points) {
MapPoint mapPoint = element.getKey();
Node node = element.getValue();
Point2D point = baseMap.getMapPoint(mapPoint.getLatitude(), mapPoint.getLongitude());
node.setVisible(true);
node.setTranslateX(point.getX());
node.setTranslateY(point.getY());
}
}
}
My workaround is in public void addPoint(double x, double y): I am calling moveX() and moveY(), because after that I can query centerLat() and centerLong(). This is not ideal because the map moves and the new foot step becomes the center of the map. What I want is the map position to remain unchanged.
If I have not overlooked it, there seems to be no API for converting mouse coordinates to geo locations. As answered in question create a polyline in gluon mapLayer, the BaseMap class has two getMapPoint methods, but I have found none the other way round. But there must be a way to do it. ;-)
If you have a look at BaseMap, there is already one method that does precisely what you are looking for, but only for the center of the map: calculateCenterCoords.
Based on it, you could add your own method to BaseMap, where the sceneX and sceneY coordinates are taken into account instead:
public MapPoint getMapPosition(double sceneX, double sceneY) {
double x = sceneX - this.getTranslateX();
double y = sceneY - this.getTranslateY();
double z = zoom.get();
double latrad = Math.PI - (2 * Math.PI * y) / (Math.pow(2, z) * 256);
double mlat = Math.toDegrees(Math.atan(Math.sinh(latrad)));
double mlon = x / (256 * Math.pow(2, z)) * 360 - 180;
return new MapPoint(mlat, mlon);
}
Then you can expose this method in MapView:
public MapPoint getMapPosition(double sceneX, double sceneY) {
return baseMap.getMapPosition(sceneX, sceneY);
}
So you can use it on your map:
mapView.setOnMouseClicked(e -> {
MapPoint mapPosition = mapView.getMapPosition(e.getSceneX(), e.getSceneY());
System.out.println("mapPosition: " + mapPosition.getLatitude()+ ", " + mapPosition.getLongitude());
});
This method should be part of Maps, so feel free to create a feature request or even a pull request.

LibGDX overwriting TouchUp

I am trying to implement a simple button in LibGDX 0.9.8 version that would change the screens on TouchUp event. However, Eclipse shows me yellow underlining on my overwrite of TouchUp method saying that it is not used anywhere.
public void resize(int width, int height) {
//Setting up stage failsafe
if(_stage == null){
_stage = new Stage(width, height, true);
}
_stage.clear();
Gdx.input.setInputProcessor(_stage);
TextButtonStyle style = new TextButtonStyle();
style.up = _buttonSkin.getDrawable("buttonUp");
style.down = _buttonSkin.getDrawable("buttonDown");
style.font = _font;
_startButton = new TextButton("START GAME" , style);
_exitButton = new TextButton("EXIT", style);
///
///PLACING BUTTONS
///
//start button
_startButton.setWidth(Gdx.graphics.getWidth() / 3);
_startButton.setHeight(Gdx.graphics.getHeight() / 4);
_startButton.setX((Gdx.graphics.getWidth() / 8) * 3);
_startButton.setY((Gdx.graphics.getHeight() / 5) * 3);
_startButton.setTouchable(Touchable.enabled);
_startButton.addListener(new InputListener(){
public boolean touchUp(InputEvent event, float x, float y, int pointer, int button) {
_game.setScreen(new World(_game));
System.out.print("up");
return true;
}
});
//adding buttons to scene
_stage.addActor(_startButton);
//_stage.addActor(_exitButton);
I did some research on the web and there were couple of posts saying that in some version of LibGDX this event method is absent so I did check the library and found my desired method in there.
In InputListener class :/** Called when a mouse button or a finger touch goes up anywhere, but only if touchDown previously returned true for the mouse
* button or touch. The touchUp event is always {#link Event#handle() handled}.
* #see InputEvent */
public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
}
Anyone can see what am I doing wrong ? I am using the textbutton from com.badlogic.gdx.scenes.scene2d.ui.TextButton;
From the libgdx javadocs, it seems that the method you're looking for is.-
public boolean touchUp(int screenX, int screenY, int pointer, int button)
without parameter InputEvent event. Make sure you remove that extra parameter, declare the method as public, and add the tag #Override as #P.T. suggested.-
#Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
super.touchUp(screenX, screenY, pointer, button);
// Do your stuff here
}

Blackberry UI different devices

I'm working on a blackberry application. I have a header which you can see in the following picture, with low resolution it takes the space more than the width, currently testing on BB Bold 9900 Simulator. So I tried using the following code to prevent UI destruction.
Questions:
Is it the correct code to prevent UI destruction?
If yes then by using this code how can we align the button to right and logo to left.
Which design/UI should I follow to prevent UI destruction in different resolution devices?
ImageButton Login = new ImageButton(configModel.getLoginButton(), FOCUSABLE, "login.png", "plogin.png",0x9cbe95);
HorizontalFieldManager hfm = new HorizontalFieldManager(Field.FIELD_HCENTER);
HorizontalFieldManager kenexaLogoHfm = new HorizontalFieldManager(hfm.FIELD_LEFT);
HorizontalFieldManager loginButtonHfm = new HorizontalFieldManager(hfm.FIELD_RIGHT);
Bitmap logo = Bitmap.getBitmapResource("logo.png");
NullField nullField = new NullField();
BitmapField kenexaLogo = new BitmapField(logo);
kenexaLogoHfm.add(new LabelField("", NON_FOCUSABLE));
kenexaLogoHfm.add(kenexaLogo);
kenexaLogoHfm.add(nullField);
loginButtonHfm.add(Login);
hfm.setPadding(0, 5, 0, 5);
hfm.add(kenexaLogoHfm);
hfm.add(loginButtonHfm)
add(hfm);
Following is the code for ImageButton
public class ImageButton extends Field{
//Image Button Class
private String _label;
private int _labelHeight;
private int _labelWidth;
private Font _font;
private Bitmap _currentPicture;
private Bitmap _onPicture;
private Bitmap _offPicture;
int color;
public ImageButton(String text, long style ,String img, String img_hvr, int color){
super(style);
_offPicture = Bitmap.getBitmapResource(img);
_onPicture = Bitmap.getBitmapResource(img_hvr);
_font = Font.getDefault().derive(Font.BOLD, 7, Ui.UNITS_pt);
_label = text;
_labelHeight = _onPicture.getHeight();
_labelWidth = _onPicture.getWidth();
this.color = color;
_currentPicture = _offPicture;
}
public void setImage(String img){
_offPicture = Bitmap.getBitmapResource(img);
_currentPicture = _offPicture;
}
/**
* #return The text on the button
*/
public void setText(String text){
_label = text;
}
String getText(){
return _label;
}
/**
* Field implementation.
* #see net.rim.device.api.ui.Field#getPreferredHeight()
*/
public int getPreferredHeight(){
return _labelHeight;
}
/**
* Field implementation.
* #see net.rim.device.api.ui.Field#getPreferredWidth()
*/
public int getPreferredWidth(){
return _labelWidth;
}
/**
* Field implementation. Changes the picture when focus is gained.
* #see net.rim.device.api.ui.Field#onFocus(int)
*/
protected void onFocus(int direction) {
_currentPicture = _onPicture;
// invalidate();
super.onFocus(direction);
}
/**
* Field implementation. Changes picture back when focus is lost.
* #see net.rim.device.api.ui.Field#onUnfocus()
*/
protected void onUnfocus() {
_currentPicture = _offPicture;
invalidate();
super.onUnfocus();
}
/**
* Field implementation.
* #see net.rim.device.api.ui.Field#drawFocus(Graphics, boolean)
*/
// protected void drawFocus(Graphics graphics, boolean on) {
// // Do nothing
// }
protected void drawFocus(Graphics graphics, boolean on) {
if (on) {
//draw your own custom focus.
}
}
/**
* Field implementation.
* #see net.rim.device.api.ui.Field#layout(int, int)
*/
protected void layout(int width, int height) {
setExtent(Math.min( width, getPreferredWidth()),
Math.min( height, getPreferredHeight()));
}
/**
* Field implementation.
* #see net.rim.device.api.ui.Field#paint(Graphics)
*/
protected void paint(Graphics graphics){
// First draw the background colour and picture
graphics.setColor(this.color);
graphics.fillRect(0, 0, getWidth(), getHeight());
graphics.drawBitmap(0, 0, getWidth(), getHeight(), _currentPicture, 0, 0);
// Then draw the text
graphics.setColor(Color.WHITE);
graphics.setFont(_font);
graphics.setFont(graphics.getFont().derive(Font.BOLD));
graphics.drawText(_label, 5,9,
(int)( getStyle() & DrawStyle.ELLIPSIS | DrawStyle.VALIGN_MASK | DrawStyle.HALIGN_MASK),
getWidth() - 6 );
}
/**
* Overridden so that the Event Dispatch thread can catch this event
* instead of having it be caught here..
* #see net.rim.device.api.ui.Field#navigationClick(int, int)
*/
protected boolean navigationClick(int status, int time){
fieldChangeNotify(1);
return true;
}
}
I would recommend getting rid of a couple of unnecessary HorizontalFieldManager objects, and just using this code:
HorizontalFieldManager hfm = new HorizontalFieldManager(Field.USE_ALL_WIDTH);
VerticalFieldManager vfm = new VerticalFieldManager(Field.USE_ALL_WIDTH);
Bitmap logo = Bitmap.getBitmapResource("logo.png");
BitmapField kenexaLogo = new BitmapField(logo, Field.FIELD_LEFT);
hfm.add(new NullField()); // to take focus from login button
hfm.add(kenexaLogo);
vfm.add(new ButtonField("Login", Field.FIELD_RIGHT));
hfm.add(vfm);
hfm.setPadding(0, 5, 0, 5);
add(hfm);
Note: I had to create a new ButtonField, because you didn't show how your login button was created (in the original question), and it's important, here.
The problem is that a HorizontalFieldManager tries to be efficient with the space it uses. So, if you only add two real fields (the logo, and the button), and those are not enough to fill the width, it's not going to put the button on the right.
You need to add a VerticalFieldManager to your HoriztonalFieldManager, tell it to use all available width, and then pass it the button, which has been created with the FIELD_RIGHT flag. Those flags for fields tell their parent containers where to lay them out. The VerticalFieldManager will respect FIELD_RIGHT and put the login button at the right, as you wish.
More
I might also suggest that instead of hard coding a 5 pixel right and left padding, you set a padding constant that's a given percentage of screen width:
int pad = Display.getWidth() / 100;
hfm.setPadding(0, pad, 0, pad);
but, that's a separate issue.
Another thing I find useful when trying to debug layout problems like this is to set a different background color on each manager or field in my layout. That helps you see and understand what's happening. Just use this:
hfm.setBackground(BackgroundFactory.createSolidBackground(Color.RED)); // TODO: remove!

JfreeChart limit the number of legend items displayed

I'm in charge of maintaining an application which can draw graphs using JFreeChart.
The application is written in Eclipse-RCP and SWT and use a ChartComposite to display the charts.
The ChartComposite has been partially overridden in order to customize contextual menus depending on the selection:
#Override
public void createPartControl(Composite parent) {
super.createPartControl(parent);
chart = createChart(timeSeriesDataset);
chartComposite = new MyChartComposite(this, parent, SWT.NONE, chart, true);
chartComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
selectionProvider = new GenericObjectSelectionProvider();
getSite().setSelectionProvider(selectionProvider);
// add information to the status line:
selectionProvider.addSelectionChangedListener(statusLineListener);
addDropSupport();// add D'n D support for dropping TimeSeries
}
protected JFreeChart createChart(TimeSeriesCollection ptimeSeriesDataset) {
JFreeChart vChart = ChartFactory.createTimeSeriesChart(null, "time", "values", ptimeSeriesDataset, true,
false, false);
vChart.setBackgroundPaint(Color.white);
XYPlot plot = vChart.getXYPlot();
plot.setBackgroundPaint(Color.lightGray);
plot.setDomainGridlinePaint(Color.white);
plot.setRangeGridlinePaint(Color.white);
// plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
plot.setDomainCrosshairVisible(true);
plot.setRangeCrosshairVisible(true);
plot.setRenderer(new /*OptimisedXYLineAndShapeRenderer()*/ StandardXYItemRendererFast());
XYItemRenderer renderer = plot.getRenderer();
renderer.setBaseToolTipGenerator(new MyXYSeriesToolTipGenerator());
renderer.setBaseItemLabelGenerator(new MyXYSeriesItemLabelGenerator());
renderer.setLegendItemLabelGenerator(new MyXYSeriesLegendItemLabelGenerator());
if (renderer instanceof XYLineAndShapeRenderer) {
XYLineAndShapeRenderer r = (XYLineAndShapeRenderer) renderer;
r.setBaseShapesVisible(false);
r.setBaseShapesFilled(true);
}
SimpleDateFormat dateFormat = getDateFormatAbscissa();
if (dateFormat != null){
DateAxis axis = (DateAxis) plot.getDomainAxis();
axis.setDateFormatOverride(dateFormat);
}
return vChart;
}
My problem is that when too many variables are added to the chart (a TimeSeriesChart) the caption takes too much space and the graph disappears from the view:
ChartComposite with 2 series
ChartComposite many series
I tried to create a ScrollComposite to scroll in the ChartComposite and the result is a little better; but it only makes it possible to add more items in the caption before the graph disappears again:
ScrolledComposite scrollableChart = new ScrolledComposite(parent, SWT.BORDER|SWT.V_SCROLL);
chartComposite = new MyChartComposite(this, scrollableChart, SWT.NONE, chart, true);
//chartComposite = new MyChartComposite(this, parent, SWT.NONE, chart, true);
//chartComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
scrollableChart.setContent(chartComposite);
scrollableChart.setExpandVertical(true);
scrollableChart.setExpandHorizontal(true);
scrollableChart.setMinSize(ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH, ChartComposite.DEFAULT_MINIMUM_DRAW_WIDTH);
My question is: How to provide a real scrollbar to the ChartComposite in order to keep the graph when many series are plotted on the graph?
I was able to sync a slider to the XYSeries in SWT using a ChartComosite and Slider objects through the use of FormData. And every time I move the slider I capture that event and update the chart myself according to my needs.
My use case may be different than yours, but it's worth to take a look to my answer here.
If you have questions regarding my implementation, described in that answer, feel free to ask for details
After several unsuccessful tries, I decided to limit the number of LegendItem shown on the chart.
To change the legend items to display in the LegendTitle I used a slider. The most difficult part was to recreate the a new LegendTitle when using the slider; I am not sure that my solution is optimal or elegant but at least it is working.
To make this possible I needed to listen to series creation (ChartChangeEventType.DATASET_UPDATED) to refresh the LegendTitle and set slider values.
So here is the code:
public class MyExampleChartComposite extends ChartComposite {
// snip
/**
* A slider to choose the legend items to display
*/
private Slider legendSlider;
/**
* Number of legend items to display on the chart
*/
private final static int NUMBER_OF_LEGENDITEMS_TO_DISPLAY = 10;
private void createPartControl(Composite parent, int style) {
JFreeChart chart = createChart();
setChart(chart);
legendSlider = new Slider(parent, SWT.NONE|SWT.H_SCROLL);
legendSlider.addSelectionListener(new SelectionListener() {
#Override
public void widgetSelected(SelectionEvent arg0) {
refreshLegend();
}
});
private JFreeChart createChart() {
chart.addChangeListener(new ChartChangeListener() {
#Override
public void chartChanged(ChartChangeEvent e) {
if (e.getType().equals(ChartChangeEventType.DATASET_UPDATED)) {
refreshLegend();
}
}
});
}
/**
* Refresh the the LegendItems and the slider,
* according to slider selection.
*/
public void refreshLegend() {
// Display LegendItems according to the selection
// display the 10 nearest legend item (current selected element included)
int begin = legendSlider.getSelection() - (NUMBER_OF_LEGENDITEMS_TO_DISPLAY/2);
int end = legendSlider.getSelection() + (NUMBER_OF_LEGENDITEMS_TO_DISPLAY/2 -1);
int seriesEndIndex = Math.max(getSeriesCount()-1, 0);
// if begin is less than 0
// set it to 0, and increase end to display 10 items
if (begin < 0) {
begin = 0;
end = NUMBER_OF_LEGENDITEMS_TO_DISPLAY - 1;
}
// if end is greater than the number of series plotted
// set it to the max possible value and increase begin to
// display 10 items
if (end > seriesEndIndex) {
end = seriesEndIndex;
begin = seriesEndIndex - (NUMBER_OF_LEGENDITEMS_TO_DISPLAY - 1);
}
end = Math.min(seriesEndIndex, end);
begin = Math.max(begin, 0);
// Refresh only if begin != end
if (end != begin) {
refreshLegendItems(begin, end);
refreshLegendSlider();
} else {
// in this case no more series are plotted on the chart
// clear legend
getChart().clearSubtitles();
}
}
/**
* Refresh the LegendTitle.
* Display only LegendItems between beginIndex and toIndex,
* to preserve space for the chart.
* #param beginIndex index of the {#link LegendItemCollection} used as the beginning of the new {#link LegendTitle}
* #param endIndex index of the {#link LegendItemCollection} used as the end of the new {#link LegendTitle}
*/
private void refreshLegendItems(int beginIndex, int endIndex) {
// Last 10 items
final LegendItemCollection result = new LegendItemCollection();
// get the renderer to retrieve legend items
XYPlot plot = getChart().getXYPlot();
XYItemRenderer renderer = plot.getRenderer();
// Number of series displayed on the chart
// Construct the legend
for (int i = beginIndex; i <= endIndex; i++) {
LegendItem item = renderer.getLegendItem(0, i);
result.add(item);
}
// Because the only way to create a new LegendTitle is to
// create a LegendItemSource first
LegendItemSource source = new LegendItemSource() {
LegendItemCollection lic = new LegendItemCollection();
{lic.addAll(result);}
public LegendItemCollection getLegendItems() {
return lic;
}
};
// clear previous legend title
getChart().clearSubtitles();
// Create the new LegendTitle and set its position
LegendTitle legend = new LegendTitle(source);
legend.setHorizontalAlignment(HorizontalAlignment.CENTER);
legend.setVerticalAlignment(VerticalAlignment.CENTER);
legend.setPosition(RectangleEdge.BOTTOM);
legend.setBorder(1.0, 1.0, 1.0, 1.0);
legend.setVisible(true);
// Add the LegendTitle to the graph
getChart().addLegend(legend);
}
/**
* Set values of the slider according to the number of series
* plotted on the graph
*/
private void refreshLegendSlider() {
int max = getSeriesCount() -1;
int selection = Math.min(legendSlider.getSelection(), max);
legendSlider.setValues(selection, 0, max, 1, 1, 1);
}
}

Resources