MPChart won't display hourly weather data - android

I'm creating a Weather app and i'm trying to display an MPChart which will use the hourly temps as Y points & the Hour (in "HH" format) as the XAxisLabels/ X points.
I'm creating the LineData for the chart in my Fragment's viewModel and expose it to the Fragment via liveData. I also create the XAxisLabels for the chart on my Fragment. The LineData seem to contain the right entries (viewModel: [Entry, x: 0.0 y: 39.07, Entry, x: 1.0 y: 38.68, Entry, x: 2.0 y: 38.16, Entry, x: 3.0 y: 37.29, Entry, x: 4.0 y: 36.2, Entry, x: 5.0 y: 35.22,........]), but the chart isn't even displaying any of the data. What am I missing here?
Here's what is rendered:
Here's the code:
ViewModel.kt
class WeatherViewModel(
private val forecastRepository: ForecastRepository,
private val weatherUnitConverter: WeatherUnitConverter,
context: Context
) : ViewModel() {
val hourlyWeatherEntries = forecastRepository.getHourlyWeather()
val hourlyChartData = hourlyWeatherEntries.switchMap { hourlyData ->
liveData {
Log.d("ViewModel hourly data", "$hourlyData")
val task = viewModelScope.async(Dispatchers.Default) {
createChartData(hourlyData)
}
emit(task.await())
}
}
/**
* Creates the line chart's data and returns them.
* #return The line chart's data (x,y) value pairs
*/
private fun createChartData(hourlyWeather: List<HourWeatherEntry>?): LineData {
if (hourlyWeather == null) return LineData()
Log.d("ViewModel class", hourlyWeather.toString())
val unitSystemValue = preferences.getString(UNIT_SYSTEM_KEY, "si")!!
val values = arrayListOf<Entry>()
for (i in hourlyWeather.indices) { // init data points
// format the temperature appropriately based on the unit system selected
val hourTempFormatted = when (unitSystemValue) {
UnitSystem.SI.name.toLowerCase(Locale.ROOT) -> hourlyWeather[i].temperature
UnitSystem.US.name.toLowerCase(Locale.ROOT) -> weatherUnitConverter.convertToFahrenheit(
hourlyWeather[i].temperature
)
else -> hourlyWeather[i].temperature
}
// Create the data point
values.add(
Entry(
i.toFloat(),
hourTempFormatted.toFloat(),
appContext.resources.getDrawable(
determineSummaryIcon(hourlyWeather[i].icon),
null
)
)
)
}
Log.d("MainFragment viewModel", "$values")
// create a data set and customize it
val lineDataSet = LineDataSet(values, "")
val color = appContext.resources.getColor(R.color.black, null)
val offset = MPPointF.getInstance()
offset.y = -35f
lineDataSet.apply {
valueFormatter = YValueFormatter()
setDrawValues(true)
fillDrawable = appContext.resources.getDrawable(R.drawable.gradient_night_chart, null)
setDrawFilled(true)
setDrawIcons(true)
setCircleColor(color)
mode = LineDataSet.Mode.HORIZONTAL_BEZIER
this.color = color // line color
iconsOffset = offset
lineWidth = 3f
valueTextSize = 9f
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
valueTypeface = appContext.resources.getFont(R.font.work_sans_medium)
}
}
// create a LineData object using our LineDataSet
val data = LineData(lineDataSet)
data.apply {
setValueTextColor(R.color.colorPrimary)
setValueTextSize(15f)
}
return data
}
}
MainFragment.kt
class MainFragment : Fragment() {
// Lazy inject the view model
private val viewModel: WeatherViewModel by viewModel()
private lateinit var hourlyChart: LineChart
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setUpChart()
lifecycleScope.launch {
// Shows a lottie animation while the data is being loaded
//scrollView.visibility = View.GONE
//lottieAnimView.visibility = View.VISIBLE
bindUIAsync().await()
// Stops the animation and reveals the layout with the data loaded
//scrollView.visibility = View.VISIBLE
//lottieAnimView.visibility = View.GONE
}
}
#SuppressLint("SimpleDateFormat")
private fun bindUIAsync() = lifecycleScope.async(Dispatchers.Main) {
// fetch hourly chart line data
val hourlyChartData = viewModel.hourlyChartData
hourlyChartData.observe(viewLifecycleOwner, Observer { lineData ->
if(lineData == null) {
Log.d("HOURLY CHART DATA", "Line data = null")
return#Observer
}
hourlyChart.data = lineData
hourlyChart.invalidate()
})
// fetch hourly weather
val hourlyWeather = viewModel.hourlyWeatherEntries//viewModel.hourlyWeatherEntries
// Observe the hourly weather live data
hourlyWeather.observe(viewLifecycleOwner, Observer { hourly ->
if (hourly == null) return#Observer
val xAxisLabels = arrayListOf<String>()
val sdf = SimpleDateFormat("HH")
for (i in hourly.indices) {
val formattedLabel = sdf.format(Date(hourly[i].time * 1000))
xAxisLabels.add(formattedLabel)
}
setChartAxisLabels(xAxisLabels)
Log.d(TAG, "label count = ${hourlyChart.xAxis.labelCount.toString()}")
})
return#async true
}
private fun setChartAxisLabels(labels: ArrayList<String>) {
// Populate the X axis with the hour labels
hourlyChart.xAxis.valueFormatter = IndexAxisValueFormatter(labels)
}
/**
* Sets up the chart with the appropriate
* customizations.
*/
private fun setUpChart() {
hourlyChart.apply {
description.isEnabled = false
setNoDataText("Data is loading...")
// enable touch gestures
setTouchEnabled(true)
dragDecelerationFrictionCoef = 0.9f
// enable dragging
isDragEnabled = true
isHighlightPerDragEnabled = true
setDrawGridBackground(false)
axisRight.setDrawLabels(false)
axisLeft.setDrawLabels(false)
axisLeft.setDrawGridLines(false)
// disable zoom functionality
setScaleEnabled(false)
setPinchZoom(false)
isDoubleTapToZoomEnabled = false
// disable the chart's legend
legend.isEnabled = false
// append extra offsets to the chart's auto-calculated ones
setExtraOffsets(0f, 0f, 0f, 10f)
data = LineData()
data.isHighlightEnabled = false
setVisibleXRangeMaximum(6f)
setBackgroundColor(resources.getColor(R.color.bright_White, null))
}
// X Axis setup
hourlyChart.xAxis.apply {
position = XAxis.XAxisPosition.BOTTOM
textSize = 14f
setDrawLabels(true)
setDrawAxisLine(false)
setDrawGridLines(false)
isEnabled = true
granularity = 1f // one hour
spaceMax = 0.2f // add padding start
spaceMin = 0.2f // add padding end
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
typeface = resources.getFont(R.font.work_sans)
}
textColor = resources.getColor(R.color.black, null)
}
// Left Y axis setup
hourlyChart.axisLeft.apply {
setDrawLabels(true)
setDrawGridLines(false)
setPosition(YAxis.YAxisLabelPosition.OUTSIDE_CHART)
// temperature values range (higher than probable temps in order to scale down the chart)
axisMinimum = 0f
axisMaximum = when (getUnitSystemValue()) {
UnitSystem.SI.name.toLowerCase(Locale.ROOT) -> 50f
UnitSystem.US.name.toLowerCase(Locale.ROOT) -> 150f
else -> 50f
}
}
// Right Y axis setup
hourlyChart.axisRight.apply {
setDrawGridLines(false)
isEnabled = false
}
}
}

Related

I have a line chart that I want to use to show temperature, but I want to show icons and time in x axis

what I want to achieve
what I have
I want to show icons on the xAxis above time but line chart takes icon and places them on the value where temperature is shown. I have searched a lot but could not find the answer. Any help would be appreciated. I have tried a lot of things but all in vein.
private fun setTempChart(hour: ArrayList<Hour>, id: String) {
val entries: MutableList<Entry> = ArrayList()
for (i in hour.indices) {
val code = hour[i].condition.code
val icon =
if (hour[i].is_day == 1) requireActivity().setIconDay(code) else requireActivity().setIconNight(
code
)
entries.add(Entry(i.toFloat(), sharedPreference.temp?.let {
hour[i].temp_c.setCurrentTemperature(
it
).toFloat()
}!!))
}
val dataSet = LineDataSet(entries, "")
dataSet.apply {
lineWidth = 0f
setDrawCircles(false)
setDrawCircleHole(false)
isHighlightEnabled = false
valueTextColor = Color.WHITE
setColors(Color.WHITE)
valueTextSize = 12f
mode = LineDataSet.Mode.CUBIC_BEZIER
setDrawFilled(true)
fillColor = Color.WHITE
valueTypeface = typeface
isDrawIconsEnabled
setDrawIcons(true)
valueFormatter = object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return String.format(Locale.getDefault(), "%.0f", value)
}
}
}
val lineData = LineData(dataSet)
chart.apply {
description.isEnabled = false
axisLeft.setDrawLabels(false)
axisRight.setDrawLabels(false)
legend.isEnabled = false
axisLeft.setDrawGridLines(false)
axisRight.setDrawGridLines(false)
axisLeft.setDrawAxisLine(false)
axisRight.setDrawAxisLine(false)
setScaleEnabled(false)
data = lineData
setVisibleXRange(8f, 8f)
animateY(1000)
xAxis.apply {
setDrawAxisLine(false)
textColor = Color.WHITE
setDrawGridLines(false)
setDrawLabels(true)
position = XAxis.XAxisPosition.BOTTOM
textSize = 12f
valueFormatter = MyAxisFormatter(hour, id)
isGranularityEnabled = true
granularity = 1f
labelCount = entries.size
}
}
}
I am using MPAndroidChart library
There isn't a nice built-in way to draw icons like that, but you can do it by making a custom extension of the LineChartRenderer and overriding drawExtras. Then you can get your icons from R.drawable.X and draw them on the canvas wherever you want. There is some work to figure out where to put them to line up with the data points, but you can copy the logic from drawCircles to find that.
Example Custom Renderer
inner class MyRenderer(private val context: Context,
private val iconY: Float,
private val iconSizeDp: Float,
chart: LineDataProvider,
animator: ChartAnimator,
viewPortHandler: ViewPortHandler)
: LineChartRenderer(chart, animator, viewPortHandler) {
private var buffer: FloatArray = listOf(0f,0f).toFloatArray()
override fun drawExtras(c: Canvas) {
super.drawExtras(c)
val iconSizePx = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
iconSizeDp,
resources.displayMetrics
)
// get the icons you want to draw
val cloudy = ContextCompat.getDrawable(context, R.drawable.cloudy)
val sunny = ContextCompat.getDrawable(context, R.drawable.sunny)
if( cloudy == null || sunny == null ) {
throw RuntimeException("Missing drawables")
}
// Determine icon width in pixels
val w = iconSizePx
val h = iconSizePx
val dataSets = mChart.lineData.dataSets
val phaseY = mAnimator.phaseY
for(dataSet in dataSets) {
mXBounds.set(mChart, dataSet)
val boundsRange = mXBounds.range + mXBounds.min
val transformer = mChart.getTransformer(dataSet.axisDependency)
for(j in mXBounds.min .. boundsRange) {
val e = dataSet.getEntryForIndex(j) ?: break
buffer[0] = e.x
buffer[1] = iconY * phaseY
transformer.pointValuesToPixel(buffer)
if( !mViewPortHandler.isInBoundsRight(buffer[0])) {
break
}
if( !mViewPortHandler.isInBoundsLeft(buffer[0]) ||
!mViewPortHandler.isInBoundsY(buffer[1])) {
continue
}
// Draw the icon centered under the data point, but at a fixed
// vertical position. Here the icon "sits on top" of the
// specified iconY value
val left = (buffer[0]-w/2).roundToInt()
val right = (buffer[0]+w/2).roundToInt()
val top = (buffer[1]-h).roundToInt()
val bottom = (buffer[1]).roundToInt()
// Alternately, use this to center the icon at the
// "iconY" value
//val top = (buffer[1]-h/2).roundToInt()
//val bottom = (buffer[1]+h/2).roundToInt()
// Use whatever logic you want to select which icon
// to use at each position
val icon = if( e.y > 68f ) {
sunny
}
else {
cloudy
}
icon.setBounds(left, top, right, bottom)
icon.draw(c)
}
}
}
}
Using the Custom Renderer
val iconY = 41f
val iconSizeDp = 40f
chart.renderer = MyRenderer(this, iconY, iconSizeDp,
chart, chart.animator, chart.viewPortHandler)
(other formatting)
val chart = findViewById<LineChart>(R.id.chart)
chart.axisRight.isEnabled = false
val yAx = chart.axisLeft
yAx.setDrawLabels(false)
yAx.setDrawGridLines(false)
yAx.setDrawAxisLine(false)
yAx.axisMinimum = 40f
yAx.axisMaximum = 80f
val xAx = chart.xAxis
xAx.setDrawLabels(false)
xAx.position = XAxis.XAxisPosition.BOTTOM
xAx.setDrawGridLines(false)
xAx.setDrawAxisLine(false)
xAx.axisMinimum = 0.5f
xAx.axisMaximum = 5.5f
xAx.granularity = 0.5f
val x = listOf(0,1,2,3,4,5,6)
val y = listOf(60,65,66,70,65,50,55)
val e = x.zip(y).map { Entry(it.first.toFloat(), it.second.toFloat())}
val line = LineDataSet(e, "temp")
line.setDrawValues(true)
line.setDrawCircles(false)
line.circleRadius = 20f // makes the text offset up
line.valueTextSize = 20f
line.color = Color.BLACK
line.lineWidth = 2f
line.setDrawFilled(true)
line.fillColor = Color.BLACK
line.fillAlpha = 50
line.mode = LineDataSet.Mode.CUBIC_BEZIER
line.valueFormatter = object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return "%.0f F".format(value)
}
}
chart.data = LineData(line)
chart.description.isEnabled = false
chart.legend.isEnabled = false
Gives the desired effect

How to set xAxis display certain value independent from entry using mpchart

For example, I have a data set like following:
val dataSetX = {101, 205, 210, 445, 505}
val dataSetY = {50, 100, 150, 200, 250}
The labels on the xAxis of the chart will be 101, 205, 295, 445, 505.
But I want them to be 100,200,300,400,500, without changing the data.
What do I do?
To be able to render custom labels independent from the Data Entry List you need to implement a custom XAxisRenderer and override the function fun drawLabels(c: Canvas?, pos: Float, anchor: MPPointF?) which is called when the labels are ready to be drawn on the Canvas. The default implementation uses the mXAxis.mEntryCount and mXAxis.mEntries to render the labels in the xAxis which are calculated internally by the library. What you can do is to modify the superclass implementation by overriding it without calling the super.drawLabels(c, pos, anchor) method and make the adjustments needed to handle your custom labels. Below i will describe how you can achieve this.
1.) First declare your CustomXAxisRenderer which extends from XAxisRenderer like the below example:
class CustomXAxisRenderer(viewPortHandler: ViewPortHandler?, xAxis: XAxis?, trans: Transformer?, renderXArray : Array<Float>, formatter: ValueFormatter)
: XAxisRenderer(viewPortHandler, xAxis, trans) {
private val xArray : Array<Float>
private val xValueFormatter: ValueFormatter
init {
xArray = renderXArray
xValueFormatter = formatter
}
override fun drawLabels(c: Canvas?, pos: Float, anchor: MPPointF?) {
//don't call the super class to draw the default x points
//super.drawLabels(c, pos, anchor)
val labelRotationAngleDegrees = mXAxis.labelRotationAngle
val centeringEnabled = false//mXAxis.isCenterAxisLabelsEnabled
val positions = FloatArray(xArray.size * 2)
for(i in positions.indices step 2){
// only fill x values
if (centeringEnabled) {
positions[i.toInt()] = mXAxis.mCenteredEntries[(i / 2).toInt()]
} else {
positions[i.toInt()] = xArray[(i / 2).toInt()]
}
}
mTrans.pointValuesToPixel(positions)
for(i in positions.indices step 2){
var x = positions[i]
if (mViewPortHandler.isInBoundsX(x)) {
val label = xValueFormatter.getAxisLabel(xArray[i / 2], mXAxis)
if (mXAxis.isAvoidFirstLastClippingEnabled) {
// avoid clipping of the last
if (i / 2 == xArray.size - 1 && xArray.size > 1) {
val width = Utils.calcTextWidth(mAxisLabelPaint, label).toFloat()
if (width > mViewPortHandler.offsetRight() * 2 && x + width > mViewPortHandler.chartWidth)
x -= width / 2
// avoid clipping of the first
} else if (i == 0) {
val width = Utils.calcTextWidth(mAxisLabelPaint, label).toFloat()
x += width / 2
}
}
drawLabel(c, label, x, pos, anchor, labelRotationAngleDegrees)
}
}
}
}
2.)Then you can use it like the below:
//prepare an Array of the x points to render independent of the x points in the Data Entry List
//below values must be between AxisMinimum and AxisMaximum
val renderXArray = arrayOf<Float>(0f, 150f, 250f, 350f, 400f, 433f, 505f)
//and set a custom XAxisRenderer
chart.setXAxisRenderer(CustomXAxisRenderer(chart.getViewPortHandler(), chart.getXAxis(), chart.getTransformer(YAxis.AxisDependency.LEFT), renderXArray, object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return value.toInt().toString()
}
}))
In case you need to return a specific Label-String based on the value you can use the ValueFormatter like the below:
chart.setXAxisRenderer(CustomXAxisRenderer(chart.getViewPortHandler(), chart.getXAxis(), chart.getTransformer(YAxis.AxisDependency.LEFT), renderXArray, object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return when (value) {
0f -> "A"
150f -> "B"
250f -> "C"
350f -> "D"
400f -> "E"
433f -> "F"
505f -> "G"
else -> ""
}
}
}))
Full Example:
chart = findViewById<LineChart>(R.id.chart)
//set xAxis properties
val xAxis = chart.xAxis
xAxis.setDrawLabels(true)
xAxis.setDrawAxisLine(true)
xAxis.setDrawGridLines(true)
xAxis.position = XAxis.XAxisPosition.BOTTOM
xAxis.axisLineWidth = 1.5f
//set xAxis Min/Max and Granularity
xAxis.setAxisMinimum(0f)
xAxis.setAxisMaximum(505f)
xAxis.setGranularity(1f)
xAxis.setGranularityEnabled(true)
//set axisLeft properties
val axisLeft = chart.axisLeft
axisLeft.setDrawLabels(true)
axisLeft.setDrawGridLines(true)
axisLeft.setDrawZeroLine(true)
axisLeft.axisLineWidth = 1.5f
axisLeft.setDrawTopYLabelEntry(true)
//set axisRight properties
val axisRight = chart.axisRight
axisRight.setDrawLabels(false)
axisRight.setDrawGridLines(false)
axisRight.setDrawZeroLine(false)
axisRight.setDrawAxisLine(false)
//prepare the Entry points
val values: ArrayList<Entry> = ArrayList()
values.add(Entry(101f, 50f))
values.add(Entry(205f, 100f))
values.add(Entry(210f, 150f))
values.add(Entry(445f, 200f))
values.add(Entry(505f, 250f))
//set the LineDataSet
val set1 = LineDataSet(values, "")
set1.setDrawCircles(true);
set1.setDrawValues(true);
//prepare LineData and set them to LineChart
val dataSets: ArrayList<ILineDataSet> = ArrayList()
dataSets.add(set1)
val data = LineData(dataSets)
chart.setData(data)
chart.getDescription().setEnabled(false);
chart.legend.isEnabled = false
//prepare an Array of the x points to render independent of the x points in the Data Entry List
//below values must be between AxisMinimum and AxisMaximum
val renderXArray = arrayOf<Float>(0f, 150f, 250f, 350f, 400f, 433f, 505f)
//and set a custom XAxisRenderer
chart.setXAxisRenderer(CustomXAxisRenderer(chart.getViewPortHandler(), chart.getXAxis(), chart.getTransformer(YAxis.AxisDependency.LEFT), renderXArray, object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return value.toInt().toString()
}
}))
Value Result:
Label Result:
Note: This was tested with version 'com.github.PhilJay:MPAndroidChart:v3.1.0'

MPAndroidChart LineChart labels on axis disappear when fragment is resumed

I have a problem updating the data in a chart. When I change the settings and come back to this fragment or when I go out of the app and come back, the chart apparently changes its size and hide the labels in its axis. However, if I navigate through the app and come back, the chart is displaying well.
Screenshot of correct display
Screenshot of axis labels disappearing
My context is the following:
I have a fragment where I observe the viewmodel in onViewCreated. When the data is successfully retrieved, I update the chart. I have tried also to make a flag to make sure that the call to set up the chart is only called once, but that doesn't work either. So the problem is not related to how many times this call is made.
viewModel.forecast.observe(viewLifecycleOwner, Observer { resource ->
if (resource != null && resource.status.isSuccessful()){
ChartUtilities.setUpChart(
viewModel.forecastOfNextHours().subList(0, 9),
binding.lineChart,
"Temperature of the next 24 hours",
isMetric
)
}
In ChartUtilities.kt:
object ChartUtilities {
fun setUpChart(list: List<ForecastListItem>, lineChart: LineChart, label: String, isMetric: Boolean){
//lineChart.clear()
val (values, yValues, xAxisValues) = fillData(list, isMetric)
val data = configureDataSet(values, label)
customSettings(lineChart, data, xAxisValues, yValues)
//lineChart.notifyDataSetChanged()
}
private fun configureDataSet(
values: ArrayList<Entry>,
label: String
): LineData {
val set1 = LineDataSet(values, label)
set1.axisDependency = YAxis.AxisDependency.LEFT
set1.color = ColorTemplate.getHoloBlue()
set1.valueTextColor = ColorTemplate.getHoloBlue()
set1.lineWidth = 4.0f
set1.setDrawCircles(false)
set1.setDrawValues(false)
set1.fillAlpha = 65
set1.fillColor = Color.RED
set1.highLightColor = Color.rgb(244, 117, 117)
set1.setDrawCircleHole(false)
set1.mode = LineDataSet.Mode.HORIZONTAL_BEZIER
// create a data object with the data sets
val data = LineData(set1)
data.setValueTextColor(Color.WHITE)
data.setValueTextSize(9f)
return data
}
private fun fillData(list: List<ForecastListItem>, isMetric: Boolean): Triple<ArrayList<Entry>, ArrayList<Double>, ArrayList<String>> {
val values = ArrayList<Entry>()
val yValues = ArrayList<Double>()
val xAxisValues = arrayListOf<String>()
list.forEachIndexed { index, forecastItem ->
xAxisValues.add(forecastItem.getHourOfDay())
var temperature = forecastItem.getTemperature()!!
if (!isMetric)
temperature = WeatherUtils.celsiusToFahrenheit(temperature)
yValues.add(temperature)
values.add(Entry(index.toFloat(), temperature.toFloat()))
}
return Triple(values, yValues, xAxisValues)
}
private fun customSettings(chart: LineChart, data: LineData, xAxisValues: ArrayList<String>, yAxisValues: ArrayList<Double>){
chart.data = data
customChart(chart)
customLegend(chart)
customXAxis(chart, xAxisValues)
customYAxis(chart, yAxisValues)
val rightAxis = chart.axisRight
rightAxis.isEnabled = false
}
private fun customChart(chart: LineChart) {
chart.description.isEnabled = false
chart.setTouchEnabled(false)
//chart.dragDecelerationFrictionCoef = 0.9f
// enable scaling and dragging
chart.isDragEnabled = false
chart.setScaleEnabled(false)
chart.setDrawGridBackground(false)
chart.isHighlightPerDragEnabled = true
// set an alternative background color
chart.setBackgroundResource(R.color.colorPrimaryDark)
chart.setViewPortOffsets(0f, 0f, 0f, 0f)
}
private fun customLegend(chart: LineChart) {
val l = chart.legend
l.isEnabled = true
l.verticalAlignment = Legend.LegendVerticalAlignment.TOP
l.textColor = Color.WHITE
}
private fun customXAxis(
chart: LineChart,
xAxisValues: ArrayList<String>
) {
val xAxis = chart.xAxis
xAxis.position = XAxis.XAxisPosition.BOTTOM
xAxis.textSize = 12f
xAxis.setDrawAxisLine(true)
xAxis.setDrawGridLines(false)
xAxis.textColor = Color.WHITE
//xAxis.setCenterAxisLabels(true)
xAxis.setAvoidFirstLastClipping(false)
xAxis.granularity = 2f // three hours
xAxis.axisMaximum = xAxisValues.size.toFloat()
xAxis.labelCount = xAxisValues.size
xAxis.valueFormatter = IndexAxisValueFormatter(xAxisValues)
xAxis.isEnabled = true
}
private fun customYAxis(
chart: LineChart,
yAxisValues: ArrayList<Double>
) {
val leftAxis = chart.axisLeft
leftAxis.setPosition(YAxis.YAxisLabelPosition.OUTSIDE_CHART)
leftAxis.textColor = Color.WHITE
leftAxis.setDrawGridLines(true)
leftAxis.isGranularityEnabled = true
leftAxis.axisMinimum = yAxisValues.min()?.minus(10)?.toFloat()!!
leftAxis.axisMaximum = yAxisValues.max()?.plus(10)?.toFloat()!!
leftAxis.yOffset = -9f
leftAxis.isEnabled = true
}
}
I realise this is a very late answer, but I had exactly the same issue and stumbled upon your question. I'd intermittently see the axes disappear when scrolling between different charts (I had them in a RecyclerView).
I solved it by deleting the following line, which I see you also have in your code:
chart.setViewPortOffsets(0, 0, 0, 0);
The javadoc for that method says that we should avoid manually setting it unless we know what we're doing (which I don't!).
I also found that increasing the values to the following worked for me:
chart.setViewPortOffsets(60, 0, 50, 60);

Set the negative values to the left of the bars in horizontal bar chart

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

mpandroidchart - can't get x axis labels to show up correctly

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

Categories

Resources