I am using MPAndroidChart for displaying a line chart for my application. What I already have is a double tap listener for the whole chart.
Is there any way to obtain double tap listener control for an individual point in line chart? My best guess is to directly obtain the View object of the point and implement a gesture listener for the same. If that is plausible, how can I achieve that?
If this is not possible using MPAndroidChart, is there any other library that might help me with this?
Your solution would involve creating a class that implements OnChartGestureListener
You can immediately see there is a method:
void onChartDoubleTapped(android.view.MotionEvent me);
You would have to implement this method with the functionality you want. It would probably involve getting the raw pixel touch points from the MotionEvent and converting them to x and y values on the chart. There is instructions on how to do that in this answer and in the code below:
#Override
public void onChartDoubleTapped(MotionEvent me) {
float tappedX = me.getX();
float tappedY = me.getY();
MPPointD point = mChart.getTransformer(YAxis.AxisDependency.LEFT).getValuesByTouchPoint(tappedX, tappedY);
Log.d(TAG, "tapped at: " + point.x + "," + point.y);
}
Further examples of a custom OnChartGestureListener are inside the example project here
David Rawson's answer is just perfect and answers the question leaving no stones unturned. However, for my situation, I found the answer by firegloves more useful. I actually get the Entry object using firegloves' solution to obtain values from. Following is the code for what solved my problem but please do note that this solution is hacky and is not advised until absolutely necessary. If you want a clean solution, David Rawson's answer already covers that.
mChart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
#Override
public void onValueSelected(Entry e, Highlight h) {
final long selectionTime = System.currentTimeMillis();
final Highlight copyHighlight = h;
final Entry copyEntry = e;
final boolean[] isTapped = {false};
mChart.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (!isTapped[0]) {
final long doubleTapTime = System.currentTimeMillis();
if (doubleTapTime - selectionTime < 300) {
if ((motionEvent.getX() <= copyHighlight.getDrawX() + 50
&& motionEvent.getX() >= copyHighlight.getDrawX() - 50)
&& (motionEvent.getY() <= copyHighlight.getDrawY() + 50
&& motionEvent.getY() >= motionEvent.getY() - 50)) {
if (copyEntry.getX() == 1.0f && copyEntry.getY() == 1.0f) {
Toast.makeText(MainActivity.this, "1,1", Toast.LENGTH_SHORT).show();
} else if (copyEntry.getX() == 2.0f && copyEntry.getY() == 2.0f) {
Toast.makeText(MainActivity.this, "2,2", Toast.LENGTH_SHORT).show();
} else if (copyEntry.getX() == 3.0f && copyEntry.getY() == 3.0f) {
Toast.makeText(MainActivity.this, "3,3", Toast.LENGTH_SHORT).show();
}
}
}
}
isTapped[0] = true;
return false;
}
});
}
#Override
public void onNothingSelected() {
}
});
The code snippet above let me work with the Entry object which was associated with the point that was double tapped. Also, I have set a range of 100 pixels while the point was double tapped for the convenience of fat-finger tapping.
I'd love to know if there is a clean solution for double tapping a value while obtaining its Entry data.
Deeping into LineChartRenderer I think it's not possible because graph rendering is reached drawing directly on Canvas like follows:
canvas.drawPath(mCirclePathBuffer, mRenderPaint);
You could try to print all your graph children to check if I'm wrong, something like:
int size =lineChart.getChildCount();
for (int i=0; i<size; i++) {
View v = lineChart.getChildAt(i);
Log.i("### LINE", "### INSTANCE OF " + v.getClass());
}
If there are children views they must be accessible it this manner.
Otherwise you could try to intercept double tap on your entire LineGraph, then you could try to check if tap position correspond to a mapped position but I think you must do the entire work on your own.
Another options could be to implement OnChartValueSelectedListener and emulate double tap with a timer mechanism.
I don't think there is a library that supports this functionality.
Let me know if you find a solution please
Related
I'm trying to make something that would allow me to press data points in a line graph. I created a subclass of GraphicalView and added a method to it, which gets called when the graph is clicked.
// This goes into the activity
graphicalView.setOnClickListener(new View.OnClickListener() {
graphicalView.displaySomething();
}
// This one is from the GraphicalView subclass
public void displaySomething() {
SeriesSelection seriesSelection = getCurrentSeriesAndPoint();
int pointIndex = seriesSelection.getPointIndex();
double xValue = seriesSelection.getXValue());
/*
Now in this method, I can do some pretty cool stuff like drawing
additional graphics near the clicked point in the line chart simply
by redrawing the graph and passing it some data. This is made possible
by subclassing both LineGraph and ScatterChart.
For the purposes of this question though, I would simply log the
values I'm curious about.
*/
Log.d(TAG, "pointIndex: " + pointIndex + ", xValue: " + xValue);
repaint();
}
Let's just say I have a graph that shows the weight of my pet dog over time and then I have an array of a list of strings which explains why my pet got that heavy, her favorite food at that time, her activites, etc. The data in the array will be displayed somewhere outside the graph (ie. to a group of TextViews). What I expect is to get the index of the clicked point, and then use that to index the array of data.
pointIndex and xValue are the variables I'm curious about. When drawing additional graphics to the graph, pointIndex was very helpful since ScatterChart's drawSeries knows how to use this variable properly. However, it can't be used for what I want to achieve because this value is not constant per point. For example, I have a point in the graph that lies in (5, 10) and it shows up as the fifth point with pointIndex 4 when the graph is not panned to the left. However, when I pan the graph to the left so that the mentioned point shows up as the first point, the pointIndex changes to 0. Which is why I can't use this variable to index the data array.
As with pointIndex, most of the values related to a point have the same behavior, but I've noticed that xValue is constant. I'm considering on using this value to index my external data source, perhaps by using a Map where xValue is the key. However, I can't figure out where to create this data structure. I've looked around achartengine's source code and it seems that the best place to do the Map's initialization is in ScatterChart's drawSeries method. The problem is the array of floats being passed to it also move around relative to the graph's panning, but my array of data does not move along with it. Can you please point me to the right direction?
this code will allows you to click data points in achartengin:
mySimpleXYPlot.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// handle the click event on the chart
if(event.getAction()==MotionEvent.ACTION_DOWN){
SeriesSelection seriesSelection = mySimpleXYPlot
.getCurrentSeriesAndPoint();
if (seriesSelection == null) {
Toast.makeText(Records.this, "No chart element",
Toast.LENGTH_SHORT).show();
} else {
// display information of the clicked point
Toast.makeText(
Records.this,
"Chart element in series index "
+ seriesSelection.getSeriesIndex()
+ " data point index "
+ seriesSelection.getPointIndex()
+ " was clicked"
+ " closest point value X="
+ seriesSelection.getXValue() + ", Y="
+ seriesSelection.getValue(),
Toast.LENGTH_LONG).show();
}
}
return false;
}
});
I finally got it. I overrided GraphicalView's onDraw method so that I can extract keys and attach them to my data. It goes something like this:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.getClipBounds(mRect);
int top = mRect.top;
int left = mRect.left;
int width = mRect.width();
int height = mRect.height();
if (mRenderer.isInScroll()) {
top = 0;
left = 0;
width = getMeasuredWidth();
height = getMeasuredHeight();
}
mChart.draw(canvas, left, top, width, height, mPaint);
/*
* This is where we build the map of <data point, info>
*/
if (mIsFirstDraw && mIsAttachData && mAdditionalPointData != null && mAdditionalPointData.size() > 0) {
// ClickablePoints is an interface that has a method to get the datapoints rendered by ScatterChart
List<float[]> pointCoords = ((ClickablePoints) mChart).getDataPointCoords();
mPointKeys = new double[pointCoords.size()];
mAdditionalDataMap = new HashMap<Double, String>();
for (int i = 0, j = mPointKeys.length; i < j; i++) {
float[] coords = pointCoords.get(i);
SeriesSelection seriesSelection = mChart.getSeriesAndPointForScreenCoordinate(
new Point(coords[0], coords[1]));
mPointKeys[i] = seriesSelection.getXValue();
mAdditionalDataMap.put(seriesSelection.getXValue(), mAdditionalPointData.get(i));
}
mIsFirstDraw = false;
}
}
mIsFirstDraw is a flag that indicates that it is the first time that the graph will be drawn. It needs to be taken down afterwards so that it does not repeat attaching keys to the data.
Hi I am trying to make an air hockey type game but I am having trouble implementing the paddle movement component. How can I move a body(the paddle) to the location of my touch with the box2d extension for andengine using setLinearVelocity? When I try to do it the ball moves in a seemingly random path.
Here is what I've tried.
#Override
public boolean onSceneTouchEvent(Scene pScene, TouchEvent p) {
if(p.isActionDown()){
moveAir(p.getY(),p.getX());
}else if(p.isActionMove()){
moveAir(p.getY(),p.getX());
}else if(p.isActionUp()){
myPad.setLinearVelocity(0, 0);
}
return false;
}
private void moveAir(float y, float x) {
x=x/32;
y=y/32;
if(myPad.getLocalCenter().x>x){
myPad.setLinearVelocity(myPad.getLinearVelocity().x-10, myPad.getLinearVelocity().y);
}else if(myPad.getLocalCenter().x<x){
myPad.setLinearVelocity( myPad.getLinearVelocity().x+10, myPad.getLinearVelocity().y);
}else{
myPad.setLinearVelocity(0, myPad.getLinearVelocity().y);
}
if(myPad.getLocalCenter().y>y){
myPad.setLinearVelocity(myPad.getLinearVelocity().x,myPad.getLinearVelocity().y-10);
}else if(myPad.getLocalCenter().y<y){
myPad.setLinearVelocity( myPad.getLinearVelocity().x,myPad.getLinearVelocity().y+10 );
}else{
myPad.setLinearVelocity(myPad.getLinearVelocity().x, 0);
}
}
Box2D has a special type of joint for exactly this type of interaction.
You want to use a "mousejoint" on the object touched.
There is already a nice mousejoint tutorial on the andengine forum:
http://www.andengine.org/forums/tutorials/tut-box2d-mousejoint-drag-and-drop-t1156.html
Thanks for the suggestion but I found that using setLinearVelocity was simpler to use than a mouse joint:
float linearVelocityX = (TouchX - myBody.getPosition().x);
float linearVelocityY = (TouchY - myBody.getPosition().y);
Vector2 linearVelocity = new Vector2(linearVelocityX, linearVelocityY);
myPad.setLinearVelocity(linearVelocity);
Im making a program about the game Go and im trying to write some code to allow a person to place a bead, which really just shows an invisible bead. But after an hour of going back and forth with it i just cant seem to figure out why the Y coordinate of my motionevent always seems 2 rows down from where it needs to be.
My code for filling my array
for(int y=0; y< 9;y++)
{
for(int x=0;x<9;x++)
{
String name="Whitebead" + ((9*y)+x+2); //The +2 is because of a naming problem
WhitePieces[x][y]=(ImageView) findViewById(getResources().getIdentifier(name, "id", getPackageName()));
}
}
My code for handling motion events
#Override
public boolean onTouchEvent(MotionEvent e)
{
if(e.getAction() == MotionEvent.ACTION_DOWN)
{
float X=e.getRawX();
float Y=e.getRawY();
if(X>Board.getLeft() && X<Board.getRight())
if(Y< Board.getTop() && Y>Board.getBottom());
player1.placepiece(X, Y);
}
return super.onTouchEvent(e);
}
And finally my code that resolves which bead to what coordinate
public void placepiece(float X, float Y)
{
int[] pieceindex=resolvePiece(X,Y);
pieces[pieceindex[0]][pieceindex[1]].setVisibility(ImageView.VISIBLE);
}
private int[] resolvePiece(float x, float y) {
int Xindex=0;
int[] oldcoords= new int[2]; //cordinates are like so {xCoord, Ycoord}
int[] newcoords= new int[2];
oldcoords[0]=pieces[0][0].getLeft(); //set the oldcoordinates to the first index
oldcoords[1]=pieces[0][0].getTop();
for(int i=1; i<9;i++) //go through the 9 indexs to find the closest X value
{
newcoords[0]=pieces[i][0].getLeft();
newcoords[1]=pieces[i][0].getTop();
if(Math.abs((int)x-newcoords[0])<Math.abs((int)x-oldcoords[0]))
{
Xindex=i;
oldcoords[0]=newcoords[0];
}
}
int Yindex=0;
oldcoords[0]=pieces[0][0].getLeft(); //Reset oldcoords again
oldcoords[1]=pieces[0][0].getTop();
for(int n=1; n<9;n++) //Go through the 9 indexes for the closest Y value
{
newcoords[0]=pieces[0][n].getLeft();
newcoords[1]=pieces[0][n].getTop();
if(Math.abs((int)y-newcoords[1])<Math.abs((int)y-oldcoords[1]))
{
Yindex=n;
oldcoords[1]=newcoords[1];
}
}
int[] rInt= new int[]{Xindex,Yindex};
return rInt;
}
//////EDIT: Fixed
I figured it out, at the top of the android window is about and inch of space where the title and battery life and stuff go, when you get motion coordinates it takes in the whole screen, where .getTop() only gets from where linear layout Starts. SO instead of using .getTop or.getLeft i used .getLocationInWindow(oldcoord[]) and it places the info i needed into my oldcoord array.
//////EDIT: Fixed I figured it out, at the top of the android window is about and inch of space where the title and battery life and stuff go, when you get motion coordinates it takes in the whole screen, where .getTop() only gets from where linear layout Starts. SO instead of using .getTop or.getLeft i used .getLocationInWindow(oldcoord[]) and it places the info i needed into my oldcoord array.
I've been exploring into Nim Game on Android. The players are going to take objects from heaps. I use openGLES to draw the objects and heaps. Where I'm stuck is how to "take".
As the samples shown on official dev guide
, I can override onTouchEvent method in the class that extends GLSurfaceView for the interaction. However, how I could tell where the objects have been drawn? Or are there any objects at the coordinates where I touch?
Any ideas?
Thx in advance!
If I'm understanding your question correctly, it sounds like you want to do some simple collision detection to see if your touch point is inside one of the objects on the heap. You can do this with some basic math between the coordinates of the touch point and the center coordinates which you used to draw the object.
For instance, assuming your objects are rectangles, this would be the general idea:
boolean detectCollision(Object object, TouchPoint touch) {
return object.x - object.width/2 <= touch.x &&
object.x + object.width/2 >= touch.x &&
object.y - object.height/2 <= touch.y &&
object.y + object.height/2 >= touch y;
}
You could then iterate through all of the objects in your heaps and if this returns true for any of them, then you know your touchpoint is inside of that object and can proceed to call whatever you need to call on it.
Keep in mind that the touch coordinates the system gives you will be screen coordinates, so you have to take into account any discrepancies between the screen coordinate system and the coordinate system you defined with your view frustum.
public class Main extends Activity implements OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
synchronized (this) {
if (!_isPaused) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
_touchedX = event.getX();
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
float touchedX = event.getX();
float dx = Math.abs(_touchedX - touchedX);
_dxLowPassed = lowPass(dx, _dxLowPassed);
switch (_screenUsage) {
case HALF_SCREEN:
if (touchedX < _width / 2) {
if(touchedX < _touchedX) {
_zAngle = (2 * _dxLowPassed / _width) * TOUCH_SENSITIVITY * ANGLE_SPAN;
_zAngleLowPassed = lowPass(_zAngle, _zAngleLowPassed);
GLES20Renderer._zAngle = GLES20Renderer._zAngle + _zAngleLowPassed;
}
} else {
if( touchedX > _touchedX ) {
_zAngle = (2 * _dxLowPassed / _width) * TOUCH_SENSITIVITY * ANGLE_SPAN;
_zAngleLowPassed = lowPass(_zAngle, _zAngleLowPassed);
GLES20Renderer._zAngle = GLES20Renderer._zAngle - _zAngleLowPassed;
}
}
Log.d("TOUCH", new Float(_zAngleLowPassed).toString());
break;
I'm working on a painting application for Android and I'd like to use raw data from the device's touch screen to adjust the user's paint brush as they draw. I've seen other apps for Android (iSteam, for example) where the size of the brush is based on the size of your fingerprint on the screen. As far as painting apps go, that would be a huge feature.
Is there a way to get this data? I've googled for quite a while, but I haven't found any source demonstrating it. I know it's possible, because Dolphin Browser adds multi-touch support to the Hero without any changes beneath the application level. You must be able to get a 2D matrix of raw data or something...
I'd really appreciate any help I can get!
There are some properties in the Motion Event class. You can use the getSize() method to find the size of the object. The Motion Event class also gives access to pressure, coordinates etc...
If you check the APIDemos in the SDK there's a simple paitning app called TouchPaint
package com.example.android.apis.graphics;
It uses the following to draw on the canvas
#Override public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
mCurDown = action == MotionEvent.ACTION_DOWN
|| action == MotionEvent.ACTION_MOVE;
int N = event.getHistorySize();
for (int i=0; i<N; i++) {
//Log.i("TouchPaint", "Intermediate pointer #" + i);
drawPoint(event.getHistoricalX(i), event.getHistoricalY(i),
event.getHistoricalPressure(i),
event.getHistoricalSize(i));
}
drawPoint(event.getX(), event.getY(), event.getPressure(),
event.getSize());
return true;
}
private void drawPoint(float x, float y, float pressure, float size) {
//Log.i("TouchPaint", "Drawing: " + x + "x" + y + " p="
// + pressure + " s=" + size);
mCurX = (int)x;
mCurY = (int)y;
mCurPressure = pressure;
mCurSize = size;
mCurWidth = (int)(mCurSize*(getWidth()/3));
if (mCurWidth < 1) mCurWidth = 1;
if (mCurDown && mBitmap != null) {
int pressureLevel = (int)(mCurPressure*255);
mPaint.setARGB(pressureLevel, 255, 255, 255);
mCanvas.drawCircle(mCurX, mCurY, mCurWidth, mPaint);
mRect.set(mCurX-mCurWidth-2, mCurY-mCurWidth-2,
mCurX+mCurWidth+2, mCurY+mCurWidth+2);
invalidate(mRect);
}
mFadeSteps = 0;
}
Hope that helps :)
I'm working on something similar, and I'd suggest looking at the Canvas and Paint classes as well. Looking at getHistorySize() in Motion Event might also be helpful for figuring out how long a particular stroke has been in play.