บทความสอนเขียนแอพ Android วันนี้ขอนำเสนอเรื่อง การเขียนกราฟ การ plot กราฟ บนแอพพลิเคชัน Android สำหรับบทความนี้จะใช้ Library ที่ชื่อว่า AChartEngine ตัวอย่าง AChartEngine ทำอะไรได้บ้าง ก็ดูได้จากลิงค์นี้ครับ ตัวอย่าง AChartEngine

มาเข้าเรื่องเลยดีกว่า บทความนี้จะเป็นการแสดง Line Chart (กราฟเส้น) แบบง่ายๆให้ดูนะครับ เริ่มต้น ก็สร้างโปรเจ็คแบบปกติ ไฟล์ที่ต้องใช้มีแค่ 2 ไฟล์ครับ คือ ไฟล์ Activity กับ ไฟล์เลเอาท์ xml

บทความนี้ใช้ Eclipse รันบน Ubuntu 14.04 นะครับ

Add Library

ทำการโหลด Library ของ AChartEngine ที่นี่ เลือกดาวน์โหลด achartengine-1.1.0.jar จากนั้นก็ import ไปที่โปรเจ็คของเรา

สำหรับ Eclipse ก็อปปี้ไฟล์ achartengine-1.1.0.jar ไปไว้ที๋โฟลเดอร์ libs ของโปรเจ็ค จากนั้นคลิกขวาที่ไฟล์ เลือก Build Path -> Add to Build Path

สำหรับ Android Studio มีสองทางเลือกครับ คือ ก็อปไปไว้ที่โฟลเดอร์ libs เช่นเดียวกัน จากนั้นแก้ไขไฟล์ build.gradle โดยเพิ่ม dependencies ดังนี้

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

หากใครมีแล้ว ก็ไม่ต้องแก้อะไร (คำสั่งด้านบนเป็นการสั่ง compile ไฟล์ .jar ทั้งหมดที่อยู่ในโฟลเดอร์ libs)

อีกวิธีหนึ่ง สำหรับคนใช้ maven ครับ เปิด build.gradle ที่ root โปรเจ็คนะครับ แล้วเพิ่ม นี้ลงไป

maven {
    url "https://repository-achartengine.forge.cloudbees.com/snapshot/"
}

จะได้เป็นแบบนี้

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.11.+'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        mavenCentral()
        maven {
            url "https://repository-achartengine.forge.cloudbees.com/snapshot/"
        }
    }
}

ส่วนไฟล์ build.gradle ที่อยู่ใน module ก็เพิ่ม dependencies เข้าไปเป็นแบบนี้

dependencies {
    compile 'com.android.support:appcompat-v7:19.+'
    compile 'org.achartengine:achartengine:1.2.0'
}

Getting Started with AChartEngine

ก่อนที่จะไปสร้าง Line Chart (กราฟเส้น) ด้วย AChartEngine เรามาดูภาพรวม แล้วก็แต่ละคลาสที่ใช้กันก่อนครับ

  • XYSeries : ตัวนี้จะเปรียบเสมือนข้อมูลของกราฟเรา เช่น รายได้ จำนวนประชากร เป็นต้น
  • XYSeriesRenderer : ตัวนี้จะเอาไว้ render กราฟเราครับ เช่น รูปทรงกราฟ สี รูปแบบต่างๆ ปรับแต่งได้
  • XYMultipleSeriesDataset : ตัวนี้คือ Dataset มันคือข้อมูลทั้งหมดของกราฟ โดยดึงมาจาก XYSeries
  • XYMultipleSeriesRenderer : ตัวนี้คือตัว render ทั้งกราฟครับ
  • GraphicalView : ตัวนี้คือ View ชนิดหนึ่ง ที่เอาไว้แสดง graph ครับ

รายละเอียดคร่าวๆ ก็เป็นดังนี้ หากใครงง (แน่นอนว่าต้องงง ถ้าเพิ่งเคยทำ ต้องลองดูตัวอย่าง แล้วจะเข้าใจมากขึ้นครับ)

เริ่มสร้างกราฟ

เป้าหมายคือ ต้องการสร้างกราฟข้อมูลยอดขาย ของ 3 บริษัท ในปีๆหนึ่ง ฉะนั้นกราฟก็จะมี 3 เส้น แบบนี้

Mockup

ให้เปิดคลาส MainActivity.java ขึ้นมา โค๊ดเริ่มต้นผมเป็นแบบนี้

package com.devahoy.sample.achartengine;

import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.ActionBar;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.os.Build;

public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, new PlaceholderFragment())
                    .commit();
        }
    }

    /**
     * A placeholder fragment containing a simple view.
     */
    public static class PlaceholderFragment extends Fragment {

        public PlaceholderFragment() {}

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);
            return rootView;
        }
    }

}

ใครใช้ fragment ไม่เป็น แนะนำให้อ่าน Android Fragment คือ? จริงๆควรจะใช้ fragment ในทุกๆโปรเจ็คนะครับ

ก่อนสร้างกราฟ เราต้องมีข้อมูลก่อน ฉะนั้น สร้างข้อมูลจำลองขึ้นมา โดยสร้างเมธอดใหม่ ชื่อ initData() แบบนี้

private void initData() {
    String[] months = {
            "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
            "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
    };

    int[] index = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    int[] incomeA = {4000, 5500, 2300, 2100, 2500, 2900, 3200, 2400, 1800, 2100, 3500, 5900};
    int[] incomeB = {3600, 4500, 3200, 3600, 2800, 1800, 2100, 2900, 2200, 2500, 4000, 3500};
    int[] incomeC = {4300, 4000, 3000, 3200, 2400, 2500, 2600, 3400, 3900, 4500, 5000, 4500};
}

โค๊ดด้านบน months หมายถึง ข้อมูลที่่จะแสดงบนแกน x ระบุว่าแต่ละเดือนยอดขายเป็นยังไง ส่วน index คือตำแหน่งของข้อมูล โดยใช้ข้อมูล 3 บริษัท ได้แก่ incomeA, incomeB และ incomeC เมื่อมีข้อมูลดิบแล้ว ขั้นตอนต่อมาทำอย่างไร?

ใช้ XYSeries เพื่อเก็บข้อมูลที่ของเราแบบนี้ (ยังอยู่ในเมธอด initData() นะ)

XYSeries seriesA = new XYSeries("Googla");
XYSeries seriesB = new XYSeries("Microsa");
XYSeries seriesC = new XYSeries("Appla");

int length = index.length;
for (int i = 0; i < length; i++) {
    seriesA.add(index[i], incomeA[i]);
    seriesB.add(index[i], incomeB[i]);
    seriesC.add(index[i], incomeC[i]);
}

ต่อมาใช้ XYSeriesRenderer เพื่อจะกำหนดให้กราฟออกมาลักษณะไหน

XYSeriesRenderer rendererA = new XYSeriesRenderer();
rendererA.setPointStyle(PointStyle.CIRCLE);
rendererA.setColor(Color.RED);
rendererA.setLineWidth(2);

ด้านบนเป็นการกำหนด ให้แสดง กราฟเป็นจุดและเป็นสีแดง

ต่อมาสร้าง dataset ขึ้นมาโดยใช้ XYMultipleSeriesDataset แล้วทำการเพิ่ม XYSeries ทั้ง 3 ที่สร้างไว้ก่อนหน้านี้ ดังนี้

XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset();
dataset.addSeries(seriesA);
dataset.addSeries(seriesB);
dataset.addSeries(seriesC);

ต่อมาใช้ XYMultipleSeriesRenderer เพื่อที่จะแสดงกราฟทั้งหมด ขึ้นตอนนี้คือการปรับแต่ง และตั้งค่ากราฟก่อนครับ

XYMultipleSeriesRenderer multipleSeriesRenderer
    = new XYMultipleSeriesRenderer();

for (int i = 0; i < length; i++) {
    multipleSeriesRenderer.addXTextLabel(i + 1, months[i]);
}
multipleSeriesRenderer.setChartTitle("ตัวอย่างกราฟเส้น (Line Chart)");
multipleSeriesRenderer.setYTitle("ยอดขายรวม(สตางค์)");
multipleSeriesRenderer.setXTitle("ปี พ.ศ. 2600");
multipleSeriesRenderer.setZoomButtonsVisible(true);

จะเห็นว่าด้านบน เป็นการกำหนด ข้อความที่จะแสดงในแกน x คือแสดงเดือน ที่เก็บไว้ใน months ส่วนอันอื่นก็ดูตามชื่อเมธอดเลยครับ มัน make sense อยู่แล้ว สุดท้าย ก็ต้องเพิ่ม XYSeriesRenderer ไปที่ XYMultipleSeriesRenderer ด้วย

multipleSeriesRenderer.addSeriesRenderer(rendererA);
multipleSeriesRenderer.addSeriesRenderer(rendererA);
multipleSeriesRenderer.addSeriesRenderer(rendererA);

ข้อควรระวัง dataset และ XYSeriesRenderer ต้องมีจำนวนเท่ากันนะครับ dataset เราเพิ่ม Series ไปเท่าไหร่ XYMultipleSeriesRenderer ก็ต้องเพิ่ม XYSeriesRenderer จำนวนเดียวกันด้วยครับ

ต่อมาเพิ่มอีกหนึ่งบรรทัด เป็นอันจบเมธอด initData()

drawChart(dataset, multipleSeriesRenderer);

ปล่อยให้ error ไว้ก่อนครับ เนื่องจากเรายังไม่ได้ทำการสร้าง เมธอด drawChart() ให้ไปสร้างเลเอาท์ xml ก่อน

สร้างไฟล์ XML

การสร้างเลเอาท์ xml ไม่มีอะไรมากครับ แค่สร้าง ViewGroup ให้มันซักตัว จะเป็น RelativeLayout หรือ LinearLayout ก็ได้ อย่างในบทความ ใช้ RelativeLayout โดยใช้ไฟล์ res/layout/fragment_main.xml จะได้ดังนี้

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/graph_container"
    tools:context="com.devahoy.sample.achartengine.MainActivity$PlaceholderFragment" >

</RelativeLayout>

ต่อมาที่คลาส PlachHolderFragment ภายในคลาส MainActivity ก็เพ่ิมนี้ลงไป

private View mView;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment_main, container, false);
    mView = rootView;
    initData();
    return rootView;
}

เพื่อจะเอา View ที่สร้างจาก onCreateView() ไปใช้ในการสร้าง graph ครับ เลยต้องประกาศเป็นตัวแปร global ไว้

ต่อมาได้เวลาเพิ่มเมธอด drawChart() ซักที ภายในเมธอดก็มีโค๊ดแบบนี้

private void drawChart(XYMultipleSeriesDataset dataset,
                       XYMultipleSeriesRenderer renderer) {
    GraphicalView graphView =
            ChartFactory.getLineChartView(getActivity(), dataset, renderer);

    RelativeLayout container =
            (RelativeLayout) mView.findViewById(R.id.graph_container);

    container.addView(graphView);
}

เราใช้ GraphicalView ซึ่งมันก็คือ View นั่นแหละ เหมือนๆกับ TextView, EditText, Button ทั่วๆไป จากนั้น ก็ใช้ RelativeLayout ที่เราได้สร้างไว้ใน fragment_main.xml ที่ชื่อว่า graph_container ทำการ addView() ซะ เป็นอันจบ

เมื่อเปิดโปรแกรมขึ้นมา จะได้ดังภาพ

AChart Engine

!!! แต่เดี๋ยวก่อน ในเมื่อมีกราฟ ถึง 3 อัน ซึ่งสีเหมือนกัน มันดุยาก เราอยากจะปรับเปลี่ยนสีทำยังไงละ ? แน่นอนครับ ก็ใช้ XYSeriesRenderer เข้ามาช่วย ฉะนั้นผมสร้าง XYSeriesRenderer เพิ่ม 2 ตัว ในเมธอด initData() ดังนี้

XYSeriesRenderer rendererB = new XYSeriesRenderer();
rendererB.setPointStyle(PointStyle.X);
rendererB.setColor(Color.BLUE);
rendererB.setLineWidth(2);

XYSeriesRenderer rendererC = new XYSeriesRenderer();
rendererC.setPointStyle(PointStyle.DIAMOND);
rendererC.setColor(Color.GREEN);
rendererC.setLineWidth(2);

แล้ว XYMultipleSeriesRenderer ก็เพิ่ม XYSeries ให้มันซะ จาก

multipleSeriesRenderer.addSeriesRenderer(rendererA);
multipleSeriesRenderer.addSeriesRenderer(rendererA);
multipleSeriesRenderer.addSeriesRenderer(rendererA);

ก็จะได้เป็น

multipleSeriesRenderer.addSeriesRenderer(rendererA);
multipleSeriesRenderer.addSeriesRenderer(rendererB);
multipleSeriesRenderer.addSeriesRenderer(rendererC);

ลองรันโปรแกรมใหม่ ผลลัพธ์เป็นแบบนี้

AChart 2

ยังไม่จบ เรายังสามารถปรับแต่งกราฟได้อีก โดยผมปรับแต่ง XYMultipleSeriesRenderer อีกเป็นแบบนี้

multipleSeriesRenderer.setXLabels(0);
multipleSeriesRenderer.setBackgroundColor(Color.WHITE);
multipleSeriesRenderer.setApplyBackgroundColor(true);
multipleSeriesRenderer.setMarginsColor(Color.WHITE);
multipleSeriesRenderer.setLabelsColor(Color.BLACK);
multipleSeriesRenderer.setAxesColor(Color.GRAY);
multipleSeriesRenderer.setYLabelsColor(0, Color.BLACK);
multipleSeriesRenderer.setXLabelsColor(Color.BLACK);

ข้างบนคือปรับสี background ปรับสี แกน x, y ปรับตัวหนังสือ และที่สำคัญ ปรับค่า x ให้เป็น 0 เนื่องจากมันจะไปทับกับค่า เดือน ที่เรากำหนดไว้นั่นเอง

สุดท้าย ก็ได้กราฟออกมาเป็นแบบลักษณะดังรูป

AChart Final

สรุป

ตัวอย่างนี้ก็เป็นตัวอย่างการใช้ AChartEngine โดยการวาด Line Chart (กราฟแท่ง) แบบง่ายๆนั้นเอง หวังว่าผู้อ่านทุกท่าน สามารถนำไปประยุกต์ ปรับแต่ง ใช้งานให้เข้ากับแอพของท่านได้นะครับ

สุดท้าย โค๊ดตัวอย่าง อัพลง Github เรียบร้อยแล้ว ไปดูได้ที่นี่ครับ

Source Code