I am using MpAndroidChart library for android. I am able to give different color for the region below and above of the limit line. but i am getting a keyword "Stack" in legend(please check the attached screen shot).
Without limit line everything working perfectly. But if i am using limit line (red for region right of the limit line and green for otherside ) i am getting two legend and keyword "Stack" is appending along with legend.
if (yAxisValue != null) {
var progressData = progressLayoutData
val barDatasets: MutableList<IBarDataSet> = java.util.ArrayList()
for (yValues in yAxisValue) {
val entries: MutableList<BarEntry> = java.util.ArrayList()
var i = 0f
var isHaveLimitLine = false
val colorList: MutableList<Int> = java.util.ArrayList()
for (value in yValues?.dataSet) {
if(progressData?.limitLine!=null && progressData?.limitLine!=0) {
if (value > progressData?.limitLine ?: 0) {
var differenceValue = value?.toFloat() - progressData?.limitLine?.toFloat()!!
entries.add(BarEntry(i, floatArrayOf(progressData?.limitLine?.toFloat()!!, differenceValue)))
colorList.add(fhuiManager?.getParsedColor(yValues?.color,context))
colorList.add(fhuiManager?.getParsedColor(progressData?.limitExceedColor ?: "#FE2020",context))
} else {
colorList.add(fhuiManager?.getParsedColor(yValues?.color,context))
entries.add(BarEntry(i, value?.toFloat()))
}
isHaveLimitLine = true
}else{
entries.add(BarEntry(i, value?.toFloat()))
}
i++
}
val barDataSet = BarDataSet(entries, "" + yValues?.datasetName)
if(isHaveLimitLine)
barDataSet?.colors = colorList
else
barDataSet?.color = fhuiManager?.getParsedColor(yValues?.color,context)
barDataSet?.setDrawValues(false)
// add to line data set
barDatasets?.add(barDataSet)
}
return barDatasets
}
Please see the screenshot of issue here
Related
My MPAndroidChart displays a stronger line to represent the middle of the graph. How do I avoid that please?
Here is what I have at the moment:
private fun addChart(totals: Totals) {
chart1.apply {
isDragEnabled = true
isScaleXEnabled = false
isScaleYEnabled = true
axisRight.isEnabled = false
axisLeft.setDrawAxisLine(false)
xAxis.isEnabled = false
legend.isEnabled = false
description.text = ""
setTouchEnabled(false)
}
val y: YAxis = chart1.axisLeft
y.axisMaximum = 800f
y.axisMinimum = 0f
y.labelCount = 5
val yValues = ArrayList<Entry>()
for ((index, i) in totals.features!!.withIndex()) {
yValues.add(Entry(index.toFloat(), i.attributes?.confirmedCovidCases!!.toFloat()))
}
val set1 = LineDataSet(yValues, "Data set 1")
set1.color = resources.getColor(R.color.orange)
set1.setDrawCircles(false)
val dataSets = ArrayList<ILineDataSet>()
dataSets.add(set1)
val data = LineData(dataSets)
chart1.data = data
}
Thank you very much.
I try to recreate your chart but did not have the problem
Try to run it on device, because it might your PC/simulator pixel bleeding
I want to set the negative values of the bars to the left of the bars but at present some of the bars are overlapping these values.
How can i remove this overlapping of the negative values and if i set horizonatal_chart.setDrawValueAboveBar(true) then the positive bar will overlap the positive values.
Here's the code:
private fun filterHorizontalBarData(myList: List<Response>?) {
try {
val brand = ArrayList<String>()
var index = 0f
var color_index = 0
val data = BarData()
val values_adapter = ArrayList<String>()
for (item in myList!!) {
if (item.kPI.equals("Value % Growth") || item.kPI.equals("Volume % Growth")) {
val kpiValue = ArrayList<BarEntry>()
kpiValue.add(BarEntry(index,item.kPIvalue.toFloat()))
brand.add(item.brand)
values_adapter.add(item.kPIvalue.toString())
val barDataSet = BarDataSet(kpiValue, item.brand)
if(color_index<7)
barDataSet.setColor(getColor(horizonatal_chart.context, getColorID(color_index)))
else
barDataSet.setColor(getColorID(color_index))
barDataSet.valueTextSize = 8f
// barDataSet.setDrawValues(false)
data.addDataSet(barDataSet)
index++
color_index++
}
}
// values_adapter.sortDescending()
// first_bar_values.adapter = AdapterValuesHorizontalBar(values_adapter)
setHorizontalChart(data, brand)
}catch (e: Exception){
e.printStackTrace()
}
}
private fun setHorizontalChart(data : BarData, brand: ArrayList<String>){
horizonatal_chart.setDrawBarShadow(false)
val description = Description()
description.text = ""
horizonatal_chart.description = description
horizonatal_chart.legend.setEnabled(false)
horizonatal_chart.setPinchZoom(false)
horizonatal_chart.setScaleEnabled(false)
horizonatal_chart.setDrawValueAboveBar(true)
//Display the axis on the left (contains the labels 1*, 2* and so on)
val xAxis = horizonatal_chart.getXAxis()
xAxis.setDrawGridLines(false)
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM)
xAxis.setEnabled(true)
xAxis.setDrawAxisLine(false)
xAxis.textColor = Color.parseColor("#a1a1a1")
xAxis.xOffset = 5f
val yLeft = horizonatal_chart.axisLeft
//Set the minimum and maximum bar lengths as per the values that they represent
yLeft.axisMaximum = 100f
yLeft.axisMinimum = 0f
yLeft.isEnabled = false
yLeft.setDrawZeroLine(true)
yLeft.granularity = 1f
//Now add the labels to be added on the vertical axis
xAxis.valueFormatter = IAxisValueFormatter { value, axis ->
if (value.toInt() < brand.size) {
brand.get(value.toInt())
} else {
"0"
}
}
val yRight = horizonatal_chart.axisRight
yRight.setDrawAxisLine(true)
yRight.setDrawGridLines(false)
yRight.isEnabled = false
//Set bar entries and add necessary formatting
horizonatal_chart.axisLeft.setAxisMinimum(data.yMin)
data.barWidth = 0.9f
// val myCustomChartRender = MyCustomChartRender(horizonatal_chart, horizonatal_chart.animator, horizonatal_chart.viewPortHandler)
// //Add animation to the graph
// horizonatal_chart.renderer = myCustomChartRender
horizonatal_chart.animateY(2000)
horizonatal_chart.data = data
horizonatal_chart.setTouchEnabled(false)
horizonatal_chart.invalidate()
}
Please help me !
The problem seems that you've not provided minimum and maximum cap for rightAxis.
horizonatal_chart.axisRight.setAxisMinimum(-100)
horizonatal_chart.axisRight.setAxisMaximum(100)
Reference : The same issue is mentioned at issue and was solved by providing axis minimum and maximum
following this (Android - Fill the color between two lines using MPAndroidChart) answer I was able to fill with color the space between two lines using AndroidMPChart library.
But now I want to customize the filling color to have:
the areas above the boundarySet filled with blue color;
the areas below the boundarySet filled with green color.
Like in the following screenshot (please note that the blue line is a lineSet, so it could be that it is not a limit line):
I would like to customize the line color of the chart, setting it as the filling:
blue color for the line above the boundarySet;
green color for the line below the boundary set.
Is it possible?
I'm not able to find anything similar in the examples using MPAndroidChart.
Thank you!
you can try to override the drawLinear in LineChartRender
This has worked for me:
dataSet.setFillFormatter(new DefaultFillFormatter() {
#Override
public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) {
return 22500;// its value of midel Y line
}
});
I've done this with this custom chart render) I use colors from line here, but you can use other colors. Change your colors here :drawFilledPath(c, filled, dataSet.colors[index], dataSet.fillAlpha)
class CustomLineChartRender(
lineDataProvider: LineDataProvider,
chartAnimator: ChartAnimator,
port: ViewPortHandler
)
: LineChartRenderer(lineDataProvider, chartAnimator, port) {
override fun drawLinearFill(
c: Canvas?,
dataSet: ILineDataSet?,
trans: Transformer?,
bounds: XBounds?
) {
val filled = mGenerateFilledPathBuffer
val startingIndex = bounds!!.min
val endingIndex = bounds!!.range + bounds!!.min
val indexInterval = 1
var currentStartIndex = 0
var currentEndIndex = indexInterval
var iterations = 0
do {
currentStartIndex = startingIndex + iterations * indexInterval
currentEndIndex = currentStartIndex + indexInterval
currentEndIndex = if (currentEndIndex > endingIndex) endingIndex else currentEndIndex
if (currentStartIndex <= currentEndIndex) {
generateFilledPath(dataSet!!, currentStartIndex, currentEndIndex, filled)
trans!!.pathValueToPixel(filled)
val drawable = dataSet.fillDrawable
if (drawable != null) {
drawFilledPath(c, filled, drawable)
} else {
val index = startingIndex + iterations
drawFilledPath(c, filled, dataSet.colors[index], dataSet.fillAlpha)
}
}
iterations++
} while (currentStartIndex <= currentEndIndex)
}
private fun generateFilledPath(
dataSet: ILineDataSet,
startIndex: Int,
endIndex: Int,
outputPath: Path
) {
val fillMin = dataSet.fillFormatter.getFillLinePosition(dataSet, mChart)
val phaseY = mAnimator.phaseY
val isDrawSteppedEnabled = dataSet.mode == LineDataSet.Mode.STEPPED
outputPath.reset()
val entry = dataSet.getEntryForIndex(startIndex)
outputPath.moveTo(entry.x, fillMin)
outputPath.lineTo(entry.x, entry.y * phaseY)
// create a new path
var currentEntry: Entry? = null
var previousEntry = entry
for (x in startIndex + 1..endIndex) {
currentEntry = dataSet.getEntryForIndex(x)
if (isDrawSteppedEnabled) {
outputPath.lineTo(currentEntry.x, previousEntry!!.y * phaseY)
}
outputPath.lineTo(currentEntry.x, currentEntry.y * phaseY)
previousEntry = currentEntry
}
// close up
if (currentEntry != null) {
outputPath.lineTo(currentEntry.x, fillMin)
}
outputPath.close()
}
}
I am trying to show the data using LineChart, and ran into issues displaying x axis labels. I use data.start and data.end to set the x-axis bounds in my chart. So let's say API returns data for two days only (Thursday and Friday), start will be Monday and end will be Saturday.
Note:X values are ALL unix timestamps.
here's my code:
class MyChart(chart: LineChart?, val data: MyData) {
private val startValue = data.start.toEpochSecond(ZoneOffset.UTC)
init {
chart?.run {
setScaleEnabled(false)
legend.isEnabled = false
description.isEnabled = false
axisRight.run {
setDrawGridLines(true)
setDrawLabels(false)
axisMinimum = 0.1f // hide 0.0 values
}
axisLeft.run {
setDrawGridLines(true)
axisMinimum = 0.1f // hide 0.0 values
}
setupXAxis(xAxis)
Log.e("MPTEST, startValue: ", startValue.toString())
val entries = ArrayList<Entry>()
data.list?.forEachIndexed { _, data ->
val timeInSeconds = data.time.toEpochSecond(ZoneOffset.UTC)
val xIndex = (timeInSeconds - startValue).toFloat()
Log.e("MPTEST, xIndex: ", xIndex.toString())
entries.add(Entry(xIndex, data.value))
}
val data = LineData(LineDataSet(entries, ""))
setData(data)
invalidate()
}}
private fun setupXAxis(xAxis: XAxis?) {
xAxis?.run {
position = XAxis.XAxisPosition.BOTTOM
val startInSeconds = data.start.toEpochSecond(ZoneOffset.UTC)
axisMinimum = (startInSeconds - startValue).toFloat()
Log.e("MPTEST, axisMinimum: ", axisMinimum.toString())
val endInSeconds = data.end.toEpochSecond(ZoneOffset.UTC)
axisMaximum = (endInSeconds - startValue).toFloat()
Log.e("MPTEST, axisMaximum: ", axisMaximum.toString())
setCenterAxisLabels(true)
setDrawGridLines(false)
// I need to fix something here I think
setLabelCount(7, true)
valueFormatter = IndexAxisValueFormatter(data.labels)
}
} }
Here's the logcat output:
E/MPTEST, axisMinimum:: 0.0
E/MPTEST, axisMaximum:: 604799.0
E/MPTEST, startValue:: 1546732800
E/MPTEST, xIndex:: 366180.0
E/MPTEST, xIndex:: 467893.0
That's what I get - https://i.ibb.co/YD2DnhJ/Screen-Shot-2019-01-16-at-7-20-23-AM.png
(as you can see only first x axis label is displayed)
and this is what I am trying to achieve - https://i.ibb.co/M8b9pyP/Screen-Shot-2019-01-16-at-7-24-15-AM.png
I'm using mapbox, with GeoJsonSource and symbollayer. When user clicks on feature it should change a color. I handle this logic with following code and it works, but it is too slow and takes several second to change icon color.
Here I configure symbol layer, add icon changelogic for 'PROPERTY_SELECTED':
mapBoxMap?.addLayer(SymbolLayer(markerStyleLayerIdentifier, markerSourceIdentifier)
.withProperties(
PropertyFactory.iconImage(markerImage),
PropertyFactory.iconAllowOverlap(false),
PropertyFactory.iconImage(match(
get(PROPERTY_SELECTED), literal(0),
literal(markerImage),
literal(markerImageSelected)
))
))
on map click features objects are update:
override fun onMapClick(point: LatLng) {
val screenPoint = mapBoxMap?.projection?.toScreenLocation(point)
var features = mapBoxMap?.queryRenderedFeatures(screenPoint
?: return, markerStyleLayerIdentifier)
if ((features ?: return).isNotEmpty()) {
var feature = features[0]
showMarkerInfo(feature)
doAsync {
var featureList = featureCollection?.features()
var id = feature.getNumberProperty(PROPERTY_STOP_ID)
if (featureList != null) {
for (i in 0 until featureList.size) {
var fId = featureList[i].getNumberProperty(PROPERTY_STOP_ID)
if (fId == id) {
featureList[i].properties()?.addProperty(PROPERTY_SELECTED, 1)
} else {
featureList[i].properties()?.addProperty(PROPERTY_SELECTED, 0)
}
}
uiThread {
refreshSource()
}
}
}
}
}
and refresh source :
private fun refreshSource() {
var source = mapBoxMap?.getSource(markerSourceIdentifier) as GeoJsonSource?
if (source != null && featureCollection != null) {
source.setGeoJson(featureCollection)
}
}
after 'refreshSource' is called , it takes several time before icon update. In my case there are 2050 features is source. Is there any better way to implement it ? Or any way to optimise this solution ?
here is a second , faster way from github answer:
var selectedLayer = mapBoxMap?.getLayer(markerSelectedStyleLayerIdentifier) as SymbolLayer?
var id = feature.getNumberProperty(PROPERTY_STOP_ID)
var selectedExpression = any(
eq(get(PROPERTY_STOP_ID), literal(id.toString()))
)
selectedLayer?.filter = selectedExpression
you can see whole issue there
https://github.com/mapbox/mapbox-java/issues/892