Get plots from CombinedDomainXYPlot (and remove them) - java

Is there a way to get plot list added to CombinedDomainXYPlot if I don't keep any references to them?
I'd like to get there plots, work with them and possibly remove them from the compined plot.
This is example code for adding plots to CombinedDomainXYPlot:
// axis
DateAxis domainAxis = new DateAxis("Date");
// plot container
CombinedDomainXYPlot plotCombined = new CombinedDomainXYPlot(domainAxis);
// plot 1
XYPlot plot1 = new XYPlot();
plot1.setDomainAxis(domainAxis);
plotCombined.add(plot1);
// plot 2
XYPlot plot2 = new XYPlot();
plot2.setDomainAxis(domainAxis);
plotCombined.add(plot2);
Update 1:
I've just tried this code but it doesn't return all the plots. It's not reliable.
for (Object sp : plotCombined.getSubplots()) {
plotCombined.remove((XYPlot)sp);
}
It this method of removing the plots correct?
Full example code:
import javax.swing.JFrame;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.XYPlot;
public class sample27 extends JFrame {
public sample27() {
super("sample27");
// axis
DateAxis domainAxis = new DateAxis("Date");
// plot container
CombinedDomainXYPlot plotCombined = new CombinedDomainXYPlot(domainAxis);
// plot 1
XYPlot plot1 = new XYPlot();
plot1.setDomainAxis(domainAxis);
plotCombined.add(plot1);
// plot 2
XYPlot plot2 = new XYPlot();
plot2.setDomainAxis(domainAxis);
plotCombined.add(plot2);
System.out.println("plot count before: " + plotCombined.getSubplots().size());
for (Object sp : plotCombined.getSubplots()) {
System.out.println("removing subplot: " + sp);
plotCombined.remove((XYPlot)sp);
}
System.out.println("plot count after: " + plotCombined.getSubplots().size());
}
public static void main(String[] args) {
new sample27();
}
}
Output:
plot count before: 2
removing subplot: org.jfree.chart.plot.XYPlot#15615099
plot count after: 1

getSubplots returns a List containing all the items - this List is copy from the standpoint that it uses Collections.unmodifiableList, which returns a new List backed by the original. As you iterate over the List, items are in fact being removed from the underlying List, affecting the iteration over the Collection.
Rather than rely on iteration (eg for (Object sp : plotCombined.getSubplots())), loop over the array backwards and use the index to remove the item.
for ( int i = plotCombined.getSubplots().size() - 1; i >= 0; i-- ){
plotCombined.remove((XYPlot)plotCombined.getSubplots().get(i));
}

As an alternative to the approach shown here, iterate over a modifiable list constructed from the unmodifiable list returned by getSubplots().
Code:
List<XYPlot> list = new ArrayList<>(plotCombined.getSubplots());
for (XYPlot plot : list) {
plotCombined.remove(plot);
}
Console:
plot count before: 2
plot count after: 0

Related

Does JavaFX seem to be losing my event handler?

I'm a complete novice to JavaFX, and fairly new to Java overall. I'm designing a graphical representation of an undirected graph for use in a self-teaching project. Right now, I'm trying to make nodes draggable such that the edges will stretch to stay connected with their nodes. I have achieved that in the case of 2 nodes with a connection. However, adding a third does something weird.
Say we have this situation:
Cell testOne = new Cell ("testOne", 123);
Cell testTwo = new Cell ("testTwo", 456);
Cell testThree = new Cell ("testThree", 200);
testOne.addConnection(testTwo);
testOne.addConnection(testThree);
What I get is three nodes with two lines strewn randomly in their general area (worth noting the nodes are positioned in a crudely random way). If I move around testTwo or testThree, a single line will trade off being connected to testOne. The second line remains unchanged no matter what. I have to think that somehow what's happening is that one of the EventHandlers is getting "unplugged" from their respective cells, or else somehow one of the lines is getting lost in memory. Here's the code to draw lines (I know it's really clunky). This method is in the Graph class, which controls graphic (oop) representation of the class. "cells" is the ArrayList storing all its nodes, "connections" is the arrayList in the Cell instance that keeps track of all the nodes it's connected to, and "LinesBetween" is a HashMap the Cell instance keeping track of whether a line has already been drawn between the two nodes.
public void drawAndManageEdgeLines(){
if (cells.size() > 1) { //don't wanna make connections if there's only one cell, or none
int count = 0;
for (Cell cell : cells) { // for every cell on the graph
List<Cell> connectionsList = cell.getConnections(); // look at that cell's connections
if (!connectionsList.isEmpty()) {// validate that the cell is actually supposed to be connected to something
for (Cell connection : connectionsList) { // go through all their connections
if (!cell.getLinesBetween().get(connection) && cell.getLinesBetween().get(connection) != null) { //check to see whether there is already a line between them
Circle sourceCircle = cell.getCellView();
Circle targetCircle = connection.getCellView();
Bounds sourceBound = sourceCircle.localToScene(sourceCircle.getBoundsInLocal());
Bounds targetBound = targetCircle.localToScene(targetCircle.getBoundsInLocal());
double targetX = targetBound.getCenterX();
double targetY = targetBound.getCenterY();
double sourceX = sourceBound.getCenterX();
double sourceY = sourceBound.getCenterY();
edge = new Line(sourceX, sourceY, targetX, targetY);
edge.setStroke(Color.BLACK);
edge.setStrokeWidth(2);
getChildren().add(edge);
edge.toBack();
cell.setLinesBetweenEntry(connection, true);
connection.setLinesBetweenEntry(cell, true);
// these handlers control where the line is dragged to
cell.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
edge.setStartX(e.getSceneX()); //this is a really cool method
edge.setStartY(e.getSceneY());
e.consume();
}
});
System.out.println("on round " + count + " we got there: ");
connection.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent e) {
edge.setEndX(e.getSceneX());
edge.setEndY(e.getSceneY());
e.consume();
}
});
}
}
}
}
}
}
It's hard to tell what's going wrong without a proper minimal reproducible example, but you seem to be making this more complicated than it needs to be. If you want the edges to be "linked" to the nodes then I recommend you use bindings. Here's a proof-of-concept:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
Circle node1 = new Circle(100, 100, 50, Color.FIREBRICK);
Circle node2 = new Circle(900, 550, 50, Color.DARKBLUE);
Circle node3 = new Circle(900, 100, 50, Color.DARKGREEN);
addDragHandling(node1);
addDragHandling(node2);
addDragHandling(node3);
Line edge1 = createEdge(node1, node2);
Line edge2 = createEdge(node1, node3);
Pane root = new Pane(edge1, edge2, node1, node2, node3);
primaryStage.setScene(new Scene(root, 1000, 650));
primaryStage.show();
}
private Line createEdge(Circle from, Circle to) {
Line edge = new Line();
edge.setStrokeWidth(2);
edge.startXProperty().bind(from.centerXProperty());
edge.startYProperty().bind(from.centerYProperty());
edge.endXProperty().bind(to.centerXProperty());
edge.endYProperty().bind(to.centerYProperty());
return edge;
}
private void addDragHandling(Circle circle) {
// changing cursors not necessary, only included to help indicate
// when something can be dragged and is being dragged
circle
.cursorProperty()
.bind(
Bindings.when(circle.pressedProperty())
.then(Cursor.CLOSED_HAND)
.otherwise(Cursor.OPEN_HAND));
double[] offset = {0, 0}; // (x, y)
circle.setOnMousePressed(
e -> {
offset[0] = e.getX() - circle.getCenterX();
offset[1] = e.getY() - circle.getCenterY();
e.consume();
});
circle.setOnMouseDragged(
e -> {
circle.setCenterX(e.getX() - offset[0]);
circle.setCenterY(e.getY() - offset[1]);
e.consume();
});
}
}
Note I added the edges to the Pane first so that they were drawn under the nodes. See Z-Order in JavaFX. Also, your drag logic may look different depending on what Node you use to represent your graph nodes.
Since you are representing a graph your application will be more complex. If the graph is dynamic and you want the view to update in real time then you'll need to keep references to the nodes and their associated edges to add and remove them at will. But remember the view is only a visual representation of the model. Don't use the view to store model information (e.g. what nodes and edges actually exist).

MPChart xAxis values not aligned

I am trying to implement grouped MPAndroid Bar chart. I have a group of 2 datasets that i want to display. The problem is that the xaxis values are not center aligned with the bar chart (as per the screenshot). I checked other questions as well and implemented the following answers provided.
I want to make the labels center aligned with the grouped bars.
float barSpace = 0.02f;
float groupSpace = 0.3f;
int groupCount = 2;
data.setBarWidth(0.155f);
pvaAmount_chart.getXAxis().setAxisMinimum(0);
pvaAmount_chart.getXAxis().setAxisMaximum(0 + pvaAmount_chart.getBarData().getGroupWidth(groupSpace, barSpace) * groupCount);
pvaAmount_chart.groupBars(0, groupSpace, barSpace);
pvaAmount_chart.getXAxis().setCenterAxisLabels(true);
pvaAmount_chart.notifyDataSetChanged();
When entering groupcount=2 as 2 types of bars:
When entering groupcount=4 number of grouped charts:
i'm doing it like this :
String [] values = new String[insert the lenght of your array]
for (int blabla=0;i<values[lenght];blabla++){
String dayOfTheWeek = dateFormat.format(mydate);
vales[blabla] = dayOfTheWeek //in my case
}
xAxis.setGranularity(1.0f); //
xAxis.setCenterAxisLabels(false);
xAxis.setGranularityEnabled(true);
xAxis.setLabelCount(values.length,false); //
List<String> stringList = new ArrayList<String>(Arrays.asList(values));
xAxis.setGranularityEnabled(true);
xAxis.setLabelCount(values.length,false); //
xAxis.setValueFormatter(new IndexAxisValueFormatter(getXAxisValues((ArrayList<String>) stringList)));
i'm using : 'com.github.PhilJay:MPAndroidChart:v3.1.0'

Display Prefuse Node field

I want to display a simple Graph with nodes IDs inside Nodes using Prefuse but this seems to be more complicated than it sounds.
Graph g = new Graph();
for (int i = 0; i < 3; ++i) {
Node n1 = g.addNode();
n1.setInt("label", 1); // I am trying to add a field in a node
Node n2 = g.addNode();
Node n3 = g.addNode();
g.addEdge(n1, n2);
g.addEdge(n1, n3);
g.addEdge(n2, n3);
}
g.addEdge(0, 3);
g.addEdge(3, 6);
g.addEdge(6, 0);
// add visual data groups
VisualGraph vg = m_vis.addGraph(GRAPH, g);
m_vis.setInteractive(EDGES, null, false);
m_vis.setValue(NODES, null, VisualItem.SHAPE, new Integer(Constants.SHAPE_STAR));
However, it seems that this field doesn't exist, it makes sense since I didn't add this field but there isn't an option to add a field neither. I am getting this exception referring to the n1.setInt("DEFAULT_NODE_KEY", 1) line:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.ArrayList.elementData(Unknown Source)
at java.util.ArrayList.get(Unknown Source)
at prefuse.data.Table.getColumn(Table.java:457)
at prefuse.data.Table.setInt(Table.java:1032)
at prefuse.data.tuple.TableTuple.setInt(TableTuple.java:215)
at prefuse.demos.AggregateDemo.initDataGroups(AggregateDemo.java:141)
at prefuse.demos.AggregateDemo.<init>(AggregateDemo.java:72)
at prefuse.demos.AggregateDemo.demo(AggregateDemo.java:182)
at prefuse.demos.AggregateDemo.main(AggregateDemo.java:176)
I am not sure how to use fields in Nodes. I tried to read the library's help but I don't manage to figure that out.
You may be looking for a LabelRenderer. In the example below, a LabelRenderer is constructed in such a way as to render nodes having the label GraphLib.LABEL, defined as "label":
LabelRenderer r = new LabelRenderer(GraphLib.LABEL);
The nodes comprising the Graph are given text via setString() using the same key, GraphLib.LABEL. For example, the root Node added to the Graph returned by GraphLib.getDiamondTree() is assigned the key GraphLib.LABEL and the value "0,0".
Node r = t.addRoot();
r.setString(LABEL, "0,0");
Later, when the Visualization runs, the renderer attempts to use text from the GraphLib.LABEL field.
import java.awt.Dimension;
import javax.swing.JFrame;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.activity.Activity;
import prefuse.controls.DragControl;
import prefuse.controls.PanControl;
import prefuse.controls.ZoomControl;
import prefuse.data.Graph;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.GraphLib;
import prefuse.visual.VisualItem;
/** #see https://stackoverflow.com/a/44274886/230513 */
public class Example {
private static final int W = 640;
private static final int H = 480;
public static void main(String[] argv) {
// -- 1. create the data ------------------------------------------------
Graph graph = GraphLib.getDiamondTree(3, 2, 1);
// -- 2. the visualization --------------------------------------------
// add the graph to the visualization as the data group "graph"
// nodes and edges are accessible as "graph.nodes" and "graph.edges"
Visualization vis = new Visualization();
vis.add("graph", graph);
vis.setInteractive("graph.edges", null, false);
// -- 3. the renderers and renderer factory ---------------------------
LabelRenderer r = new LabelRenderer(GraphLib.LABEL);
r.setRoundedCorner(8, 8); // round the corners
// create a new default renderer factory
// return our name label renderer as the default for all non-EdgeItems
// includes straight line edges for EdgeItems by default
vis.setRendererFactory(new DefaultRendererFactory(r));
// -- 4. the processing actions ---------------------------------------
ColorAction fill = new ColorAction("graph.nodes",
VisualItem.FILLCOLOR, ColorLib.rgb(200, 200, 255));
// use black for node text
ColorAction text = new ColorAction("graph.nodes",
VisualItem.TEXTCOLOR, ColorLib.gray(0));
// use light grey for edges
ColorAction edges = new ColorAction("graph.edges",
VisualItem.STROKECOLOR, ColorLib.gray(200));
// create an action list containing all color assignments
ActionList color = new ActionList();
color.add(fill);
color.add(text);
color.add(edges);
// create an action list with an animated layout
ActionList layout = new ActionList(Activity.INFINITY);
layout.add(new ForceDirectedLayout("graph"));
layout.add(new RepaintAction());
// add the actions to the visualization
vis.putAction("color", color);
vis.putAction("layout", layout);
// -- 5. the display and interactive controls -------------------------
Display d = new Display(vis) {
#Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
};
d.setSize(W, H); // set display size
d.pan(W / 2, H / 2); // pan to center
d.addControlListener(new DragControl());
d.addControlListener(new PanControl());
d.addControlListener(new ZoomControl());
// -- 6. launch the visualization -------------------------------------
JFrame frame = new JFrame("prefuse label example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(d);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true); // show the window
vis.run("color");
vis.run("layout");
}
}

Graphing scientific data in LineChart from MPAndroidChart

I am using MPAndroidChart library to draw graphs (especially LineCharts) in my App.
To draw a LineChart with the mentioned library first we need to create entries and labels as follow:
// Getting LineChart
LineChart lineChart = (LineChart) rootView.findViewById(R.id.chart);
// Creating list of entry
ArrayList<Entry> entries = new ArrayList<>();
// Creating labels
ArrayList<String> labels = new ArrayList<String>();
// Fill entries and lables
entries.add(new Entry(326.422f, 0));
entries.add(new Entry(8.36f, 1));
entries.add(new Entry(6.5f, 2));
entries.add(new Entry(2.37f, 3));
entries.add(new Entry(18.13f, 4));
entries.add(new Entry(9f, 5));
labels.add("0");
labels.add("1");
labels.add("2");
labels.add("3");
labels.add("4");
labels.add("5");
// Create dataset
final LineDataSet dataset = new LineDataSet(entries, "Legend description");
// Create LineData with labels and dataset prepared previously
LineData data = new LineData(labels, dataset);
// Set the data and list of labels into chart
lineChart.setData(data);
Ok this is working, but the point is what if I want to graph a set of coordinates like this: X = {(35.3, 22.9), (69.39, 27.36), (66.37, 31.697), (58.36, 36.32), (45.336, 38.296), (25.39, 40), (67.396, 43.633)}.
The constructor of Entry accepts a float number as first parameter and an integer as second parameter, so how can I give the above X set to the LineChart?
Someone could say that I can set the labels accordingly, for example the first label could be labeled as "22.9", the second as "27.36", and so on... But this is mathematically wrong as the graph is not scaled properly.
In the documentation I found classes like Entry, BarEntry, BubbleEntry, CandleEntry but there is nothing something like LineEntry.
Can anyone point me to the right direction on how to achieve this goal?
Thank you,
HSB
Currently only integers are supported for the x-axis. The reason therefore is that each string on the x-axis should correspond to a value on the y-axis.
This will change in the next release of the library, where both values will be changed to double.
The new version should be released in April.

JFreeChart : obtain data source value on mouse click

I have a JFreeChart instance that displays process memory status, initialized as follows:
m_data = new TimeSeriesCollection();
TimeSeries vmsize = new TimeSeries("VMSize");
TimeSeries resident = new TimeSeries("Resisdent");
TimeSeries shared = new TimeSeries("Shared memory");
TimeSeries code = new TimeSeries("Code");
TimeSeries data = new TimeSeries("Data");
m_data.addSeries(vmsize);
m_data.addSeries(resident);
m_data.addSeries(shared);
m_data.addSeries(code);
m_data.addSeries(data);
JFreeChart chart = ChartFactory.createTimeSeriesChart("Memory usage", "Time", "Size", m_data, true, true, false);
m_chart = new ChartPanel(chart);
Later I add values to each TimeSeries in the TimeSeriesCollection. I would like to somehow know - when the user clicks on the Chart - either what time associated with that columm, or even better - what is the index of the value.
I looked at the JFreeChart and ChartMouseListener classes, but I could not figure out how to do that (also the documentation of JFreeChart is annoyingly scarce, I guess they are trying to get people to buy their developer's guide).
if you click dead on the item, the event.getEntity() function returns XYItem and then from there onwards
XYItemEntity xyitem=(XYItemEntity) event.getEntity(); // get clicked entity
XYDataset dataset = (XYDataset)xyitem.getDataset(); // get data set
System.out.println(xyitem.getItem()+" item of "+xyitem.getSeriesIndex()+"series");
System.out.println(dataset.getXValue(xyitem.getSeriesIndex(), xyitem.getItem()));
System.out.println(dataset.getYValue(xyitem.getSeriesIndex(), xyitem.getItem()));
Comparable comparable=dataset.getSeriesKey(0);
XYPlot xyplot = (XYPlot) event.getChart().getPlot();
System.out.println(xyplot.getRangeCrosshairValue());
however incase you do not click on the item itself but your crosshair is set to auto lock on data, in such case the crosshair will move to nearest item but since the item has not been clicked, you will not be able to get the XYItem and hence you cannot know the series and item index, to solve this problem there is this code below, it should be put in the catch clause while the above mentioned code should be in try clause
first define a function which will take crosshair value at domain and range and also Xydataset, this functions returns an inner class object that groups item index and series index
public static SeriesAndItemIndex getItemIndex(double domainVal,double rangeVal,XYDataset xydataset){
Comparable comparable;
int indexOf;
for(int i=0;i<xydataset.getSeriesCount();i++){
comparable = xydataset.getSeriesKey(i);
indexOf=xydataset.indexOf(comparable);
for(int j=0 ; j<xydataset.getItemCount(indexOf);j++){
double x=xydataset.getXValue(indexOf, j);
double y=xydataset.getYValue(indexOf, j);
if(x == domainVal && y==rangeVal){
return new SeriesAndItemIndex(j,indexOf);//return item index and series index
}
}
}
return null;
}
private static class SeriesAndItemIndex{ ///inner CLASS to group series and item clicked index
public int itemIndex;
public int seriesIndex;
public SeriesAndItemIndex(int i,int s){
itemIndex=i;
seriesIndex=s;
}
#Override
public String toString(){
return "itemIndex="+itemIndex+",seriesIndex="+seriesIndex;
}
}
how to use it?
try{......code block from the top
}catch(Exception e){
Object source=event.getSource();
JFreeChart chartpanel=(JFreeChart)source;
XYPlot xyplot = (XYPlot) chartpanel.getPlot();
XYDataset xydataset= xyplot.getDataset();
double d=xyplot.getDomainCrosshairValue(); //get crosshair X value
double r =xyplot.getRangeCrosshairValue(); //get crosshair y value
SeriesAndItemIndex index=getItemIndex(d,r,xydataset);
if(index != null){
System.out.println(index.toString());
}
}
hmm should work, if you replace the last two lines by something like this:
ChartPanel panel=new ChartPanel(ChartFactory.createTimeSeriesChart("Memory usage", "Time", "Size", m_data, true, true, false)));
panel.addChartMouseListener(new ChartMouseListener(){
void chartMouseClicked(ChartMouseEvent e){
[...do something on click...]
}
void chartMouseMoved(ChartMouseEvent e){
[...do something on move...]
}
});
return panel;

Categories

Resources