Show image instead of circle on LineChart

前端 未结 3 1575
旧巷少年郎
旧巷少年郎 2021-01-17 04:18

I have created a LineChart using the library MPAndroidChart and everything works great.

Now what I want to do is show a drawable (image) instead of the

3条回答
  •  暖寄归人
    2021-01-17 04:39

    I also came across this question, but had a bit more specific requirements:

    1. Draw the circles only at specific points on the chart.
    2. Have the flexibility to use different drawables in different cases.

    Finally, I managed to achieve this:

    Where each circle is actually a regular drawable and can be replaced with anything else.

    Solved it in a next way:

    1.Create an Entry subclass which takes a drawable as a parameter.

    /**
     * Represents an [Entry] which is able to use drawables (including different drawables for different points) instead of the circle.
     * For the points where you don't need points use a regular [Entry].
     */
    
    class DrawableCircleEntry @JvmOverloads constructor(
            @DrawableRes val circleDrawableRes: Int,
            x: Float,
            y: Float,
            icon: Drawable? = null,
            data: Any? = null
    ) : Entry(x, y, icon, data) 
    

    2.Create a custom rendered, which

    • draws the drawable instead of the circle in case entry is a type of DrawableCircleEntry.

    • Doesn't draw the circle in case try is a regular Entry.

      internal class LineChartCustomCirclesRenderer(private val context: Context, lineChart: LineChart
        ) : LineChartRenderer(lineChart, lineChart.animator, lineChart.viewPortHandler) {
      
        // Contains (left, top) coordinates of the next circle which has to be drawn
        private val circleCoordinates = FloatArray(2)
        // Cached drawables
        private val drawablesCache = SparseArray()
      
        override fun drawCircles(canvas: Canvas) {
            val phaseY = mAnimator.phaseY
            circleCoordinates[0] = 0f
            circleCoordinates[1] = 0f
            val dataSets = mChart.lineData.dataSets
      
            dataSets.forEach { dataSet ->
                if (!dataSet.isVisible || !dataSet.isDrawCirclesEnabled || dataSet.entryCount == 0)
                    return@forEach
      
                val transformer = mChart.getTransformer(dataSet.axisDependency)
                mXBounds[mChart] = dataSet
                val boundsRangeCount = mXBounds.range + mXBounds.min
      
                for (i in mXBounds.min..boundsRangeCount) {
                    // don't do anything in case entry is not type of DrawableCircleEntry
                    val entry = dataSet.getEntryForIndex(i) as? DrawableCircleEntry
                            ?: continue
                    circleCoordinates[0] = entry.x
                    circleCoordinates[1] = entry.y * phaseY
      
                    transformer.pointValuesToPixel(circleCoordinates)
      
                    if (!mViewPortHandler.isInBoundsRight(circleCoordinates[0])) break
                    if (!mViewPortHandler.isInBoundsLeft(circleCoordinates[0]) || !mViewPortHandler.isInBoundsY(circleCoordinates[1])) continue
      
                    // Drawable radius is taken as `dataSet.circleRadius`
                    val radius = dataSet.circleRadius
      
                    // Retrieve the drawable, center it and draw on canvas
                    getDrawable(entry.circleDrawableRes)?.run {
                        setBounds((circleCoordinates[0] - radius).roundToInt(), (circleCoordinates[1] - radius).roundToInt(),
                                (circleCoordinates[0] + radius).roundToInt(), (circleCoordinates[1] + radius).roundToInt())
                        draw(canvas)
                    }
                }
            }
        }
      
      
        private fun getDrawable(@DrawableRes drawableRes: Int): Drawable? {
            drawablesCache[drawableRes]?.let {
                return it
            }
            return ContextCompat.getDrawable(context, drawableRes)
                    .also { drawablesCache.append(drawableRes, it) }
        }
      

      }

    3.Enable circles for the dataset and set the needed radius. The drawable's size will be radius*2

     dataSet.setDrawCircles(true)
     dataSet.circleRadius = 3f
    

    4.When constructing Entries, create either normal Entry for a point where you don't need to draw a circle and a DrawableCircleEntry when you need one. For instance,

        ...
        val entry = when {
            someCondition ->  DrawableCircleEntry(R.drawable.your_awesome_drawable, floatIndex, floatValue)
            anotherCondition ->  DrawableCircleEntry(R.drawable.your_another_drawable, floatIndex, floatValue)
            else -> Entry(floatIndex, floatValue)
        }
        ...
    
    1. One of the drawables in my case looks like:

    
        
            
        
    
    

    But it can be any other.

    Enjoy.

提交回复
热议问题