I’m using the MPAndroidChart library (3.0.0) to represent psychometric personality test results in a radar chart (AKA spider chart or cobweb chart). Since the documentation I found on this was either incomplete or obsolete, I’ll briefly document the steps I took.
I’m displaying the chart inside a ConstraintLayout. A fixed height gives satisfactory results on all tested devices.
<com.github.mikephil.charting.charts.RadarChart android:id="@+id/chart" android:layout_width="0dp" android:layout_height="360dp" android:layout_marginEnd="24dp" android:layout_marginLeft="24dp" android:layout_marginRight="24dp" android:layout_marginStart="24dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/textViewTitle" app:layout_constraintVertical_bias="0.0" /> |
Some fields are needed in our Fragment to hold reference to the RadarChart (and optionally a Typeface), store the variables (factors), the raw data (scores), the List of chart entries and the List of data sets to plot (only one set in this example).
private RadarChart mChart; private Typeface mTfLight; private SparseIntArray factors = new SparseIntArray(5); private SparseIntArray scores = new SparseIntArray(5); private ArrayList<RadarEntry> entries = new ArrayList<>(); private ArrayList<IRadarDataSet> dataSets = new ArrayList<>(); |
The factors (‘x-axis’) are defined in onCreate(), as well as a Typeface to be loaded from the assets directory:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... factors.append(1, R.string.extraversion); factors.append(2, R.string.agreeableness); factors.append(3, R.string.conscientiousness); factors.append(4, R.string.emotional_stability); factors.append(5, R.string.intellect_imagination); mTfLight = Typeface.createFromAsset(mActivity.getAssets(), "OpenSans-Light.ttf"); // ... } |
The strings will be displayed on the chart axes (the ‘x-axis’ of the chart object).
We’ll set the appearance properties of the chart in onCreateView(). We load our data the proper way, asynchronously using a Loader pattern, so we should populate the chart somewhat later in the lifecycle from onLoadFinished. Most properties should be self-explanatory. Use an IAxisValueFormatter to return a formatted String representation of the values to display.
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_result, container, false); mChart = (RadarChart) rootView.findViewById(R.id.chart); XAxis xAxis = mChart.getXAxis(); xAxis.setXOffset(0f); xAxis.setYOffset(0f); xAxis.setTypeface(mTfLight); xAxis.setTextSize(8f); xAxis.setValueFormatter(new IAxisValueFormatter() { private String[] mFactors = new String[]{getString(factors.get(1)), getString(factors.get(2)), getString(factors.get(3)), getString(factors.get(4)), getString(factors.get(5))}; @Override public String getFormattedValue(float value, AxisBase axis) { return mFactors[(int) value % mFactors.length]; } @Override public int getDecimalDigits() { return 0; } }); YAxis yAxis = mChart.getYAxis(); yAxis.setAxisMinimum(0f); yAxis.setAxisMaximum(50f); yAxis.setTypeface(mTfLight); yAxis.setTextSize(9f); yAxis.setLabelCount(5, false); yAxis.setDrawLabels(false); mChart.getLegend().setEnabled(false); mChart.getDescription().setEnabled(false); mChart.animateXY( 1400, 1400, Easing.EasingOption.EaseInOutQuad, Easing.EasingOption.EaseInOutQuad); // ... return rootView; } |
Start the loader with restartLoader() (not initLoader()) to ensure that the cursor refreshes itself:
mActivity.getSupportLoaderManager().restartLoader(ANSWERED_ITEMS_LOADER_ID, null, this); |
Populate the data array in onLoadFinished():
@Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { scores.clear(); // while (cursor.moveToNext()) { // ... // scores.put(..., ...); // } // Or hardcode some test data: scores.append(1, 18); scores.append(2, 26); scores.append(3, 35); scores.append(4, 40); scores.append(5, 48); drawChart(); } |
To draw the chart, first build the list of entries. Then create a RadarDataSet from this list and add it to the list of data sets. Create a RadarData object from this list and add it to the chart.
An IValueFormatter returns a formatted string for the integer values to display.
Finally, invalidate the View to ensure that it gets redrawn when populated.
private void drawChart() { entries.clear(); for (int i = 1; i <= 5; i++) { entries.add(new RadarEntry(scores.get(i))); } RadarDataSet dataSet = new RadarDataSet(entries, ""); dataSet.setColor(R.color.colorPrimary); dataSet.setDrawFilled(true); dataSets.add(dataSet); RadarData data = new RadarData(dataSets); data.setValueTypeface(mTfLight); data.setValueTextSize(8f); data.setValueFormatter(new IValueFormatter() { @Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { return String.valueOf((int) value); } }); mChart.setData(data); mChart.invalidate(); } |
You can check out our app to see the chart in action.
For newer versions of MPAndroidChart (3.0.3) do not implement getDecimalDigits() in IAxisValueFormatter.