aboutsummaryrefslogtreecommitdiff
path: root/android/WALT/app/src/main/java/org/chromium/latency/walt/HistogramChart.java
diff options
context:
space:
mode:
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.java277
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();
+ }
+ }
+}