Show image instead of circle on LineChart

前端 未结 3 1576
旧巷少年郎
旧巷少年郎 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<Drawable>()
      
        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:

    <item>
        <shape
            android:shape="ring"
            android:thickness="@dimen/chart_circle_stroke_thickness"
            android:useLevel="false">
            <solid android:color="#497EFF" />
        </shape>
    </item>
    

    But it can be any other.

    Enjoy.

    0 讨论(0)
  • 2021-01-17 04:43

    And finally after trying so many things, with the help of @David Rawson's suggestion and this post MPAndroidChart LineChart custom highlight drawable

    I have managed to create a custom renderer, which replaces the default circle image in chart with the provided image.

    Following is the code snippet of solution.

    class ImageLineChartRenderer extends LineChartRenderer {
    private final LineChart lineChart;
    private final Bitmap image;
    
    
    ImageLineChartRenderer(LineChart chart, ChartAnimator animator, ViewPortHandler viewPortHandler, Bitmap image) {
        super(chart, animator, viewPortHandler);
        this.lineChart = chart;
        this.image = image;
    }
    
    private float[] mCirclesBuffer = new float[2];
    
    @Override
    protected void drawCircles(Canvas c) {
        mRenderPaint.setStyle(Paint.Style.FILL);
        float phaseY = mAnimator.getPhaseY();
        mCirclesBuffer[0] = 0;
        mCirclesBuffer[1] = 0;
        List<ILineDataSet> dataSets = mChart.getLineData().getDataSets();
    
        //Draw bitmap image for every data set with size as radius * 10, and store it in scaled bitmaps array
        Bitmap[] scaledBitmaps = new Bitmap[dataSets.size()];
        float[] scaledBitmapOffsets = new float[dataSets.size()];
        for (int i = 0; i < dataSets.size(); i++) {
            float imageSize = dataSets.get(i).getCircleRadius() * 10;
            scaledBitmapOffsets[i] = imageSize / 2f;
            scaledBitmaps[i] = scaleImage((int) imageSize);
        }
    
        for (int i = 0; i < dataSets.size(); i++) {
            ILineDataSet dataSet = dataSets.get(i);
    
            if (!dataSet.isVisible() || !dataSet.isDrawCirclesEnabled() || dataSet.getEntryCount() == 0)
                continue;
    
            mCirclePaintInner.setColor(dataSet.getCircleHoleColor());
            Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
            mXBounds.set(mChart, dataSet);
    
    
            int boundsRangeCount = mXBounds.range + mXBounds.min;
            for (int j = mXBounds.min; j <= boundsRangeCount; j++) {
                Entry e = dataSet.getEntryForIndex(j);
                if (e == null) break;
                mCirclesBuffer[0] = e.getX();
                mCirclesBuffer[1] = e.getY() * phaseY;
                trans.pointValuesToPixel(mCirclesBuffer);
                if (!mViewPortHandler.isInBoundsRight(mCirclesBuffer[0]))
                    break;
                if (!mViewPortHandler.isInBoundsLeft(mCirclesBuffer[0]) || !mViewPortHandler.isInBoundsY(mCirclesBuffer[1]))
                    continue;
    
                if (scaledBitmaps[i] != null) {
                    c.drawBitmap(scaledBitmaps[i],
                            mCirclesBuffer[0] - scaledBitmapOffsets[i],
                            mCirclesBuffer[1] - scaledBitmapOffsets[i],
                            mRenderPaint);
                }
            }
        }
    
    }
    
    
    private Bitmap scaleImage(int radius) {
        return Bitmap.createScaledBitmap(image, radius, radius, false);
    }
    

    Hope this helps someone.

    0 讨论(0)
  • 2021-01-17 04:49

    You can simply create entry with drawable, and it will be drawn instead of circle on graph.

    new Entry(i, value, drawable)
    
    0 讨论(0)
提交回复
热议问题