I am a newbie to programming GUIs and Processing. My questions is how can I get a list of checkboxes that I can scroll through? What I want is exactly the list of countries on the right here (http://goo.gl/MIKHi4).
I looked through the ControlP5 library and was able to find Checkboxes, but I don't know how I can make a scrollable list of them.
Thank you.
I had also been searching for this last week and hoping that there was a ready-for-use library for me to easily add the scrollable checkboxes to my application, but finally I had no luck. At last, what I did was implementing my own scrollable list of checkboxes.
Firstly, I added a ControlP5 slider as the scroll bar, and then at each frame, got value from the slider and draw the specific checkboxes based on that value.
Suppose that you have a list of 200 countries for the user to select. Then the code will be like:
ControlP5 cp5;
Slider scrollBar;
PFont fLabel;
int boxOver = -1; //Indicate mouse is over which checkbox
boolean[] boxSelected; //Checkbox selected or not
void setup() {
size(1024, 800);
colorMode(HSB, 360, 100, 100);
cp5 = new ControlP5();
scrollbar = cp5.addSlider("scrollbar")
.setPosition(1005, 110)
.setRange(0, 180)
.setSize(15, 490)
.setHandleSize(30)
.setSliderMode(Slider.FLEXIBLE)
.setValue(180); //Put handler at top because 0 value is at bottom of slider
fLabel = createFont("Arial", 12, false);
boxSelected = new boolean[200];
for(int i=0;i<200;i++) {
boxSelected[i] = false;
}
}
void draw() {
noFill();
stroke(200, 255);
rect(820, 110, 200, 490); //The outline of the scrollable box
stroke(150, 255);
int count = 0;
//Suppose that you want to display 20 items each time
for(int i=180-(int)scrollBar.getValue();i<180-(int)scrollBar.getValue()+20;i++) {
if(boxOver < 0) {
if(mouseX>=825 && mouseX<837 && mouseY >= 120+count*24 && mouseY <= 132+count*24) {
boxOver = i;
cursor(HAND);
}
}
if(boxSelected[i]) {
fill(50); //If the box is selected, fill this box
} else {
fill(360);
}
rect(825, 120+count*24, 12, 12); //Draw the box
//Draw the label text
textFont(fLabel);
fill(50);
text(countries[i], 843, 132+count*24); //Suppose the country names are stored in countries[]
count++;
}
}
void mousePressed() {
if(boxOver >=0) {
boxSelected[boxOver] = !boxSelected[boxOver]; //Toggle selection
}
}
Hope this helps you, or anyone who may encounter the same problem in the future.
There is now an example in the experimental examples called ControlP5SliderList
Related
I'm constructing a custom node editor window in Unity, and I've had a look at various resources such as this one, which uses GUI.Box to construct node windows.
This works great, and I'm able to drag these windows around the way I want, however once I add controls to the GUI.Box, I want them to override the Drag() function I've written.
Here's an example of the issue - When I move the vertical slider up, the entire box drags with it.
Is there a way to fix this behavior using GUI.Box, or should I go back to GUI.Window with its built-in GUI.DragWindow()?
Here's a simplified version of the code I'm using:
EditorMouseInput.cs:
private bool ActionLeftMouseDown()
{
mouseDownNode = editor.GetSelectedNode(Input.current.mousePosition);
if (mouseDownNode == null)
editor.StartMovingEditorCanvas();
else
mouseDownNode.IsSelected = true;
}
BaseNodeEditor.cs:
public BaseNode GetSelectedNode(Vector2 mousePos)
{
foreach (BaseNode node in Nodes)
{
if (node.WindowRect.Contains(mousePos))
return node;
}
return null;
}
public void Drag(Vector2 delta)
{
if (!MoveEditorMode && !ConnectionMode)
{
foreach (BaseNode node in Nodes)
{
node.Drag(delta);
}
}
BaseNode.cs:
public void Drag(Vector2 delta)
{
if (IsSelected)
draggedDistance += delta;
}
The vertical slider is added in the derived JumpNode class. Extract of the helper class that constructs the slider:
Vector2 pos = node.WindowRect.position + rect.position * GridSpacing;
value = GUI.VerticalSlider(new Rect(pos, rect.size * GridSpacing), value, maxValue, minValue);
I can see why this doesn't do what I want, but I don't know how to go about it given the GUI controls aren't part of the GUI.Box.
Any help or suggestions, even a nudge towards another source would be greatly appreciated - I feel I've used all the search terms that exist in my head!
Edit - Solved: Thanks to Kleber for solving this one for me. In case anyone else runs into this or a similar issue, the solution for me was in realising that GUI controls consume left mousedown events automatically, so clicking a slider means there's no propagation to the Box to check if it was clicked.
What I needed to do was separate the IsSelected and IsDragged flags in the Node class, and clear IsDragged on mouseUp. I originally used IsSelected to flag both drag enabled, and selected (multiple nodes could be selected and dragged at once).
It's quite a complex tutorial so I didn't read it entirely, but the problem seems to be the MouseDrag detection. Well, basically you want to stop the event propagation when you click on a GUI element inside the Box, right? To do so, you call:
Event.current.Use()
every time the user drags the mouse on one of your components.
Using the resource you've mentioned, I altered the Node class and added a slider inside the Draw() method, ending like this:
public void Draw() {
inPoint.Draw();
outPoint.Draw();
GUI.Box(rect, title, style);
GUI.BeginGroup(rect);
_value = GUI.HorizontalSlider(new Rect(20, 0, 50, 20), _value, 100, -100);
GUI.EndGroup();
}
Another thing you can do is change how you draw your window. Here it's a simple example that I've tested on the latest Unity version (5.6):
private void OnGUI() {
GUI.Box(_rect, string.Empty);
GUI.BeginGroup(_rect);
_value = GUI.VerticalSlider(new Rect(145, 100, 20, 100), _value, 100, -100);
GUI.EndGroup();
var e = Event.current;
if (e.type == EventType.MouseDrag && _rect.Contains(e.mousePosition)) {
_rect.x += e.delta.x;
_rect.y += e.delta.y;
Repaint();
}
}
As you can see, this example doesn't need an Event.current.Use() to work properly.
I just made my image generator work with PNG files. For now, it's divided into 3 categories (backgrounds, objects & texts). These are now all combined, and with every mouse click it randomises these PNGs.
I made three toggles, where you could to choose to show either the background and the objects on top, all of them, or all separate. Whenever I run the sketch, it shows the "grey" background, but when I use the toggles, it doesn't show anything, or shows a flickering image, where the mouse-click can't be used to go to the next image. I can't seem to find the problem. Hopefully, you can help. :)
import controlP5.*;
boolean showBackground = false;
boolean showObjects = false;
boolean showGrids = false;
ControlP5 cp5;
PImage[] myImageArray = new PImage[8];
PImage[] myImageArray2 = new PImage[15];
PImage[] myImageArray3 = new PImage[15];
void setup() {
size(1436, 847);
background(211, 211, 211);
for (int i=0; i<myImageArray.length; i++) {
myImageArray[i] = loadImage ( "o" + i + ".png");
myImageArray2[i] = loadImage ( "g" + i + ".png");
myImageArray3[i] = loadImage( "b" + i + ".jpg");
cp5 = new ControlP5(this);
// create a toggle and change the default look to a (on/off) switch look
cp5.addToggle("showBackground")
.setPosition(40, 250)
.setSize(50, 20)
.setValue(true)
.setMode(ControlP5.SWITCH);
cp5.addToggle("showObjects")
.setPosition(40, 400)
.setSize(50, 20)
.setValue(true)
.setMode(ControlP5.SWITCH);
cp5.addToggle("showGrid")
.setPosition(40, 600)
.setSize(50, 20)
.setValue(true)
.setMode(ControlP5.SWITCH);
}
display();
}
void display() {
image(myImageArray3[(int)random(myImageArray.length)], 0, 0, 1436, 847); // b
image(myImageArray2[(int)random(myImageArray.length)], 0, 0, 1436, 847); // g
image(myImageArray[(int)random(myImageArray.length)], 0, 0, 1436, 847); // o
}
void mousePressed() {
display();
}
void draw() {
pushMatrix();
if (showBackground==false) {
image(myImageArray3[(int)random(myImageArray.length)], 0, 0, 1436, 847); // b
} else {
background(211, 211, 211);
}
if (showGrids==false) {
image(myImageArray2[(int)random(myImageArray.length)], 0, 0, 1436, 847); // g
} else {
background(211, 211, 211);
}
if (showObjects==false) {
image(myImageArray[(int)random(myImageArray.length)], 0, 0, 1436, 847); // o
} else {
background(211, 211, 211);
}
popMatrix();
}
Here are a couple of things where the logic your wrote in your code might not match what you had in mind:
When you call display() on mouse it renders those 3 images once (also it will be different images within those since it's using a randomised index). Similarly in draw(), when an does get picked to be rendered, frames will be flickering fast as a random index is generated multiple times per second(each frame). You may want to randomise indices in a different event (e.g. mouse or key press) and store this value in a variable you can re-use.
the conditions you use in draw(): you probably meant to check if the values are true(toggled enabled/turned on in controlP5) ? (e.g. e.g. if (showBackground==true) and initialise the toggles with false, instead of true?)
a big one: in draw() , after each condition(showBackground,showGrids,showObjects), if it's false, you're clearing the the whole frame (so a previous image would be erased)
you have 3 arrays, but you use the size of the first(myImageArray.length) only, which means, even though you may have more images for myImageArray2 and myImageArray3, you're not loading, nor displaying them.
The third grid is labeled "showGrid" when it should be "showGrids": if you aren't consistent with the toggle labels and variable names, toggles won't update the variable names.
you should use more descriptive names for the arrays: it will make it easier to scan/follow your code on the long run.
there's no need to add toggles multiple times in the for loop where you load images: once will do.
Here's what I mean:
import controlP5.*;
boolean showBackground = false;
boolean showObjects = false;
boolean showGrids = false;
ControlP5 cp5;
PImage[] objects = new PImage[8];
PImage[] grids = new PImage[15];
PImage[] backgrounds = new PImage[15];
int currentImage = 0;
void setup() {
size(1436, 847);
//load objects
for (int i=0; i<objects.length; i++) {
objects[i] = loadImage ( "o" + i + ".png");
}
//load grids
for(int i = 0 ; i < grids.length; i++){
grids[i] = loadImage ( "g" + i + ".png");
}
//load backgrounds
for(int i = 0 ; i < grids.length; i++){
backgrounds[i] = loadImage( "b" + i + ".jpg");
}
//setup UI
cp5 = new ControlP5(this);
// create a toggle and change the default look to a (on/off) switch look
cp5.addToggle("showBackground")
.setPosition(40, 250)
.setSize(50, 20)
.setValue(false)
.setMode(ControlP5.SWITCH);
cp5.addToggle("showObjects")
.setPosition(40, 400)
.setSize(50, 20)
.setValue(false)
.setMode(ControlP5.SWITCH);
cp5.addToggle("showGrids")
.setPosition(40, 600)
.setSize(50, 20)
.setValue(false)
.setMode(ControlP5.SWITCH);
}
void mousePressed() {
//go to next image index
currentImage = currentImage + 1;
//check if the incremented index is still valid, otherwise, reset it to 0 (so it doesn't go out of bounds)
if (currentImage >= objects.length) {
currentImage = 0;
}
}
void draw() {
//clear current frame
background(211);//for gray scale value you can just use one value: the brightness level :)
if (showBackground==true) {
image(backgrounds[currentImage], 0, 0, 1436, 847); // b
}
if (showGrids==true) {
image(grids[currentImage], 0, 0, 1436, 847); // g
}
if (showObjects==true) {
image(objects[currentImage], 0, 0, 1436, 847); // o
}
}
Note that currently the same index is used for all 3 arrays.
You may want to add a separate index variable for each array (e.g. currentObjectIndex, currentBackgroundIndex, currentGridIndex) that you can increment independently of each other.
I recommend having a bit more patience and double checking your code first.
Visualise what each line of code will do, then check if it actually does what you expect it to do. Either you will learn something new or improve your logic.
Also, if mentally joggling 3 arrays is tricky (and it can be), go one step back: try your logic with one array only until you get the hang of it, then move on.
A step backwards is sometimes a step forward when you're going in the wrong direction.
As creative as you'd like to be with Processing, bare in mind, the interface to plugging your ideas to it is still a series of one instruction at a time, each precisely tuned to do exactly what you want it to do. There's room for fun, but unfortunately you need to get past the boring parts first
In a GTK# application, I am using a TreeView inside a ScrolledWindow, and try to find the position of an item inside the TreeStore to draw lines starting from that item in Cairo. For now, I am using a hard coded value for cell height, which works for few items, but when other font sizes are used or the item list gets longer, there is a visible offset. Is there a method to get the cell height of a TreeView, or another way to get the vertical position of an item in the TreeStore?
Here is the method, that I currently use:
int GetYPositionForPort (TreeView tree, TreeStore store, Port selectedPort)
{
// Is there a way to get the cell height?
int cellHeight = 24;
// We start in the middle of the first Treeview item
int position = cellHeight / 2;
ScrolledWindow treeParent = tree.Parent as ScrolledWindow;
if (treeParent != null) {
position -= Convert.ToInt32 (treeParent.Vadjustment.Value);
}
TreeIter clientIter;
TreeIter portIter;
if (store.GetIterFirst (out clientIter)) {
do {
if (store.IterHasChild (clientIter)
&& tree.GetRowExpanded (store.GetPath (clientIter))) {
if (store.IterChildren (out portIter, clientIter)) {
do {
position += cellHeight;
} while (((Port)store.GetValue(portIter, 0) != selectedPort
|| (Client)store.GetValue(clientIter, 0) != selectedPort.Client)
&& store.IterNext(ref portIter));
}
}
//Necessary because the first Treeview item only counts as 1/2 cell height.
if (((Client)store.GetValue (clientIter, 0)) == selectedPort.Client) {
break;
}
position += cellHeight;
} while (store.IterNext(ref clientIter));
}
return position;
}
You can see the error on the bottom of the screenshot where the lines are not aligned with the items in the TreeView:
Reading the documentation, I have come up with the following ugly piece of code:
int GetCellHeight (TreeView tree)
{
if (_cellHeight > 0) {
return _cellHeight;
}
int offsetX;
int offsetY;
int cellWidth;
Gdk.Rectangle rectangle = new Gdk.Rectangle ();
TreeViewColumn column = tree.GetColumn (0);
// Getting dimensions from TreeViewColumn
column.CellGetSize(rectangle, out offsetX, out offsetY,
out cellWidth, out _cellHeight);
// And now get padding from CellRenderer
CellRenderer renderer = column.CellRenderers[0];
_cellHeight += (int)renderer.Ypad;
return _cellHeight;
}
This is what it looks like now:
This is a continuation of an earlier thread, that was answered by Neil- thanks Neil! I probably should have included this in the first question, but I wanted to simplify things...
Another feature that I need is to have a "dialogue box" that holds a title and some text that animates on and off next to the circle when it comes on. I achieved this in my earlier version prior to the help from Neil. I have spent some time trying to integrate it into the new and improved code and get some unexpected results. For example, if I rollover the first circle on the right, it works as it should, however, if I try to rollover the middle and right circles, they don't work. Oddly, if I refresh and start on the right circle, each will work when moving right to left, until I reach the left one, and then the middle and right don't work- but the left one continues to work. Additionally, if I click on the left circle, it works as it should, but then the others don't work. And conversely, if I click on the right one first, and then move to the middle, the middle works on click, but then the right one does not.
The behavior that I am looking for is that each circle, animate up with the rectangle next to the circle fading in with dynamic text on mouseover and animate down, with the rectangle with text fading out on mouseout. The rectangle with text should fade out on click and not fade up again if the user mousesover the clicked circle (need to remove the mouseover event as well I guess). One additional thing that needs to happen is that the rectangle needs to appear in a different place on the circle, depending where on the map it is- so that it doesn't fall off the map. I did this successfully in the earlier version, but have left that out on the previous post for better clarity. I will include it here so you get the gist of what I'm doing.
My guess is that I need to create a set() of the rectangle/text component and place it in an another set() along with the circle?
Any help on this is truly appreciated! Thanks
// JavaScript Document
var arr = [
[50, 50, "this", "Is text that is to be the abstract"],
[100, 50, "that", "Is text that I hope is here"],
[150, 50, "another thing", "Even more text"]
];
var currRect;
var currTitleTxt;
var currTeaseTxt;
var prevItem;
doMe();
function doMe() {
var paper = new Raphael(document.getElementById('canvas_container'), 696, 348);
for (var i = 0; i < arr.length; i++) {
paper.circle(arr[i][0], arr[i][1], 6).attr({
fill: '#fff',
'fill-opacity': 0.5
}).data("i", [arr[i][0], arr[i][1], arr[i][2], arr[i][3]]).click(function () {
this.unmouseout();
}).click(function () {
if (this.data('selected') != 'true') {
if (prevItem != null) {
prevItem.data('selected', '');
handleOutState(prevItem);
}
prevItem = this.data('selected', 'true');
currRect.animate({"fill-opacity":0, "stroke-opacity":0}, 150 );
currTitleTxt.animate({"fill-opacity":0}, 150 );
currTeaseTxt.animate({"fill-opacity":0}, 150 );
}
}).mouseover(function () {
handleOverState(this);
if(this.data("i")[0] <= 350){ //this is where I test for the position on the map
paper.setStart(); //create rectangle and text set
currRect =paper.rect(17, -20, 265,90).attr({fill:"#999","fill-opacity":0.5});
currTitleTxt = paper.text(25, -8, this.data("i")[2]).attr({"text-anchor":"start",fill:'#ffffff',"font-size": 14, "font-weight":"bold","fill-opacity":0});
currTeaseTxt = paper.text(25, 30).attr({"text-anchor":"start",fill:'#eeeeee',"font-size": 11, "font-weight":"bold","fill-opacity":0});
var maxWidth = 250;
var content = this.data("i")[3];
var words = content.split(" ");
var tempText = ""; //since Raphael doesn't have native word wrap, I break the line manually
for (var i=0; i<words.length; i++) {
currTeaseTxt.attr("text", tempText + " " + words[i]);
if (currTeaseTxt.getBBox().width > maxWidth) {
tempText += "\n" + words[i];
} else {
tempText += " " + words[i];
}
}
currTeaseTxt.attr("text", tempText.substring(1));
var st = paper.setFinish();
st.translate(this.data("i")[0]+10, this.data("i")[1]+0).animate({"fill-opacity":1}, 150 );
}else if(this.data("i")[0] >= 351){ //this is where I test for the position on the map
paper.setStart();
currRect = paper.rect(-280, -20, 250,50).attr({fill:"#999","fill-opacity":0.5});
currTitleTxt = paper.text(-270, -10, this.data("i")[2]).attr({"text-anchor":"start",fill:'#ffffff',"font-size": 14, "font-weight":"bold","fill-opacity":0});
currTeaseTxt =paper.text(-270, 5, this.data("i")[3]).attr({"text-anchor":"start",fill:'#cccccc',"font-size": 12, "font-weight":"bold","fill-opacity":0});
var st = paper.setFinish();
st.translate(this.data("i")[0]+10, this.data("i")[1]+0).animate({"fill-opacity":1}, 150 );
}
}).mouseout(function () {
currRect.animate({"fill-opacity":0, "stroke-opacity":0}, 150 );
currTitleTxt.animate({"fill-opacity":0}, 150 );
currTeaseTxt.animate({"fill-opacity":0}, 150 );
if (this.data('selected') != 'true') handleOutState(this);
});
}
function handleOverState(el) {
el.animate({
r: 8
}, 250).animate({
"fill-opacity": 1
}, 150);
}
function handleOutState(el) {
el.animate({
r: 6
}, 250).animate({
"fill-opacity": 0.5
}, 150);
}
}
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);
}
}