blob: 3fc68f61a491a3f7862dc4a5f581851275b7704c [file] [log] [blame]
/*
* 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();
}
}
}