今天來寫關於Android中MPChart圖表框架一些功能-MakerView以及X軸標籤的部分

我之前有寫過關於在Android中圖表的設置(-‿◦)

->碼農日常-『Android studio』使用MPChart第三方資料庫在Android裝置上繪製圖表

這那文章中,最後完成的功能如下

before

 

在打圈的部分,當時的圖表僅能做到顯示一條線的資訊

那本篇文章中,將要介紹如何寫出所有資訊,如下圖

 

after

 

以及還有另一個要注意的,就是我在標籤中的更動

after

 

Okay,以上幾個就是今天要介紹的內容

來看一下功能吧!

 

以及Github

->https://github.com/thumbb13555/LineChartExample

此外,本次的內容將沿用之前寫過的這篇

碼農日常-『Android studio』使用MPChart第三方資料庫在Android裝置上繪製圖表

文章去修改,如果對於圖表設置不熟悉的朋友建議可以先參考這篇的內容

如果需要在Github看舊程式的朋友,可以參考這篇

->碼農日常-『Android studio』Room資料庫建立與操作(Kotlin)的第四大段,有圖文教學喔

那~開始吧:D

 


 

1. 這篇到底新增了哪些內容?

截圖 2021-05-09 下午2.18.10

 

Screenshot_1620541405

其分別對應圖中的三個顏色

 

關於兩行的X軸可能有些人會有疑惑...為何要特別寫一篇?

其緣由是來自於stack overflow中的一個問題

->In MPAndroidChart Library, How to wrap X Axis Labels to two lines when long?

 

在MPChart這個圖表UI框架中..恩~也不能算是缺點啦(´・д・`)

就是有一點..怎麼說..比較沒這麼完善的地方?

畢竟人家都已經幫我們些出那麼完整的UI框架了,其實也不要東嫌西嫌啦XD

但是很明顯,在沒有更動的情況下,就算再字串中放入換行符號"\n",X軸的標籤頁不會執行換行

於是今天就是來解決這個問題的

 


 

2. MyMarkerView.java撰寫

 

首先來修改MyMarkerView.java的部分

 

此處原先的程式如下

public class MyMarkerView extends MarkerView {

    private final TextView tvContent;

    public MyMarkerView(Context context, int layoutResource) {
        super(context, layoutResource);

        tvContent = findViewById(R.id.tvContent);
    }

    // runs every time the MarkerView is redrawn, can be used to update the
    // content (user-interface)
    @Override
    public void refreshContent(Entry e, Highlight highlight) {

        if (e instanceof CandleEntry) {

            CandleEntry ce = (CandleEntry) e;

            tvContent.setText(Utils.formatNumber(ce.getHigh(), 1, true));
        } else {

            tvContent.setText(Utils.formatNumber(e.getY(), 1, true));
        }

        super.refreshContent(e, highlight);
    }

    @Override
    public MPPointF getOffset() {
        return new MPPointF(-(getWidth() / 2), -getHeight());
    }
}

 

那我們把它改成這樣

public class MyMarkerView extends MarkerView {

    private TextView tvValue, tvTitle;
    private ArrayList<String> customxLable;
    private LineChart chart;

    public MyMarkerView(Context context, int layoutResource, ArrayList<String> customxLable,LineChart chart) {
        super(context, layoutResource);
        this.customxLable = customxLable;
        this.chart = chart;
        tvTitle = findViewById(R.id.textView_Title);
        tvValue = findViewById(R.id.textView_Value);
    }

    @Override
    public void refreshContent(Entry e, Highlight highlight) {
        String label = customxLable.get(Math.round(e.getX())).replace("資料\n","");
        tvTitle.setText(label);
        int line =  chart.getLineData().getDataSets().size();

        StringBuffer value = new StringBuffer();
        for (int i = 0; i <line ; i++) {
            ILineDataSet set = chart.getLineData().getDataSets().get(i);
            String s = set.getLabel()+": "+set.getEntryForIndex(Math.round(e.getX())).getY();
            if (i<line-1) value.append(s+"\n");
            else value.append(s);
        }
        tvValue.setText(value);

        super.refreshContent(e, highlight);
    }

    @Override
    public MPPointF getOffsetForDrawingAtPoint(float posX, float posY) {
        if (posX < 300) return new MPPointF(-getWidth() / 2f+100f, -getHeight() - 10f);
        else return new MPPointF(-getWidth() / 2f-100f, -getHeight()-10f);
    }
}

 

重點有幾個,首先看建構子的部分

 

private TextView tvValue, tvTitle;
private ArrayList<String> customxLable;
private LineChart chart;

public MyMarkerView(Context context, int layoutResource, ArrayList<String> customxLable,LineChart chart) {
    super(context, layoutResource);
    this.customxLable = customxLable;
    this.chart = chart;
    tvTitle = findViewById(R.id.textView_Title);
    tvValue = findViewById(R.id.textView_Value);
}

 

除了原先的Context跟layoutResoure的部分、這次多輸入了chart本身以及當初輸入Xlabel的陣列

然後layout的部分也做了一些修改

 

Custom_marker_view.xml

截圖 2021-05-09 下午2.47.27

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="100dp"
    android:layout_height="wrap_content"
    android:background="@drawable/round_layout"
    android:backgroundTint="@android:color/darker_gray"
    android:padding="5dp"
    >
    <TextView
        android:id="@+id/textView_Title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="TextView"
        android:textAlignment="center"
        android:textAllCaps="false"
        android:textColor="@android:color/white"
        android:textSize="14sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />
    <TextView
        android:id="@+id/textView_Value"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="2dp"
        android:ellipsize="end"
        android:gravity="center"
        android:text="數值"
        android:textColor="@android:color/white"
        android:textSize="14sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView_Title" />
</androidx.constraintlayout.widget.ConstraintLayout>

再來是顯示內容的部分..

@Override
public void refreshContent(Entry e, Highlight highlight) {
    String label = customxLable.get(Math.round(e.getX())).replace("資料\n","");
    tvTitle.setText(label);
    int line =  chart.getLineData().getDataSets().size();

    StringBuffer value = new StringBuffer();
    for (int i = 0; i <line ; i++) {
        ILineDataSet set = chart.getLineData().getDataSets().get(i);
        String s = set.getLabel()+": "+set.getEntryForIndex(Math.round(e.getX())).getY();
        if (i<line-1) value.append(s+"\n");
        else value.append(s);
    }
    tvValue.setText(value);

    super.refreshContent(e, highlight);
}

比較值得注意的是這邊

ILineDataSet set = chart.getLineData().getDataSets().get(i);

其實ILineDataSet就是指每一條"線"的類別

而每一條"線"也很貼心,他都會順便給你一些資訊,像是這條線的Label名稱,抑或是內容等等的

因此我們就可以從這條"線"裡面去尋找每個點所要的資料

 

最後是底下getOffsetForDrawingAtPoint的複寫

@Override
public MPPointF getOffsetForDrawingAtPoint(float posX, float posY) {
    if (posX < 300) return new MPPointF(-getWidth() / 2f+100f, -getHeight() - 10f);
    else return new MPPointF(-getWidth() / 2f-100f, -getHeight()-10f);
}

 

這邊是我用來偵測顯示位置的,如果這裡不處理就會如下

Screenshot_1620543308

 

Emmmm..他跑出去了(´・д・`)

Screenshot_1620543403

所以把它寫上去後,我只要判斷超過300點,就會自動把標籤往另一個方向移動囉~

 


 

3. CustomXLabel.java撰寫

 

為何在MPChart中的X-Label不會自動換行呢?

原因在於作者可能基於某修原因沒有寫關於換行符號的判斷吧..不過最大的理由是因為那個自不是我們尋常看得到TextView原生元件

而是可以把它視為一張"圖片"

所以變成是如果要使他換行,就是要自己去做圖片的位移

那這個位移要如何實踐呢?

直接給你程式XD

CustomXLabel.java

class CustomXLabel extends XAxisRenderer {
    public CustomXLabel(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans) {
        super(viewPortHandler, xAxis, trans);
    }

    @Override
    protected void drawLabel(Canvas c, String formattedLabel, float x, float y, MPPointF anchor, float angleDegrees) {
//        super.drawLabel(c, formattedLabel, x, y, anchor, angleDegrees);
        //⬆這行要記得刪掉
        String[] lines = formattedLabel.split("\n");
        for (int i = 0; i < lines.length ; i++) {
            float vOffset = i* mAxisLabelPaint.getTextSize();
            Utils.drawXAxisValue(c, lines[i], x, y + vOffset, mAxisLabelPaint, anchor, angleDegrees);
        }

    }
}

這邊可能有一點難理解~我一行一行解釋

首先這裡

String[] lines = formattedLabel.split("\n");

這裏就是一個字串分割,首先先找出字串中的換行符號

再來關鍵是這行

float vOffset = i* mAxisLabelPaint.getTextSize();

這裏是偵測文字高度用的,利用迴圈數*文字高度,就能計算出文字位置

最後再將文字"畫"進去,就完成囉(・ω・)b

Utils.drawXAxisValue(c, lines[i], x, y + vOffset, mAxisLabelPaint, anchor, angleDegrees);

 


 

4. 修改MainActivity.java

 

最後是修改原先的MainActivity.java

直接給你程式,我標示修改的位置

MainActivity.java

 

public class MainActivity extends AppCompatActivity {
    public static final String TAG = MainActivity.class.getSimpleName()+"My";
    LineChart chart;
    ArrayList<String> customxLable = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        chart = (LineChart)findViewById(R.id.linechart);
        setChart();
        Button btReset = findViewById(R.id.button_reSet);
        btReset.setOnClickListener((v)->{
            chart.clear();
            setChart();
        });

    }

    private void setChart() {

        ArrayList<HashMap<String,String>> mData = makeFakeData();
        for (int i=0;i<mData.size();i++){//自定義X軸標籤(一般為時間)
            customxLable.add("資料\n第"+(i+1)+"筆");
        }

        /**設定圖表框架↓*/
        YAxis leftAxis = chart.getAxisLeft();//設置Y軸(左)
        YAxis rightAxis = chart.getAxisRight();//設置Y軸(右)
        rightAxis.setEnabled(false);//讓右邊Y消失
        XAxis xAxis = chart.getXAxis();//設定X軸
        xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);//將x軸表示字移到底下
        xAxis.setLabelCount(3,false);//設定X軸上要有幾個標籤
        chart.getDescription().setEnabled(false);//讓右下角文字消失
//        xAxis.setEnabled(false);//去掉X軸數值
        xAxis.setDrawGridLines(false);//將X軸格子消失掉
        xAxis.setValueFormatter(new MyValueFormatter());//設置X軸

        /**此處第二版新增*/
        MyMarkerView mv = new MyMarkerView(this, R.layout.custom_marker_view,customxLable,chart);//設置點擊標籤
        chart.setMarker(mv);//設置點擊標籤
        chart.setExtraBottomOffset(20f);//圖表上移20點,避免擋住X軸文字

        /**此處第二版新增*/
        //設置X軸標籤換行
        chart.setXAxisRenderer(new CustomXLabel(chart.getViewPortHandler(),chart.getXAxis()
                ,chart.getTransformer(YAxis.AxisDependency.LEFT)));
        /**設定圖表框架↑*/

        /**載入資料↓*/

        ArrayList<Entry> yValues1 = new ArrayList<>();
        ArrayList<Entry> yValues2 = new ArrayList<>();

        for (int i=0;i<mData.size();i++){
            float getFirst = Float.parseFloat(mData.get(i).get("FirstData"));
            float getSecond = Float.parseFloat(mData.get(i).get("SecondData"));
            yValues1.add(new Entry(i,getFirst));
            yValues2.add(new Entry(i,getSecond));

        }
        LineDataSet set1 = new LineDataSet(yValues1, "溫度");
        LineDataSet set2 = new LineDataSet(yValues2, "濕度");
        setChartImage(set1,1);//設置圖表線1
        setChartImage(set2,2);//設置圖表線2

        chart.animateX(2000);

        ArrayList<ILineDataSet> dataSets = new ArrayList<>();
        leftAxis.setAxisMaximum(100f);//設置上限
        leftAxis.setAxisMinimum(0f);//設置下限
        //其實上下限可以不用刻意設置,圖表會自動依數值調整到最適合的範圍
        dataSets.add(set1);
        dataSets.add(set2);
        LineData lineData = new LineData(dataSets);
        chart.setData(lineData);
        /**載入資料↑*/
     }
    ...(略)
}

 

粉底白字的是跟MarkerView相關的內容

綠底白字則是與X軸相關的修改內容歐~

 


 

這週是母親節,我弟北漂好段時間了,難得從台北回來~

這篇文章..真的是好不容易生出來的的XD

畢竟為了慶祝母親節,又是吃大餐又是出去玩,好忙啊~(棒讀)

然後肥了1kg..難得減肥有成啊!!!!QQ

上週更是完全寫不出文章,我上週去打了AZ疫苗,然後整整攤睡了一天XD

不過其實我體質可能還不錯,我倒是除了一般常見的疫苗症狀之外,其他也都沒有

現在打完一瞄過一個禮拜,我已經完全沒事了!!

所以各位啊~不要聽信媒體危言聳聽,因為製造混亂就是他們的工作(笑)

 

那~這篇文章到這,如果本文對你有幫助~

TK

arrow
arrow

    碼農日常 發表在 痞客邦 留言(16) 人氣()