I want to set a vertical line in center of LineChart like this:
When scrolling to each point, it can notify to change the date below (the orange date field). And it can move left or right programmatically by click on arrow button.
Currently, I can set viewport and allow moving to center with this code:
LineData data = new LineData(xVals, dataSets);
mChart.setScaleMinima((float) data.getXValCount() / 7f, 1f);
mChart.moveViewTo(0, 7, YAxis.AxisDependency.LEFT);
And get the result:
How can I draw and set a vertical line like above?
Update:
For the listener, I think OnChartGestureListener onChartTranslate(MotionEvent me, float dX, float dY) may help. What I need is the distance between 2 points and how to calculate how many points are in current view port. Does anyone know that?
Have you tried using getEntryByTouchPoint on your chart supplying the x and y coordinates of the center of the chart?
public Entry getEntryByTouchPoint(float x, float y)
returns the Entry object displayed at the touched position of the chart
Take a look at the method
protected void drawGridBackground(Canvas c) {
in the BarLineChartBase class (parent for a LineChart). In that method you have all data to draw your line right in the middle.
Something like this
RectF rectF = mViewPortHandler.getContentRect();
float xMiddle = (rectF.right - rectF.left)/2;
Paint p = new Paint();
p.setColor(Color.BLACK);
c.drawLine(xMiddle, rectF.bottom, xMiddle, rectF.top, p);
Maybe it's too late but here is my answer. It's encoded in Swift using Charts (MPAndroidCharts port for iOS) but API is 99% the same ;)
let verticalPointEntry = ChartDataEntry(x: xValue, y: yValue)
let dataSet = LineChartDataSet(values: [verticalPointEntry], label: "")
dataSet.drawCirclesEnabled = false
dataSet.drawValuesEnabled = false
dataSet.setDrawHighlightIndicators(true)
dataSet.drawHorizontalHighlightIndicatorEnabled = false
dataSet.highlightColor = UIColor.white
dataSet.highlightLineWidth = 1
let highlightPoint = Highlight(x: xValue, y: yValue, dataSetIndex: datasetIndex)
self.highlightValues([highlightPoint])
// "yourNormalDataSet" is your regular dataSet in which you want to display vertical line over it
let chartData = LineChartData(dataSets: [yourNormalDataSet, dataSet])
self.data = chartData
self.data?.notifiyDataChanged()
self.notifyDataSetChanged
This will display a vercital line over the point defined by your xValue variable.
Hope it helps!
Related
I am a bit new to MPAndroidChart library and I am going to make a line chart for my data. I intend to set yaxis values exactly in the center of each grid rectangle which is inside them.
How that would be possible ?
I am actually trying to do this also,get the Y values show up above the graph in the center X axis, but I was having trouble. I know you have to override the draw method in your MarkerView class, the code I was using is below. Maybe you can play with it and get the results you need. Other questions about this I was looking at had posX = getXoffset() & posY = 0 but for some reason I wasn't able to get the getXoffset() Override method.
#Override
public void draw(Canvas canvas, float posX, float posY) {
posX = 400;
posY = -30;
canvas.translate(posX,posY);
draw(canvas);
canvas.translate(-posX,-posY);
}
I am not getting what exactly do you want. Try this:
lineChart.getXAxis().setCenterAxisLabels(true);
In a custom view I design a text with drawText function. Now, I would intercepted the click on the text. this my code:
float x = (float)position.getX() + Constants.RADIUS + Constants.OFFSET_OVER_ARCH;
float y = (float)position.getY() ;
for (String line : node.getNode().getLabel().split(Constants.CHAR_SEPARATOR)) {
canvas.drawText(line, x, y, paintSele);
y += paintSele.descent() - paintSele.ascent();
}
Draw rectangle in the text and check if the coordinates of the click is on the rectangle area? This a better solution?
Thanks a lot!
What you can do, is to Paint.measureText(), then Paint.getTextBound() of the text which will fill the inputted Rect with the text's coordinates, and then listen for clicks in that rectangle.
I am using MpChart's LineChart for showing my graphs. I have added multiple data set lines. Everything is working fine. But i want the MarkerView should be set to some point in the middle and should be visible by default. Right now marker view is visible only when I touch it. Is there any method to achieve this ?
Initial graph
Markerview shown after graph is touched
Is it help?
for (IDataSet set : mChart.getData().getDataSets())
set.setDrawValues(true);
mChart.invalidate();
I've done this in the IOS version of this lib.But as the documentation says they almost identical, so i hope my answer is correctly "translated".
What i did was get a point where your default marker will always be shown.
Default point were the marker should be locked, define own your point based on HighestVisibleX or something else.
Then on first render i highlight this point:
Highlight myFirstRenderedHighlight = new Highlight(myLockedMarkerPoint.x, 0);
someChart.highlightValues(new Highlight[] { myFirstRenderedHighlight });
So if u always want to show this point even when dragging along x-axis, then u need to redraw the highlight. This can be done by listening on chartTranslation This can be done by implementing the interface OnChartGestureListener. onChartTranslate() , example:
// same logic as picking first point with HighestVisibleX or something
Highlight movingHighlight = new Highlight(entryInLockedPoint.x,0);
someChart.highlightValues(new Highlight[] { movingHighlight });
And if you want to mark several of graphs then choose from charts datasets.
Hope this was what your were looking for :)
There is no default implementation to do this in the library.
One way to do this can be to modify the LineChartRenderer class in the library. MpAndroidCharts allows you to draw circles on plotted points, you can modify this by defining a new constructor for LineChartEntry and pass a bitmap to it. You can then draw your bitmap at the plotted point instead of the circle that is drawn.
ArrayList<Entry> values = new ArrayList<Entry>();
Drawable d;
for (int i = 0; i < dataList.size(); i++) {
LineChartData data = dataList.get(i);
float val = Float.valueOf(Utils.decimalValuePrecisionTwoPlaces((float) data.getDataVolGallon()));
if (data.getImageIndex() >= 0) {
d = ContextCompat.getDrawable(getContext(), resIcon[data.getImageIndex()]);
bitmap = ((BitmapDrawable) d).getBitmap();
bitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() / 2, bitmap.getHeight() / 2, false);
values.add(new Entry(i, val, bitmap));
} else {
values.add(new Entry(i, val));
}
}
Above code is an example for how to set entries with and without bitmap.
if(e.getBitmap() != null)
{
c.drawBitmap(e.getBitmap(),mCirclesBuffer[0] - circleRadius, mCirclesBuffer[1] - circleRadius, mRenderPaint);
}
This the code to draw the image from bitmap, just comment the line to draw circles in drawCircles() of LineChartRenderer and use this instead.
Leave a comment if you have any question.Hope this helps !!
To adjust start x,y position of your Marker just override this method in your MarkerView class. This also adjust your marker x position if it gets out of bounds of your chart view.
override fun draw(canvas: Canvas, positionX: Float, positionY: Float) {
// Check marker position and update offsets.
var posx = positionX
val posy: Float
val w = width
val h = height
posx -= if (resources.displayMetrics.widthPixels - positionX < w) {
w.toFloat()
} else {
w / 2.toFloat() // Draw marker in the middle of highlight
}
posy = lineChart.height - h.toFloat() * 2 // Always starts from middle of chart
// Translate to the correct position and draw
canvas.translate(posx, posy)
draw(canvas)
canvas.translate(-posx, -posy)
}
I am trying to randomize the position of a few textviews inside a frameview. The textviews will also have a randomized rotation between 0 and 360 degrees. The textViews is not allowed to be on top of eachother which means I need to check for collisions (or at least know which points that are valid/not valid). I do not know how to check for collision between two textviews when they are rotated. I have tried to use Rect intersects but this does not really work because this function only works if there is no rotation to the view.
Here is an example on what i want:
TEXT1 is placed first. When TEXT2 is placed the green border around the TEXT1 and TEXT2 is colliding which means that TEXT2 should not be allowed to be placed there. TEXT3 does however not collide with anything and should be allowed to be placed. So I want to check the collision for the green border and not the blue rectangle. How do I do this?
Edit
To rotate the view I am using View.setRotation(float)
To position the textview I am using setX(float) and setY(float).
I ended up with the following solution where I create 4 points, one for each corner of the textView, which I then rotate at the same angle as the textView. With these points I then create a Path which I am using to create a region.
private Region createRotatedRegion(TextView textView){
Matrix matrix = new Matrix();
matrix.setRotate(textView.getRotation(), textView.getX() + textView.getMeasuredWidth() / 2, textView.getY() + textView.getMeasuredHeight() / 2);
Path path = new Path();
Point LT = rotatePoint(matrix, textView.getX(), textView.getY());
Point RT = rotatePoint(matrix, textView.getX() + textView.getMeasuredWidth(), textView.getY());
Point RB = rotatePoint(matrix, textView.getX() + textView.getMeasuredWidth(), textView.getY() + textView.getMeasuredHeight());
Point LB = rotatePoint(matrix, textView.getX(), textView.getY() + textView.getMeasuredHeight());
path.moveTo(LT.x, LT.y);
path.lineTo(RT.x, RT.y);
path.lineTo(RB.x, RB.y);
path.lineTo(LB.x, LB.y);
Region region = new Region();
region.setPath(path, new Region(0, 0, textViewParent.getWidth(), textViewParent.getHeight()));
return region;
}
private Point rotatePoint(Matrix matrix, float x, float y){
float[] pts = new float[2];
pts[0] = x;
pts[1] = y;
matrix.mapPoints(pts);
return new Point((int)pts[0], (int)pts[1]);
}
When I have two regions which now have the same position and rotation as two textViews I can then use the following code to check for collision:
if (!region1.quickReject(region2) && region1.op(region2, Region.Op.INTERSECT)) {
return true; //There is a collision
}
Probably not the best solution but it gets the job done.
Background
I'm developing an app for Android that plots data as a line graph using AndroidPlot. Because of the nature of the data, it's important that it be pannable and zoomable. I'm using AndroidPlot's sample code on bitbucket for panning and zooming, modified to allow panning and zooming in both X and Y directions.
Everything works as desired except that there are no X and Y axis lines. It is very disorienting to look at the data without them. The grid helps, but there's no guarantee that grid lines will actually fall on the axis.
To remedy this I have tried adding two series, one that falls on just the X axis and the other on the Y. The problem with this is that if one zooms out too far the axis simply end, and it becomes apparent that I have applied a 'hack'.
Question
Is it possible to add X and Y axis lines to AndroidPlot? Or will my sad hack have to do?
EDIT
Added tags
I figured it out. It wasn't trivial, took a joint effort with a collaborator, and sucked up many hours of our time.
Starting with the sample mentioned in my question, I had to extend XYPlot (which I called GraphView) and override the onPreInit method. Note that I have two PointF's, minXY and maxXY, that are defined in my overridden XYPlot and manipulated when I zoom or scroll.
#Override
protected void onPreInit() {
super.onPreInit();
final Paint axisPaint = new Paint();
axisPaint.setColor(getResources().getColor(R.color.MY_AXIS_COLOR));
axisPaint.setStrokeWidth(3); //or whatever stroke width you want
XYGraphWidget oldWidget = getGraphWidget();
XYGraphWidget widget = new XYGraphWidget(getLayoutManager(),
this,
new SizeMetrics(
oldWidget.getHeightMetric(),
oldWidget.getWidthMetric())) {
//We now override XYGraphWidget methods
RectF mGridRect;
#Override
protected void doOnDraw(Canvas canvas, RectF widgetRect)
throws PlotRenderException {
//In order to draw the x axis, we must obtain gridRect. I believe this is the only
//way to do so as the more convenient routes have private rather than protected access.
mGridRect = new RectF(widgetRect.left + ((isRangeAxisLeft())?getRangeLabelWidth():1),
widgetRect.top + ((isDomainAxisBottom())?1:getDomainLabelWidth()),
widgetRect.right - ((isRangeAxisLeft())?1:getRangeLabelWidth()),
widgetRect.bottom - ((isDomainAxisBottom())?getDomainLabelWidth():1));
super.doOnDraw(canvas, widgetRect);
}
#Override
protected void drawGrid(Canvas canvas) {
super.drawGrid(canvas);
if(mGridRect == null) return;
//minXY and maxXY are PointF's defined elsewhere. See my comment in the answer.
if(minXY.y <= 0 && maxXY.y >= 0) { //Draw the x axis
RectF paddedGridRect = getGridRect();
//Note: GraphView.this is the extended XYPlot instance.
XYStep rangeStep = XYStepCalculator.getStep(GraphView.this, XYAxisType.RANGE,
paddedGridRect, getCalculatedMinY().doubleValue(),
getCalculatedMaxY().doubleValue());
double rangeOriginF = paddedGridRect.bottom;
float yPix = (float) (rangeOriginF + getRangeOrigin().doubleValue() * rangeStep.getStepPix() /
rangeStep.getStepVal());
//Keep things consistent with drawing y axis even though drawRangeTick is public
//drawRangeTick(canvas, yPix, 0, getRangeLabelPaint(), axisPaint, true);
canvas.drawLine(mGridRect.left, yPix, mGridRect.right, yPix, axisPaint);
}
if(minXY.x <= 0 && maxXY.x >= 0) { //Draw the y axis
RectF paddedGridRect = getGridRect();
XYStep domianStep = XYStepCalculator.getStep(GraphView.this, XYAxisType.DOMAIN,
paddedGridRect, getCalculatedMinX().doubleValue(),
getCalculatedMaxX().doubleValue());
double domainOriginF = paddedGridRect.left;
float xPix = (float) (domainOriginF - getDomainOrigin().doubleValue() * domianStep.getStepPix() /
domianStep.getStepVal());
//Unfortunately, drawDomainTick has private access in XYGraphWidget
canvas.drawLine(xPix, mGridRect.top, xPix, mGridRect.bottom, axisPaint);
}
}
};
widget.setBackgroundPaint(oldWidget.getBackgroundPaint());
widget.setMarginTop(oldWidget.getMarginTop());
widget.setMarginRight(oldWidget.getMarginRight());
widget.setPositionMetrics(oldWidget.getPositionMetrics());
getLayoutManager().remove(oldWidget);
getLayoutManager().addToTop(widget);
setGraphWidget(widget);
//More customizations can go here
}
And that was that. I sure wish this was built into AndroidPlot; it'll be nasty trying to fix this when it breaks in an AndroidPlot update...