diff options
Diffstat (limited to 'android/WALT/app/src/main/java/org/chromium/latency/walt/HistogramChart.java')
-rw-r--r-- | android/WALT/app/src/main/java/org/chromium/latency/walt/HistogramChart.java | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/android/WALT/app/src/main/java/org/chromium/latency/walt/HistogramChart.java b/android/WALT/app/src/main/java/org/chromium/latency/walt/HistogramChart.java new file mode 100644 index 0000000..3fc68f6 --- /dev/null +++ b/android/WALT/app/src/main/java/org/chromium/latency/walt/HistogramChart.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.chromium.latency.walt; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.widget.RelativeLayout; + +import com.github.mikephil.charting.charts.BarChart; +import com.github.mikephil.charting.components.AxisBase; +import com.github.mikephil.charting.components.Description; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.data.BarData; +import com.github.mikephil.charting.data.BarDataSet; +import com.github.mikephil.charting.data.BarEntry; +import com.github.mikephil.charting.formatter.IAxisValueFormatter; +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; +import com.github.mikephil.charting.utils.ColorTemplate; + +import java.text.DecimalFormat; +import java.util.ArrayList; + +public class HistogramChart extends RelativeLayout implements View.OnClickListener { + + static final float GROUP_SPACE = 0.1f; + private HistogramData histogramData; + private BarChart barChart; + + public HistogramChart(Context context, AttributeSet attrs) { + super(context, attrs); + inflate(getContext(), R.layout.histogram, this); + + barChart = (BarChart) findViewById(R.id.bar_chart); + findViewById(R.id.button_close_bar_chart).setOnClickListener(this); + + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HistogramChart); + final String descString; + final int numDataSets; + final float binWidth; + try { + descString = a.getString(R.styleable.HistogramChart_description); + numDataSets = a.getInteger(R.styleable.HistogramChart_numDataSets, 1); + binWidth = a.getFloat(R.styleable.HistogramChart_binWidth, 5f); + } finally { + a.recycle(); + } + + ArrayList<IBarDataSet> dataSets = new ArrayList<>(numDataSets); + for (int i = 0; i < numDataSets; i++) { + final BarDataSet dataSet = new BarDataSet(new ArrayList<BarEntry>(), ""); + dataSet.setColor(ColorTemplate.MATERIAL_COLORS[i]); + dataSets.add(dataSet); + } + + BarData barData = new BarData(dataSets); + barData.setBarWidth((1f - GROUP_SPACE)/numDataSets); + barChart.setData(barData); + histogramData = new HistogramData(numDataSets, binWidth); + groupBars(barData); + final Description desc = new Description(); + desc.setText(descString); + desc.setTextSize(12f); + barChart.setDescription(desc); + + XAxis xAxis = barChart.getXAxis(); + xAxis.setGranularityEnabled(true); + xAxis.setGranularity(1); + xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); + xAxis.setValueFormatter(new IAxisValueFormatter() { + DecimalFormat df = new DecimalFormat("#.##"); + + @Override + public String getFormattedValue(float value, AxisBase axis) { + return df.format(histogramData.getDisplayValue(value)); + } + }); + + barChart.setFitBars(true); + barChart.invalidate(); + } + + BarChart getBarChart() { + return barChart; + } + + /** + * Re-implementation of BarData.groupBars(), but allows grouping with only 1 BarDataSet + * This adjusts the x-coordinates of entries, which centers the bars between axis labels + */ + static void groupBars(final BarData barData) { + IBarDataSet max = barData.getMaxEntryCountSet(); + int maxEntryCount = max.getEntryCount(); + float groupSpaceWidthHalf = GROUP_SPACE / 2f; + float barWidthHalf = barData.getBarWidth() / 2f; + float interval = barData.getGroupWidth(GROUP_SPACE, 0); + float fromX = 0; + + for (int i = 0; i < maxEntryCount; i++) { + float start = fromX; + fromX += groupSpaceWidthHalf; + + for (IBarDataSet set : barData.getDataSets()) { + fromX += barWidthHalf; + if (i < set.getEntryCount()) { + BarEntry entry = set.getEntryForIndex(i); + if (entry != null) { + entry.setX(fromX); + } + } + fromX += barWidthHalf; + } + + fromX += groupSpaceWidthHalf; + float end = fromX; + float innerInterval = end - start; + float diff = interval - innerInterval; + + // correct rounding errors + if (diff > 0 || diff < 0) { + fromX += diff; + } + } + barData.notifyDataChanged(); + } + + public void clearData() { + histogramData.clear(); + for (IBarDataSet dataSet : barChart.getBarData().getDataSets()) { + dataSet.clear(); + } + barChart.getBarData().notifyDataChanged(); + barChart.invalidate(); + } + + public void addEntry(int dataSetIndex, double value) { + histogramData.addEntry(barChart.getBarData(), dataSetIndex, value); + recalculateXAxis(); + } + + public void addEntry(double value) { + addEntry(0, value); + } + + private void recalculateXAxis() { + final XAxis xAxis = barChart.getXAxis(); + xAxis.setAxisMinimum(0); + xAxis.setAxisMaximum(histogramData.getNumBins()); + barChart.notifyDataSetChanged(); + barChart.invalidate(); + } + + public void setLabel(int dataSetIndex, String label) { + barChart.getBarData().getDataSetByIndex(dataSetIndex).setLabel(label); + barChart.getLegendRenderer().computeLegend(barChart.getBarData()); + barChart.invalidate(); + } + + public void setLabel(String label) { + setLabel(0, label); + } + + public void setDescription(String description) { + getBarChart().getDescription().setText(description); + } + + public void setLegendEnabled(boolean enabled) { + barChart.getLegend().setEnabled(enabled); + barChart.notifyDataSetChanged(); + barChart.invalidate(); + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.button_close_bar_chart: + this.setVisibility(GONE); + } + } + + static class HistogramData { + private float binWidth; + private final ArrayList<ArrayList<Double>> rawData; + private double minBin = 0; + private double maxBin = 100; + private double min = 0; + private double max = 100; + + HistogramData(int numDataSets, float binWidth) { + this.binWidth = binWidth; + rawData = new ArrayList<>(numDataSets); + for (int i = 0; i < numDataSets; i++) { + rawData.add(new ArrayList<Double>()); + } + } + + float getBinWidth() { + return binWidth; + } + + double getMinBin() { + return minBin; + } + + void clear() { + for (int i = 0; i < rawData.size(); i++) { + rawData.get(i).clear(); + } + } + + private boolean isEmpty() { + for (ArrayList<Double> data : rawData) { + if (!data.isEmpty()) return false; + } + return true; + } + + void addEntry(BarData barData, int dataSetIndex, double value) { + if (isEmpty()) { + min = value; + max = value; + } else { + if (value < min) min = value; + if (value > max) max = value; + } + + rawData.get(dataSetIndex).add(value); + recalculateDataSet(barData); + } + + void recalculateDataSet(final BarData barData) { + minBin = Math.floor(min / binWidth) * binWidth; + maxBin = Math.floor(max / binWidth) * binWidth; + + int[][] bins = new int[rawData.size()][getNumBins()]; + + for (int setNum = 0; setNum < rawData.size(); setNum++) { + for (Double d : rawData.get(setNum)) { + ++bins[setNum][(int) (Math.floor((d - minBin) / binWidth))]; + } + } + + for (int setNum = 0; setNum < barData.getDataSetCount(); setNum++) { + final IBarDataSet dataSet = barData.getDataSetByIndex(setNum); + dataSet.clear(); + for (int i = 0; i < bins[setNum].length; i++) { + dataSet.addEntry(new BarEntry(i, bins[setNum][i])); + } + } + groupBars(barData); + barData.notifyDataChanged(); + } + + int getNumBins() { + return (int) (((maxBin - minBin) / binWidth) + 1); + } + + double getDisplayValue(float value) { + return value * getBinWidth() + getMinBin(); + } + } +} |