Home | History | Annotate | Download | only in walt
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package org.chromium.latency.walt;
     18 
     19 import android.content.Context;
     20 import android.content.res.TypedArray;
     21 import android.util.AttributeSet;
     22 import android.view.View;
     23 import android.widget.RelativeLayout;
     24 
     25 import com.github.mikephil.charting.charts.BarChart;
     26 import com.github.mikephil.charting.components.AxisBase;
     27 import com.github.mikephil.charting.components.Description;
     28 import com.github.mikephil.charting.components.XAxis;
     29 import com.github.mikephil.charting.data.BarData;
     30 import com.github.mikephil.charting.data.BarDataSet;
     31 import com.github.mikephil.charting.data.BarEntry;
     32 import com.github.mikephil.charting.formatter.IAxisValueFormatter;
     33 import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
     34 import com.github.mikephil.charting.utils.ColorTemplate;
     35 
     36 import java.text.DecimalFormat;
     37 import java.util.ArrayList;
     38 
     39 public class HistogramChart extends RelativeLayout implements View.OnClickListener {
     40 
     41     static final float GROUP_SPACE = 0.1f;
     42     private HistogramData histogramData;
     43     private BarChart barChart;
     44 
     45     public HistogramChart(Context context, AttributeSet attrs) {
     46         super(context, attrs);
     47         inflate(getContext(), R.layout.histogram, this);
     48 
     49         barChart = (BarChart) findViewById(R.id.bar_chart);
     50         findViewById(R.id.button_close_bar_chart).setOnClickListener(this);
     51 
     52         final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HistogramChart);
     53         final String descString;
     54         final int numDataSets;
     55         final float binWidth;
     56         try {
     57             descString = a.getString(R.styleable.HistogramChart_description);
     58             numDataSets = a.getInteger(R.styleable.HistogramChart_numDataSets, 1);
     59             binWidth = a.getFloat(R.styleable.HistogramChart_binWidth, 5f);
     60         } finally {
     61             a.recycle();
     62         }
     63 
     64         ArrayList<IBarDataSet> dataSets = new ArrayList<>(numDataSets);
     65         for (int i = 0; i < numDataSets; i++) {
     66             final BarDataSet dataSet = new BarDataSet(new ArrayList<BarEntry>(), "");
     67             dataSet.setColor(ColorTemplate.MATERIAL_COLORS[i]);
     68             dataSets.add(dataSet);
     69         }
     70 
     71         BarData barData = new BarData(dataSets);
     72         barData.setBarWidth((1f - GROUP_SPACE)/numDataSets);
     73         barChart.setData(barData);
     74         histogramData = new HistogramData(numDataSets, binWidth);
     75         groupBars(barData);
     76         final Description desc = new Description();
     77         desc.setText(descString);
     78         desc.setTextSize(12f);
     79         barChart.setDescription(desc);
     80 
     81         XAxis xAxis = barChart.getXAxis();
     82         xAxis.setGranularityEnabled(true);
     83         xAxis.setGranularity(1);
     84         xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
     85         xAxis.setValueFormatter(new IAxisValueFormatter() {
     86             DecimalFormat df = new DecimalFormat("#.##");
     87 
     88             @Override
     89             public String getFormattedValue(float value, AxisBase axis) {
     90                 return df.format(histogramData.getDisplayValue(value));
     91             }
     92         });
     93 
     94         barChart.setFitBars(true);
     95         barChart.invalidate();
     96     }
     97 
     98     BarChart getBarChart() {
     99         return barChart;
    100     }
    101 
    102     /**
    103      * Re-implementation of BarData.groupBars(), but allows grouping with only 1 BarDataSet
    104      * This adjusts the x-coordinates of entries, which centers the bars between axis labels
    105      */
    106     static void groupBars(final BarData barData) {
    107         IBarDataSet max = barData.getMaxEntryCountSet();
    108         int maxEntryCount = max.getEntryCount();
    109         float groupSpaceWidthHalf = GROUP_SPACE / 2f;
    110         float barWidthHalf = barData.getBarWidth() / 2f;
    111         float interval = barData.getGroupWidth(GROUP_SPACE, 0);
    112         float fromX = 0;
    113 
    114         for (int i = 0; i < maxEntryCount; i++) {
    115             float start = fromX;
    116             fromX += groupSpaceWidthHalf;
    117 
    118             for (IBarDataSet set : barData.getDataSets()) {
    119                 fromX += barWidthHalf;
    120                 if (i < set.getEntryCount()) {
    121                     BarEntry entry = set.getEntryForIndex(i);
    122                     if (entry != null) {
    123                         entry.setX(fromX);
    124                     }
    125                 }
    126                 fromX += barWidthHalf;
    127             }
    128 
    129             fromX += groupSpaceWidthHalf;
    130             float end = fromX;
    131             float innerInterval = end - start;
    132             float diff = interval - innerInterval;
    133 
    134             // correct rounding errors
    135             if (diff > 0 || diff < 0) {
    136                 fromX += diff;
    137             }
    138         }
    139         barData.notifyDataChanged();
    140     }
    141 
    142     public void clearData() {
    143         histogramData.clear();
    144         for (IBarDataSet dataSet : barChart.getBarData().getDataSets()) {
    145             dataSet.clear();
    146         }
    147         barChart.getBarData().notifyDataChanged();
    148         barChart.invalidate();
    149     }
    150 
    151     public void addEntry(int dataSetIndex, double value) {
    152         histogramData.addEntry(barChart.getBarData(), dataSetIndex, value);
    153         recalculateXAxis();
    154     }
    155 
    156     public void addEntry(double value) {
    157         addEntry(0, value);
    158     }
    159 
    160     private void recalculateXAxis() {
    161         final XAxis xAxis = barChart.getXAxis();
    162         xAxis.setAxisMinimum(0);
    163         xAxis.setAxisMaximum(histogramData.getNumBins());
    164         barChart.notifyDataSetChanged();
    165         barChart.invalidate();
    166     }
    167 
    168     public void setLabel(int dataSetIndex, String label) {
    169         barChart.getBarData().getDataSetByIndex(dataSetIndex).setLabel(label);
    170         barChart.getLegendRenderer().computeLegend(barChart.getBarData());
    171         barChart.invalidate();
    172     }
    173 
    174     public void setLabel(String label) {
    175         setLabel(0, label);
    176     }
    177 
    178     public void setDescription(String description) {
    179         getBarChart().getDescription().setText(description);
    180     }
    181 
    182     public void setLegendEnabled(boolean enabled) {
    183         barChart.getLegend().setEnabled(enabled);
    184         barChart.notifyDataSetChanged();
    185         barChart.invalidate();
    186     }
    187 
    188     @Override
    189     public void onClick(View v) {
    190         switch (v.getId()) {
    191             case R.id.button_close_bar_chart:
    192                 this.setVisibility(GONE);
    193         }
    194     }
    195 
    196     static class HistogramData {
    197         private float binWidth;
    198         private final ArrayList<ArrayList<Double>> rawData;
    199         private double minBin = 0;
    200         private double maxBin = 100;
    201         private double min = 0;
    202         private double max = 100;
    203 
    204         HistogramData(int numDataSets, float binWidth) {
    205             this.binWidth = binWidth;
    206             rawData = new ArrayList<>(numDataSets);
    207             for (int i = 0; i < numDataSets; i++) {
    208                 rawData.add(new ArrayList<Double>());
    209             }
    210         }
    211 
    212         float getBinWidth() {
    213             return binWidth;
    214         }
    215 
    216         double getMinBin() {
    217             return minBin;
    218         }
    219 
    220         void clear() {
    221             for (int i = 0; i < rawData.size(); i++) {
    222                 rawData.get(i).clear();
    223             }
    224         }
    225 
    226         private boolean isEmpty() {
    227             for (ArrayList<Double> data : rawData) {
    228                 if (!data.isEmpty()) return false;
    229             }
    230             return true;
    231         }
    232 
    233         void addEntry(BarData barData, int dataSetIndex, double value) {
    234             if (isEmpty()) {
    235                 min = value;
    236                 max = value;
    237             } else {
    238                 if (value < min) min = value;
    239                 if (value > max) max = value;
    240             }
    241 
    242             rawData.get(dataSetIndex).add(value);
    243             recalculateDataSet(barData);
    244         }
    245 
    246         void recalculateDataSet(final BarData barData) {
    247             minBin = Math.floor(min / binWidth) * binWidth;
    248             maxBin = Math.floor(max / binWidth) * binWidth;
    249 
    250             int[][] bins = new int[rawData.size()][getNumBins()];
    251 
    252             for (int setNum = 0; setNum < rawData.size(); setNum++) {
    253                 for (Double d : rawData.get(setNum)) {
    254                     ++bins[setNum][(int) (Math.floor((d - minBin) / binWidth))];
    255                 }
    256             }
    257 
    258             for (int setNum = 0; setNum < barData.getDataSetCount(); setNum++) {
    259                 final IBarDataSet dataSet = barData.getDataSetByIndex(setNum);
    260                 dataSet.clear();
    261                 for (int i = 0; i < bins[setNum].length; i++) {
    262                     dataSet.addEntry(new BarEntry(i, bins[setNum][i]));
    263                 }
    264             }
    265             groupBars(barData);
    266             barData.notifyDataChanged();
    267         }
    268 
    269         int getNumBins() {
    270             return (int) (((maxBin - minBin) / binWidth) + 1);
    271         }
    272 
    273         double getDisplayValue(float value) {
    274             return value * getBinWidth() + getMinBin();
    275         }
    276     }
    277 }
    278