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)
}
Related
I have gone through the samples for creating a custom Android 2.0 watchface with support for complications. the document for ComplicationDrawable states that we can provide custom progressbars and use setRangedValueProgressHidden() to suppress the default UI.
Optional fields are not guaranteed to be displayed. If you want to draw your own progress bar, you can use the setRangedValueProgressHidden() method to hide the progress bar provided by the ComplicationDrawable class.
But I have been unable to find guides on how to draw the custom UI after setting the default progress bar to hidden. Any pointers will be highly appreciated.
There is no guide because there isn't a single/preferred way to do this. Here are a few steps to help you get started:
1) Create a Canvas and a Bitmap that are large enough to contain your custom progress bar:
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
2) Make sure that the complication has data to show, and that it is a ranged value complication. (You can access the complication data from the onComplicationDataUpdate(int complicationId, ComplicationData complicationData) method.):
if(complicationData != null && complicationData.getType() == ComplicationData.TYPE_RANGED_VALUE) {
// TODO: Get progress data
}
3) Get the progress values from your ComplicationData object (all these fields are required):
float minValue = complicationData.getMinValue();
float maxValue = complicationData.getMaxValue();
float currentValue = complicationData.getValue();
4) Draw the progress in whatever way you want on your Canvas. Below is a simplified example from one of our watch faces.
// Calculate the start angle based on the complication ID.
// Don't worry too much about the math here, it's very specific to our watch face :)
float startAngle = 180f + 22.5f + ((complicationId - 2) * 45f);
// Calculate the maximum sweep angle based on the number of complications.
float sweepAngle = 45;
// Translate the current progress to a percentage value between 0 and 1.
float percent = 0;
float range = Math.abs(maxValue - minValue);
if (range > 0) {
percent = (currentValue - minValue) / range;
// We don't want to deal progress values below 0.
percent = Math.max(0, percent);
}
// Calculate how much of the maximum sweep angle to show based on the current progress.
sweepAngle *= percent;
// Add an arc based on the start and end values calculated above.
Path progressPath = new Path();
progressPath.arcTo(getScreenRect(), startAngle, sweepAngle);
// Draw it on the canvas.
canvas.drawPath(progressPath, getProgressPaint());
And here is the end result:
I'm doing a simple android animation using my self-customized VIEW. I have two circles drawn on the onDraw() method of the class extends to View class. The one circle is moving upon dragging using MotionEvent while the other one is static on a certain position. If the moving circle touches any point of a static circle, the color of the moving circle will change to the color of the static circle.
For example
int_circle_radius= 50;
int circle1_x = 0;
int circle1_y = 0;
int circle2_x = 200;
int circle2_y = 200;
let's assume that the moving circle which is the circle 1 was drag and drop to a certain point of the circle 2.
I tried using the below formula but the circle 1's color only change if it really goes to the exact location of the circle 2.
if (circle1_x == circle1_x && circle1_y == circle2_y){
paint.setColor(Color.RED);
}
I know that the problem here is a circle has many points from it's radius, but how can I trigger a specific action if the a circle touches any of his point to another circle? Thanks.
You can simply calculate the distance between the centers of the two circles. If the distance is less than two times the radius, the circles are intersecting. Calculating that is easy. You can not expect to get the exact MotionEvent where the circles distance equals the double radius, so you have to check for a distance that is less or equal:
int deltaX = circle1_x - circle2_x;
int deltaY = circle1_y - circle2_y;
if(Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)) <= 2 * circle_radius) {
paint.setColor(Color.RED);
}
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!
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...
I am building a line chart for one of our applications. The line part in the chart is working fine but now I have to fill the area below the line with a color, something like this image below.
I am using this code to draw the line on to a bitmap using a canvas.
List<Float> xCoordinates = (List<Float>) coordinates[0];
List<Float> yCoordinates = (List<Float>) coordinates[1];
for (int i = 0; i < xCoordinates.size(); i++) {
if (!firstSet) {
x = xCoordinates.get(i);
y = yCoordinates.get(i);
p.moveTo(x, y);
firstSet = true;
} else {
x = xCoordinates.get(i);
y = yCoordinates.get(i);
p.lineTo(x, y);
}
}
textureCanvas.drawPath(p, pG);
What I need to know is, is there a way to easily find the area below the line and make it a color or will I have to calculate each area between 3 points for the graph and fill it with a color?
I cannot use existing chart libraries like AchartEngine, ChartDroid, aFreeChart ect.
Found a solution
I first painted the gradient, after that I painted the line chart and erased all along the line by creating another path that followed the line chart and closed it up at the end. After that I painted the area transparent to erase the top part of the gradient.
List<Float> xCoordinates = coordinates.first;
List<Float> yCoordinates = coordinates.second;
for (int i = 0; i < xCoordinates.size(); i++) {
if (!firstSet) {
x = xCoordinates.get(i);
y = yCoordinates.get(i);
linePath.moveTo(x, y);
erasePath.moveTo(x, 0);
erasePath.lineTo(x, y);
firstSet = true;
} else {
x = xCoordinates.get(i);
y = yCoordinates.get(i);
linePath.lineTo(x, y);
erasePath.lineTo(x, y);
}
}
erasePath.lineTo(getWidth(), y);
erasePath.lineTo(getWidth(), 0);
erasePath.lineTo(0, 0);
getTextureCanvas().drawPath(erasePath, clear);
getTextureCanvas().drawPath(linePath, pG);
The result looked like this
Another way to do this is to follow the path exactly and clip around the bottom edges and just paint the gradient inside the second path. This will give the same result but all content above the graph will be kept and nothing will be erased.