Search code examples
androidandroidplot

How to rotate a text value on a graph


I have a graph built using androidplot. I need to see each value horizontally (rotate 90 degrees). In the screenshot I showed what I mean. How to do it? enter image description here


Solution

  • There is not a configuration option built in to do this, but it'd relatively easy to do in a custom renderer. Here's the quickstart example modified to do it:

    import android.app.Activity;
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.DashPathEffect;
    import android.graphics.Paint;
    import android.graphics.PointF;
    import android.graphics.RectF;
    import android.os.Bundle;
    import android.support.annotation.NonNull;
    
    import com.androidplot.ui.SeriesRenderer;
    import com.androidplot.util.PixelUtils;
    import com.androidplot.xy.CatmullRomInterpolator;
    import com.androidplot.xy.LineAndPointFormatter;
    import com.androidplot.xy.LineAndPointRenderer;
    import com.androidplot.xy.PointLabelFormatter;
    import com.androidplot.xy.PointLabeler;
    import com.androidplot.xy.SimpleXYSeries;
    import com.androidplot.xy.XYGraphWidget;
    import com.androidplot.xy.XYPlot;
    import com.androidplot.xy.XYSeries;
    
    import java.text.FieldPosition;
    import java.text.Format;
    import java.text.ParsePosition;
    import java.util.Arrays;
    import java.util.List;
    
    /**
     * A simple XYPlot
     */
    public class SimpleXYPlotActivity extends Activity {
    
        private XYPlot plot;
    
        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.simple_xy_plot_example);
    
            // initialize our XYPlot reference:
            plot = (XYPlot) findViewById(R.id.plot);
    
            // create a couple arrays of y-values to plot:
            final Number[] domainLabels = {1, 2, 3, 6, 7, 8, 9, 10, 13, 14};
            Number[] series1Numbers = {1, 4, 2, 8, 4, 16, 8, 32, 16, 64};
            Number[] series2Numbers = {5, 2, 10, 5, 20, 10, 40, 20, 80, 40};
    
            // turn the above arrays into XYSeries':
            // (Y_VALS_ONLY means use the element index as the x value)
            XYSeries series1 = new SimpleXYSeries(
                    Arrays.asList(series1Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Series1");
            XYSeries series2 = new SimpleXYSeries(
                    Arrays.asList(series2Numbers), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "Series2");
    
            // create formatters to use for drawing a series using LineAndPointRenderer
            // and configure them from xml:
            LineAndPointFormatter series1Format =
                    new MyLineAndPointFormatter(this, R.xml.line_point_formatter_with_labels);
    
            LineAndPointFormatter series2Format =
                    new MyLineAndPointFormatter(this, R.xml.line_point_formatter_with_labels_2);
    
            // add an "dash" effect to the series2 line:
            series2Format.getLinePaint().setPathEffect(new DashPathEffect(new float[] {
    
                    // always use DP when specifying pixel sizes, to keep things consistent across devices:
                    PixelUtils.dpToPix(20),
                    PixelUtils.dpToPix(15)}, 0));
    
            // (optional) add some smoothing to the lines:
            // see: http://androidplot.com/smooth-curves-and-androidplot/
            series1Format.setInterpolationParams(
                    new CatmullRomInterpolator.Params(10, CatmullRomInterpolator.Type.Centripetal));
    
            series2Format.setInterpolationParams(
                    new CatmullRomInterpolator.Params(10, CatmullRomInterpolator.Type.Centripetal));
    
            // add a new series' to the xyplot:
            plot.addSeries(series1, series1Format);
            plot.addSeries(series2, series2Format);
    
            plot.getGraph().getLineLabelStyle(XYGraphWidget.Edge.BOTTOM).setFormat(new Format() {
                @Override
                public StringBuffer format(Object obj, @NonNull StringBuffer toAppendTo, @NonNull FieldPosition pos) {
                    int i = Math.round(((Number) obj).floatValue());
                    return toAppendTo.append(domainLabels[i]);
                }
                @Override
                public Object parseObject(String source, @NonNull ParsePosition pos) {
                    return null;
                }
            });
        }
    
        /**
         * A LineAndPointRenderer that rotates it's point labels -90 degrees.
         */
        static class MyLineAndPointRenderer extends LineAndPointRenderer<MyLineAndPointFormatter> {
    
            public MyLineAndPointRenderer(XYPlot plot) {
                super(plot);
            }
    
            // Basically just copy the entire renderPoints implementation and add a rotation as shown below
            @Override
            protected void renderPoints(Canvas canvas, RectF plotArea, XYSeries series, int iStart, int iEnd, List<PointF> points,
                                        LineAndPointFormatter formatter) {
                if (formatter.hasVertexPaint() || formatter.hasPointLabelFormatter()) {
                    final Paint vertexPaint = formatter.hasVertexPaint() ? formatter.getVertexPaint() : null;
                    final boolean hasPointLabelFormatter = formatter.hasPointLabelFormatter();
                    final PointLabelFormatter plf = hasPointLabelFormatter ? formatter.getPointLabelFormatter() : null;
                    final PointLabeler pointLabeler = hasPointLabelFormatter ? formatter.getPointLabeler() : null;
                    for(int i = iStart; i < iEnd; i++) {
                        PointF p = points.get(i);
                        if(p != null) {
    
                            if (vertexPaint != null) {
                                canvas.drawPoint(p.x, p.y, vertexPaint);
                            }
    
                            if (pointLabeler != null) {
                                // this is where we rotate the text:
                                final int canvasState = canvas.save();
                                try {
                                    canvas.rotate(-90, p.x, p.y);
                                    canvas.drawText(pointLabeler.getLabel(series, i),
                                            p.x + plf.hOffset, p.y + plf.vOffset, plf.getTextPaint());
                                } finally {
                                    canvas.restoreToCount(canvasState);
                                }
                            }
                        }
                    }
                }
            }
    
        }
    
        static class MyLineAndPointFormatter extends LineAndPointFormatter {
    
            // if you dont use configurator you can omit this constructor.  this example uses it
            // tho so here it is.
            public MyLineAndPointFormatter(Context context, int xmlCfgId) {
                super(context, xmlCfgId);
            }
    
            @Override
            public Class<? extends SeriesRenderer> getRendererClass() {
                return MyLineAndPointRenderer.class;
            }
    
            @Override
            public SeriesRenderer doGetRendererInstance(XYPlot plot) {
                return new MyLineAndPointRenderer(plot);
            }
    
        }
    }
    

    The important stuff is at the bottom in MyLineAndPointRenderer. Basically you're just extending LineAndPointRenderer and overriding renderPoints(...) to rotate the canvas -90 degrees before drawing the text labels, and then restore the canvas immediately after.