Related to: How can I share DomainAxis/RangeAxis across subplots without drawing them on each plot?
Note - I originally wrote this up as a question, and then realize my mistake, so I decided to just share the knowledge since I already had this all typed out.
In JFreeChart, there are two plot types that share an axis range and draw only one axis, while sharing that information with their subplots. These are CombinedDomainXYPlot and CombinedRangeXYPlot. I will focus on CombinedDomainXyPlot, but the other should be identical for the purposes of this question. Looking at the draw code we see:
...
setFixedRangeAxisSpaceForSubplots(null);
AxisSpace space = calculateAxisSpace(g2, area);
Rectangle2D dataArea = space.shrink(area, null);
// set the width and height of non-shared axis of all sub-plots
setFixedRangeAxisSpaceForSubplots(space);
// draw the shared axis
ValueAxis axis = getDomainAxis();
RectangleEdge edge = getDomainAxisEdge();
double cursor = RectangleEdge.coordinate(dataArea, edge);
AxisState axisState = axis.draw(g2, cursor, area, dataArea, edge, info); // <- draw the share axis
if (parentState == null) {
parentState = new PlotState();
}
parentState.getSharedAxisStates().put(axis, axisState); // <- put the state of the shared axis in a shared object
// draw all the subplots
for (int i = 0; i < this.subplots.size(); i++) {
XYPlot plot = (XYPlot) this.subplots.get(i);
PlotRenderingInfo subplotInfo = null;
if (info != null) {
subplotInfo = new PlotRenderingInfo(info.getOwner());
info.addSubplotInfo(subplotInfo);
}
plot.draw(g2, this.subplotAreas[i], anchor, parentState, subplotInfo); // <- pass this shared object to the subplot draw function.
}
...
in the subplot (XYPlot) draw method, we see:
domainAxisState = (AxisState) parentState.getSharedAxisStates().get(getDomainAxis());
The getDomainAxis() method is calls getDomainAxis(0), which is:
public ValueAxis getDomainAxis(int index) {
ValueAxis result = null;
if (index < this.domainAxes.size()) {
result = (ValueAxis) this.domainAxes.get(index); //<- try to find this domain axis in self
}
if (result == null) { //<- if not found in self ...
Plot parent = getParent();
if (parent instanceof XYPlot) {
XYPlot xy = (XYPlot) parent;
result = xy.getDomainAxis(index); //<- ...try to get from parent
}
}
return result;
}
Here, parent is the CombinedDomainXYPlot. This will return a reference to the domainAxis from CombinedDomainXYPlot, to be used for retrieving the axisState from the parentState object. It seems the getDomainAxis() method is not used for getting an axis to draw, only for getting a reference to it for other purposes.
I was looking into this because I was trying to use the same technique to pass multiple shared axes to multiple different groups of plots, but only draw them once. This technique seems to work, but there are glitches right now in that zooming the plots that only get state information does not update the main plot that has the axis... Working on it, but hopefully this information is useful to someone down the line.
Thanks,
Igor
Answered in question post.
As state above, the getDomainAxis() method in XYPlot is apparently not used for getting an axis to draw, only for getting a reference to it for other purposes. So if you override this method to link to another axis and put it's state information into a shared object, this state info will be shared between plots.
Related
I have a number of TextButtons I can drag, checking if they overlap an Image when I let go. Currently what I'm experiencing is either a particular object will detect collision located anywhere on screen, or it will never collide. Note that I'm not using the native DragAndDrop class, but have adapted a parallel implementation from a book.
Given that my TextButtons move when I drag them, I think the following function is updating the (x,y) of the object:
public void touchDragged(InputEvent event, float eventOffsetX, float eventOffsetY, int pointer)
{
float deltaX = eventOffsetX - grabOffsetX;
float deltaY = eventOffsetY - grabOffsetY;
a.moveBy(deltaX, deltaY);
}
Is that correct that the Actor a's x, y change due to moveBy? Because my latter collision detection - where I examine the dragged objects coordinates - reports the same x,y coordinates for the dragged object no matter where I release it. Here's the log for releasing the object from two different locations on the screen:
Does (626.8995, 393.1301)(923.8995, 393.1301)(923.8995, 499.1301)(626.8995, 499.1301) fit into (610.0, 256.0)(990.0, 256.0)(990.0, 677.0)(610.0, 677.0)?
Does (626.8995, 393.1301)(923.8995, 393.1301)(923.8995, 499.1301)(626.8995, 499.1301) fit into (610.0, 256.0)(990.0, 256.0)(990.0, 677.0)(610.0, 677.0)?
and here's the collision detection and sys out generating those log messages:
//this is called by the dragged obj, a, on touchUp() against each of the targets
public boolean overlaps(Actor other)
{
//a is the first, dragged object, other is the target
if (poly1 == null)
poly1 = getPolygon(a);
Polygon poly2 = getPolygon(other);
float[] p1v = poly1.getVertices();
StringBuilder sb = new StringBuilder();
for (int i=0; i<p1v.length-1; i+=2)
sb.append("(").append(p1v[i]).append(", ").append(p1v[i+1]).append(")");
float[] p2v = poly2.getVertices();
StringBuilder sb2 = new StringBuilder();
for (int i=0; i<p2v.length-1; i+=2)
sb2.append("(").append(p2v[i]).append(", ").append(p2v[i+1]).append(")");
System.out.println("Does " + sb + " fit into " + sb2 + "?");
// initial test to improve performance
if ( !poly1.getBoundingRectangle().overlaps(poly2.getBoundingRectangle()) )
return false;
return Intersector.overlapConvexPolygons( poly1, poly2 );
}
public Polygon getPolygon(Actor a) {
Polygon p = new Polygon();
// float[] vertex = { a.getOriginX(), a.getOriginY(), a.getOriginX(), a.getY(), a.getX(), a.getY(), a.getX(), a.getOriginY() };//new float[8];
// float[] vertex = { 0, 0, a.getWidth(), 0, a.getWidth(), a.getHeight(), 0, a.getHeight()};
float[] vertex = { a.getX(), a.getY(), (a.getX() + a.getWidth()), a.getY(), (a.getX() + a.getWidth()), (a.getY() + a.getHeight()), a.getX(), (a.getY() + a.getHeight())};
p.setVertices(vertex);
// p.setPosition(a.getX(), a.getY());
//p.setOrigin(a.getOriginX(), a.getOriginY());
return p;
}
There are a HORDE of collision detection posts already on StackOverflow, and they helped some in showing me how to form valid polygons. Perhaps the 3rd party drag and drop is why I'm not finding my answer in the wealth of knowledge out there, but I'm leaning towards some annoying mistake I'm overlooking.
Score another one for logic failure. Originally I thought I'd just be saving the dimensions of the dragged polygon when I decided to cache it, thinking it would save a few polygon creation steps as it was checked against a number of potential targets. Later, as I kept reworking what values to feed the polygon vertices, I tied in the location of the polygon as well. So it was just caching the first place I dragged it to, and using that every time I dragged it somewhere.
Thanks for the comment, it helped me move past thinking I wasn't understanding the classes. I'm doubtful this particular mistake/resolution will ever be of use to someone else, and would be very understanding if this post is removed.
I got 2 anchors, I'd like to render an arrow object at one anchor and the direction of the arrow head to the other. I put some code, but it didn't work properly
private void drawLine(AnchorNode node1, AnchorNode node2) {
Vector3 point1, point2;
point1 = node1.getWorldPosition();
point2 = node2.getWorldPosition();
node1.setParent(mArFragment.getArSceneView().getScene());
//find the vector extending between the two points and define a look rotation
//in terms of this Vector.
final Vector3 difference = Vector3.subtract(point1, point2);
final Vector3 directionFromTopToBottom = difference.normalized();
final Quaternion rotationFromAToB =
Quaternion.lookRotation(directionFromTopToBottom, Vector3.up());
MaterialFactory.makeTransparentWithColor(getApplicationContext(), new Color(247, 181, 0, 0.7f))
.thenAccept(
material -> {
// create a rectangular prism, using ShapeFactory.makeCube()
// use the difference vector to extend to the necessary length
ModelRenderable model = ShapeFactory.makeCube(
new Vector3(.15f, .001f, difference.length()),
Vector3.zero(), material);
// set the world rotation of the node to the rotation calculated earlier
// and set the world position to the midpoint between the given points
Node nodeForLine = new Node();
nodeForLine.setParent(node1);
nodeForLine.setRenderable(model);
nodeForLine.setWorldPosition(Vector3.add(point1, point2).scaled(.5f));
nodeForLine.setWorldRotation(rotationFromAToB);
}
); // end rendering
ModelRenderable.builder()
.setSource(this, Uri.parse("model.sfb"))
.build()
.thenAccept(modelRenderable -> {
AnchorNode anchorNode = new AnchorNode(node1.getAnchor());
TransformableNode transformableNode = new TransformableNode(mArFragment.getTransformationSystem());
transformableNode.setParent(anchorNode);
transformableNode.setRenderable(modelRenderable);
transformableNode.setWorldRotation(rotationFromAToB);
mArFragment.getArSceneView().getScene().addChild(anchorNode);
transformableNode.select();
})
.exceptionally(throwable -> {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(throwable.getMessage()).show();
return null;
});
}
You see the arrow is rendered but its direction is not correct.
Current situation. See image
I think you want the arrow on one anchor to point towards the other anchor - you can do this using 'setLookDirection' and a TransformableNode.
See below for an example:
var newAnchorNode:AnchorNode = AnchorNode(newAnchor)
var transNode = TransformableNode(arFragment.transformationSystem)
transNode.setLookDirection(Vector3(0f, 1f, 1f), Vector3.left())
transNode.renderable = selectedRenderable
transNode.setParent(newAnchorNode)
newAnchorNode.setParent(arFragment.arSceneView.scene)
See API documentation here:
https://developers.google.com/sceneform/reference/com/google/ar/sceneform/ux/TransformableNode
This is actually for Scenefrom 1.15 and the newer version 1.16 is now open source on GitHub, but I don't think a detailed API description exists so the above is still a good place to look at this time (May 2020).
You can use your own values for the two vector3's.
The first Vector3 is the point you want your renderable to 'look at', in your case the other anchor most likely, and the second Vector3 is the orientation of the renderable in the scene - i.e. if you want it upright, facing left etc.
One thing to be aware of, AFAIK, Sceeform is still designed for landscape mode so you may need to experiment to get the orientation the way you want if it not using landscape - for example the vector3.left() in the example above is to make a renderable appear upright on a portrait display.
I am working with achartengine. I have to read a txt file and plot the graph. I get the graph plot. But, what I want to do is when the graph reaches the end of the layout view, it should get plotted from the beginning view as similar to oscilloscope view.
I want my graph exactly similar to the graph in this link
http://www.youtube.com/watch?v=N6BuRqeUhqc.
What I have done so far is:
private class ChartTask extends AsyncTask<Void, String, Void>{
String[] values = new String[2];int i1=0;
// Generates dummy data in a non-ui thread
#Override
protected Void doInBackground(Void... params) {
int i = 0;
try{
do{
values[0] = Integer.toString(i);
values[1] = Integer.toString(amplitude[i]);
if(i<=600){
multiRenderer.setXAxisMax(600);
}
else if(i>600){
double minX = amplitudeSeries.getMaxX();
multiRenderer.setXAxisMin(minX);
}
publishProgress(values);
Thread.sleep(1);
i++;
}while(i<=amplitude.length);}
catch (Exception e1){
}
return null;
}
Can someone help me with this. Thanks for anyone's help.
It should be quite easy to draw dynamic charts using AChartEngine. Just update the contents of your dataset and call chartView.repaint().
This is an animation task. Use Bitmap to hold the chart and use timer to plot periodically one X,Y pair per call into bitmap. After that, call invalidate() to repaint the bitmap on the screen of device.
The last thing you need is a trivial mod operation. Here are the possible code fragments of this periodically called code:
// Get position in bitmap from the iteration index
int ig = mIteration % bitmap.getWidth();
// Erase the vertical line in bitmap
for (int y = 0, y < bitmap.getHeight(); i++)
bitmap.setPixel(ig, y, Color.WHITE);
// Plot the point
bitmap.setPixel(ig, data[mIteration], Color.BLACK);
// Advance mIteration field value.
mIteration = (mIteration + 1) % data.length;
// Force to repaint component containing the bitmap.
invalidate();
this will care for you about returning the cursor back and repeating the animation after reaching the end of data.
I was able to implement real-time mouse tracing as follow :
The source code is as follow :
http://jstock.cvs.sourceforge.net/viewvc/jstock/jstock/src/org/yccheok/jstock/charting/CrossHairUI.java?revision=1.5&view=markup
http://jstock.cvs.sourceforge.net/viewvc/jstock/jstock/src/org/yccheok/jstock/gui/ChartJDialog.java?revision=1.9&view=markup
However, I unable to obtained the correct y screen coordinate, when an subplot being added.
(broken image)
I suspect I didn't get the correct screen data area.
When there is only one plot in the screen, I get a screen data area with height 300++
When a sub plot added to the bottom, I expect screen data area height will be reduced, due to the height occupied by the newly added subplot.
However, I have no idea how to obtain the correct screen data area for the first plot.
final XYPlot plot = (XYPlot) cplot.getSubplots().get(0);
// Shall I get _plotArea represents screen for getSubplots().get(0)?
// How?
// I try
//
// chartPanel.getScreenDataArea(0, 0);
// chartPanel.getScreenDataArea(0, 1);
// chartPanel.getScreenDataArea(1, 0);
// chartPanel.getScreenDataArea(1, 1);
//
// All returned null
// OK. I suspect this is causing me unable to get the correct screen y coordinate
// I always get the same _plotArea, although a new sub plot had been added.
final Rectangle2D _plotArea = chartPanel.getScreenDataArea();
final RectangleEdge rangeAxisEdge = plot.getRangeAxisEdge();
final double yJava2D = rangeAxis.valueToJava2D(yValue, _plotArea, rangeAxisEdge);
Here is the code snippet to obtained the correct rectangle after dive into JFreeChart source code.
/* Try to get correct main chart area. */
final Rectangle2D _plotArea = chartPanel.getChartRenderingInfo().getPlotInfo().getSubplotInfo(0).getDataArea();
We are using JFreeChart to make XY plots and we have a feature request to do a crosshair that moves along with the mouse and highlights the data point that most closely maps to the x-value of the mouse. You can see a similar example at Google Finance - http://www.google.com/finance?q=INDEXDJX:.DJI,INDEXSP:.INX,INDEXNASDAQ:.IXIC.
Those Google charts only highlight the current value (we want to do that and also show crosshairs), but they show the live mouse interaction we are looking for.
Anyone have any elegant suggestions?
Thanks.
I got this working using a mouse listener and the CrosshairOverlay class. After I get back from holiday travel, I will post my code. It ended up being not too difficult.
Sorry, I forgot about this!
First, you want to calculate the x, y values for where you want your crosshair. For me, I wanted it to move along the points of our line, so I calculated the closest x value and used that data pair for x, y.
Then I call this method:
protected void setCrosshairLocation(double x, Double y) {
Crosshair domainCrosshair;
List domainCrosshairs = crosshairOverlay.getDomainCrosshairs();
if (domainCrosshairs.isEmpty()) {
domainCrosshair = new Crosshair();
domainCrosshair.setPaint(BlueStripeColors.LIGHT_GRAY_C0);
crosshairOverlay.addDomainCrosshair(domainCrosshair);
}
else {
// We only have one at a time
domainCrosshair = (Crosshair) domainCrosshairs.get(0);
}
domainCrosshair.setValue(x);
if (y != null) {
Crosshair rangeCrosshair;
List rangeCrosshairs = crosshairOverlay.getRangeCrosshairs();
if (rangeCrosshairs.isEmpty()) {
rangeCrosshair = new Crosshair();
rangeCrosshair.setPaint(BlueStripeColors.LIGHT_GRAY_C0);
crosshairOverlay.addRangeCrosshair(rangeCrosshair);
}
else {
// We only have one at a time
rangeCrosshair = (Crosshair) rangeCrosshairs.get(0);
}
rangeCrosshair.setValue(y);
}
}
Note that crosshairOverlay is an instance of CrosshairOverlay.
JFreeChart can't render a sub-section of a chart, so you'll want to do something that doesn't require repainting the chart. You could write your chart to a BufferedImage and store that in memory, then have a custom component which uses the buffered chart as the background image, and draws crosshairs and other popup windows over it.
There are methods in JFreeChart to get the data point for a given coordinate on a rendered chart. Don't recall what these are off the top of my head. Depending on your needs, you might consider rendering your own chart data, it's not as hard as you'd think.
The first thing that comes to my mind would be to write a custom Cursor and set it on your chart. It can have a reference to the chart and highlight the x value that's consistent with the Cursor's x/y location.
This worked for me. I set the
chartPanel.addChartMouseListener(new ChartMouseListener() {
public void chartMouseMoved(ChartMouseEvent event)
{
try
{
double[] values = getCrossHairValue(event);
plot.clearRangeMarkers();
plot.clearDomainMarkers();
Marker yMarker = new ValueMarker(values[1]);
yMarker.setPaint(Color.darkGray);
plot.addRangeMarker(yMarker);
Marker xMarker = new ValueMarker(values[0]);
xMarker.setPaint(Color.darkGray);
plot.addDomainMarker(xMarker);
chartPanel.repaint();
} catch (Exception e)
{
}
}
}