I have a list of Entry points which I have plotted on LineChart. Now I want to set icon to a particular point. How to set or draw icons in LineChart to my selected position in MPAndroidChart?
You can add icon to your selected point using 3 parameters Entry constructor where third parameter is a Drawable:
public Entry(float x, float y, android.graphics.drawable.Drawable icon)
Parameters:
x - the x value
y - the y value (the actual value of the entry)
icon - icon image
Ref: MPAndroidChart v3.0.3 JavaDoc
Example:
ArrayList<Entry> values = new ArrayList<Entry>();
values.add(new Entry (x, y, ContextCompat.getDrawable(getApplicationContext(),R.drawable.star)));
You can mark a single point with icon from entire LineDataSet by adding Drawable in Entry constructor.
I know this is an old question but This is my solution in case anyone still needs it.
First, you need to call setDrawIcons(true) on your data set. Then You need to define your entry as a member inside your class to be able to reset it later.
private var selectedEntry: Entry? = null
And Inside onValueSelected you need to set selectedEntry's icon as null if it exists and set the icon for the new selectedEntry point.
override fun onValueSelected(e: Entry?, h: Highlight?) {
if (selectedEntry != null) {
selectedEntry?.icon = null
}
for (set in chart.data.dataSets) {
selectedEntry = set.getEntryForIndex(set.getEntryIndex(e))
selectedEntry?.icon = ContextCompat.getDrawable(this,R.drawable.star)
}
chart.invalidate()
}
Related
Is it possible to get the x coordinate from a character in a TextView in Android?
I'm not looking for the coordinate of the TextView itself, I need the coordinate of the last character in the TextView (multi line)
Thanks in advance
Java Solution
Here is how to get the x and y coordinates of a specific character. offset is the index of the desired character in the textView's String. These coordinates are relative to the parent container
Layout layout = textView.getLayout();
if (layout == null) { // Layout may be null right after change to the text view
// Do nothing
}
int lineOfText = layout.getLineForOffset(offset);
int xCoordinate = (int) layout.getPrimaryHorizontal(offset);
int yCoordinate = layout.getLineTop(lineOfText);
Kotlin Extension Function
If you expect to use this more than once:
fun TextView.charLocation(offset: Int): Point? {
layout ?: return null // Layout may be null right after change to the text view
val lineOfText = layout.getLineForOffset(offset)
val xCoordinate = layout.getPrimaryHorizontal(offset).toInt()
val yCoordinate = layout.getLineTop(lineOfText)
return Point(xCoordinate, yCoordinate)
}
NOTE: To ensure layout is not null, you can call textview.post(() -> { /* get coordinates */ }) in Java or textview.post { /* get coordinates */ } in Kotlin
Use:
layout.getPrimaryHorizontal(int offset)
It is simple to use. You just iterate through the layout using the length of the text it uses.
It will return the x of the Character . So lines I'm still getting from the layout.getLineTop() . By the way, if you are using the layout.getLineTop() , note that there is some strange behaviour, possibly a bug.
Given a span that has one or more paragraphs, try to get the last character of the entire span dosen't work. Is there another way to get the same result of getPrimaryHorizontal()?
I'm using a BarChart in an app but sometimes when I re-draw it the bars width is not respected and I get inconsistent results (even though the amount of values in the X axis is always 100).
This is how I want it to look always:
but sometimes it looks like this:
Does anyone know why this happens and how can I force it to always look the same?
I think it has something to do with the range of values in the X axis, as the chart looks good when the range goes from 0 to 50 or similar, but it looks bad when the range is smaller.
I'm already setting a bar width value like this but it doesn't help:
barChartView?.data?.barWidth = 0.3f
This is the full chart configuration I'm using (this method runs only once when the Fragment is created):
private fun setUpChartView() {
barChartView?.setTouchEnabled(false)
val xAxis = barChartView?.xAxis
val yAxis = barChartView?.axisLeft
// Hide all parts of the chart that we don't want to show
barChartView?.legend?.isEnabled = false
barChartView?.description?.isEnabled = false
barChartView?.setDrawGridBackground(false)
xAxis?.setDrawGridLines(false)
xAxis?.setDrawAxisLine(false)
yAxis?.setDrawAxisLine(false)
barChartView?.axisRight?.isEnabled = false
// Show the Y axis grid lines as dashed
yAxis?.enableGridDashedLine(5f, 10f, 0f)
val barChartTextColor = getColor(R.color.chart_text)
val barChartTextSize = 12f
val barChartLabelsTypeface = Typeface.SANS_SERIF
// Show the X axis labels at the bottom and set the proper style
xAxis?.position = XAxis.XAxisPosition.BOTTOM
xAxis?.textColor = barChartTextColor
xAxis?.textSize = barChartTextSize
xAxis?.typeface = barChartLabelsTypeface
// Set the proper style for the Y axis labels
yAxis?.textSize = barChartTextSize
yAxis?.textColor = barChartTextColor
yAxis?.typeface = barChartLabelsTypeface
barChartView?.setNoDataText("")
val viewPortHandler = barChartView?.viewPortHandler
val axisTransformer = barChartView?.getTransformer(LEFT)
// Set a custom axises so that we can control the drawing of the labels and grid lines
barChartView?.setXAxisRenderer(CustomEntriesXAxisRenderer(viewPortHandler = viewPortHandler,
xAxis = xAxis,
transformer = axisTransformer))
barChartView?.rendererLeftYAxis = CustomEntriesYAxisRenderer(viewPortHandler = viewPortHandler,
yAxis = yAxis,
transformer = axisTransformer)
}
This is the code I use to draw the chart when I get new data:
private fun drawBarChart() {
// The data list has always 100 items
val entries = data.map { BarEntry(it.x, it.y) }
// Prepare the chart data set by adding the entries and configuring its style (colors, etc.)
val dataSet = BarDataSet(entries, null)
dataSet.setDrawValues(false)
dataSet.colors = colorsList
// Load data to chart
if (barChartView?.data == null) {
// If there is no data set yet, create a new data object and add it to the chart (this happens the first
// time we draw the chart after the Fragment was created, or after an empty data list was returned)
barChartView?.data = BarData(dataSet)
} else {
// If there is already data in the chart, remove the existing data set and add the new one
barChartView.data?.removeDataSet(0)
barChartView.data?.addDataSet(dataSet)
barChartView.notifyDataSetChanged()
}
// Set a custom width for the bars or the chart will draw them too wide
barChartView?.data?.barWidth = 0.3f
val xAxis = barChartView?.xAxis
val yAxis = barChartView?.axisLeft
// Set the min and max values for the Y axis or the chart lib will calculate them and add more values than
// we need
yAxis?.axisMinimum = 0f
yAxis?.axisMaximum = entries.last().y
// Set the entries that need to be drawn in the X axis, so the chart doesn't calculate them automatically
(barChartView?.rendererXAxis as CustomEntriesXAxisRenderer).entries = xAxisEntries
// Set the entries that need to be drawn in the Y axis, so the chart doesn't calculate them automatically
(barChartView?.rendererLeftYAxis as CustomEntriesYAxisRenderer).entries = yAxisEntries
// Add the label count to avoid the chart from automatically setting a range of labels instead of the ones we need,
// which prevents the axis value formatter below to set the correct labels
xAxis?.setLabelCount(xAxisEntries.size, true)
yAxis?.setLabelCount(yAxisEntries.size, true)
// Use a custom value formatter that sets only the needed labels on the axises
xAxis?.setValueFormatter { value, _ ->
// TODO
}
yAxis?.setValueFormatter { value, _ ->
// TODO
}
// Draw chart
barChartView?.invalidate()
}
And this is the implementation of the custom axis renderers I created:
class CustomEntriesXAxisRenderer(viewPortHandler: ViewPortHandler?, xAxis: XAxis?, transformer: Transformer?)
: XAxisRenderer(viewPortHandler, xAxis, transformer) {
/**
* Entries used to draw the grid lines and labels on the X axis.
*/
var entries: List<Float>? = null
override fun computeSize() {
entries?.forEachIndexed { i, value ->
mAxis.mEntries[i] = value
}
super.computeSize()
}
}
class CustomEntriesYAxisRenderer(viewPortHandler: ViewPortHandler?, yAxis: YAxis?, transformer: Transformer?)
: YAxisRenderer(viewPortHandler, yAxis, transformer) {
/**
* Entries used to draw the grid lines and labels on the Y axis.
*/
var entries: List<Float>? = null
override fun computeAxisValues(min: Float, max: Float) {
super.computeAxisValues(min, max)
entries?.forEachIndexed { i, value ->
mAxis.mEntries[i] = value
}
}
}
One of the MPAndroidChart devs said this is indeed a bug in the library and it's now in their to-do list. If anyone is having this issue you can follow the progress here.
In the meantime, after playing around with this for a while I found a (quite ugly) hack that prevents the chart from looking too bad:
barChartView?.data?.barWidth = when (entries.last().x) {
in 0..30 -> 0.1f
in 30..60 -> 0.2f
else -> 0.3f
}
You can adjust the values to what looks good for your data entries, but basically by setting a different bar width depending on the range of X values in the data set you can at least make this a bit better until the bug is fixed.
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 using MPAndroidChart.
Searched through the documentation but couldn't find anything that implemented correctly. I have a graph at the moment but I want to change the color of the line if it goes over a certain amount. Graph Example In the example I have linked, it shows a line drawn through the values from 10. I would like this line (the one going through the chart) and the color of the line in the chart to change color over 10. Is this possible? Using MPAndroidChart. I have one dataset at the moment.
Thanks in advance.
Yes. You just need a simple logic.
List<Integer> colors = ...;
List<Entry> entries = ...;
for(...) {
entries.add(...);
if(entries.get(i).getVal() > 10)
colors.add(customcolor);
else
colos.add(othercolor);
}
With LineDataSet.setColors() you can add a list of colors. Each color entry is for one data entry.
'
The trick is to calculate intermediate values for crossing the border.
Everytime I add a data entry, i call this method
private fun addDiffValue(newEntry : Entry){
val last = recordedValues[2].last()
val limit = 50f
if(last.y < limit && newEntry.y > limit ){
val gradient = (newEntry.y - last.y) / (newEntry.x - last.x)
val x_border = last.x + ((limit - last.y) / gradient)
recordedValues[2].add(Entry(x_border, limit))
diffColors.add(Color.LTGRAY)
diffColors.add(Color.RED)
}
// Vorher größer, jetzt kleiner
else if(last.y > limit && newEntry.y < limit) {
val gradient = (newEntry.y - last.y) / (newEntry.x - last.x)
val x_border = last.x + ((limit - last.y) / gradient)
recordedValues[2].add(Entry(x_border, limit))
diffColors.add(Color.RED)
diffColors.add(Color.LTGRAY)
}else if(last.y > limit ){
diffColors.add(Color.RED)
} else {
diffColors.add(Color.LTGRAY)
}
recordedValues[2].add(newEntry)
}
It is important to say, that I start with recordedValues[2].add(Entry(0f,0f)), otherwise last() would throw an error.
I create the LineDataSet and add the colors:
val dataSet3 = LineDataSet(recordedValues[2], "My Label")
dataSet3.setColors(diffColors)
As you can see in this screenshot, all values above 50 are 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!