/*
* Copyright 2014 Diogo Bernardino
*
* 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 com.db.chart.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.AttributeSet;
import com.db.chart.model.Bar;
import com.db.chart.model.BarSet;
import com.db.chart.model.ChartSet;
import java.util.ArrayList;
/**
* Implements an HorizontalStackBarChart chart extending {@link com.db.chart.view.BaseStackBarChartView}
*/
public class HorizontalStackBarChartView extends BaseStackBarChartView {
public HorizontalStackBarChartView(Context context, AttributeSet attrs) {
super(context, attrs);
setOrientation(Orientation.HORIZONTAL);
setMandatoryBorderSpacing();
}
public HorizontalStackBarChartView(Context context) {
super(context);
setOrientation(Orientation.HORIZONTAL);
setMandatoryBorderSpacing();
}
/**
* Method responsible to draw bars with the parsed screen points.
*
* @param canvas The canvas to draw on
* @param data {@link java.util.ArrayList} of {@link com.db.chart.model.ChartSet} to use
*/
@Override
public void onDrawChart(Canvas canvas, ArrayList<ChartSet> data) {
float offset;
float currBottom;
float negOffset;
float negCurrBottom;
float y0;
float y1;
float x1;
float barSize;
int bottomSetIndex;
int topSetIndex;
float cornersPatch;
BarSet barSet;
Bar bar;
int dataSize = data.size();
int setSize = data.get(0).size();
float zeroPosition = this.getZeroPosition();
for (int i = 0; i < setSize; i++) {
// If bar needs background
if (style.hasBarBackground)
drawBarBackground(canvas,
(int) this.getInnerChartLeft(),
(int) (data.get(0).getEntry(i).getY() - barWidth / 2),
(int) this.getInnerChartRight(),
(int) (data.get(0).getEntry(i).getY() + barWidth / 2));
// Vertical offset to keep drawing bars on top of the others
offset = 0;
negOffset = 0;
// Bottom of the next bar to be drawn
currBottom = zeroPosition;
negCurrBottom = zeroPosition;
// Unfortunately necessary to discover which set is the bottom and top in case there
// are entries with value 0. To better understand check one of the methods.
bottomSetIndex = discoverBottomSet(i, data);
topSetIndex = discoverTopSet(i, data);
for (int j = 0; j < dataSize; j++) {
barSet = (BarSet) data.get(j);
bar = (Bar) barSet.getEntry(i);
barSize = Math.abs(zeroPosition - bar.getX());
// If:
// Bar not visible OR
// Bar value equal to 0 OR
// Size of bar < 2 (Due to the loss of precision)
// Then no need to draw
if (!barSet.isVisible() || bar.getValue() == 0 || barSize < 2)
continue;
style.barPaint.setColor(bar.getColor());
style.applyAlpha(style.barPaint, barSet.getAlpha());
y0 = (bar.getY() - barWidth / 2);
y1 = (bar.getY() + barWidth / 2);
if (bar.getValue() > 0) {
x1 = zeroPosition + (barSize - offset);
// Draw bar
if (j == bottomSetIndex) {
drawBar(canvas, (int) currBottom, (int) y0, (int) x1, (int) y1);
if (bottomSetIndex != topSetIndex && style.cornerRadius != 0) {
// Patch top corners of bar
cornersPatch = (x1 - currBottom) / 2;
canvas.drawRect(new Rect((int) (x1 - cornersPatch), (int) y0,
(int) x1, (int) y1),
style.barPaint);
}
} else if (j == topSetIndex) {
drawBar(canvas, (int) currBottom, (int) y0, (int) x1, (int) y1);
// Patch bottom corners of bar
cornersPatch = (x1 - currBottom) / 2;
canvas.drawRect(new Rect((int) currBottom, (int) y0,
(int) (currBottom + cornersPatch), (int) y1),
style.barPaint);
} else {// if(j != bottomSetIndex && j != topSetIndex){ // Middle sets
canvas.drawRect(new Rect((int) currBottom, (int) y0,
(int) x1, (int) y1),
style.barPaint);
}
currBottom = x1;
// Increase the vertical offset to be used by the next bar
if (barSize != 0)
// Sum 1 to compensate the loss of precision in float
offset -= barSize - 0;
} else { // if(bar.getValue() < 0)
x1 = zeroPosition - (barSize + negOffset);
if (j == bottomSetIndex) {
drawBar(canvas, (int) x1, (int) y0, (int) negCurrBottom, (int) y1);
if (bottomSetIndex != topSetIndex && style.cornerRadius != 0) {
// Patch top corners of bar
cornersPatch = (negCurrBottom - x1) / 2;
canvas.drawRect(new Rect((int) (negCurrBottom - cornersPatch), (int) y0,
(int) negCurrBottom, (int) y1),
style.barPaint);
}
} else if (j == topSetIndex) {
drawBar(canvas, (int) x1, (int) y0, (int) negCurrBottom, (int) y1);
// Patch bottom corners of bar
cornersPatch = (negCurrBottom - x1) / 2;
canvas.drawRect(new Rect((int) x1, (int) y0,
(int) (x1 + cornersPatch), (int) y1),
style.barPaint);
} else {// if(j != bottomSetIndex && j != topSetIndex){ // Middle sets
canvas.drawRect(new Rect((int) x1, (int) y0,
(int) negCurrBottom, (int) y1),
style.barPaint);
}
negCurrBottom = x1;
// Increase the vertical offset to be used by the next bar
if (barSize != 0)
negOffset += barSize;
}
}
}
}
/**
* (Optional) To be overriden in case the view needs to execute some code before
* starting the drawing.
*
* @param data Array of {@link com.db.chart.model.ChartSet} to do the necessary preparation just before onDraw
*/
@Override
public void onPreDrawChart(ArrayList<ChartSet> data) {
// Doing calculations here to avoid doing several times while drawing
// in case of animation
if (data.get(0).size() == 1)
barWidth = (this.getInnerChartBottom() - this.getInnerChartTop()
- this.getBorderSpacing() * 2);
else
calculateBarsWidth(-1, data.get(0).getEntry(1).getY(), data.get(0).getEntry(0).getY());
}
/**
* (Optional) To be overridden in order for each chart to define its own clickable regions.
* This way, classes extending ChartView will only define their clickable regions.
* <p/>
* Important: the returned vector must match the order of the data passed
* by the user. This ensures that onTouchEvent will return the correct index.
*
* @param data {@link java.util.ArrayList} of {@link com.db.chart.model.ChartSet}
* to use while defining each region of a {@link com.db.chart.view.BarChartView}
* @return {@link java.util.ArrayList} of {@link android.graphics.Region} with regions
* where click will be detected
*/
@Override
public ArrayList<ArrayList<Region>> defineRegions(ArrayList<ChartSet> data) {
int dataSize = data.size();
int setSize = data.get(0).size();
ArrayList<ArrayList<Region>> result = new ArrayList<ArrayList<Region>>(dataSize);
for (int i = 0; i < dataSize; i++)
result.add(new ArrayList<Region>(setSize));
float offset;
float currBottom;
float negOffset;
float negCurrBottom;
float x1;
float barSize;
BarSet barSet;
Bar bar;
float zeroPosition = this.getZeroPosition();
for (int i = 0; i < setSize; i++) {
// Vertical offset to keep drawing bars on top of the others
offset = 0;
negOffset = 0;
// Bottom of the next bar to be drawn
currBottom = zeroPosition;
negCurrBottom = zeroPosition;
for (int j = 0; j < dataSize; j++) {
barSet = (BarSet) data.get(j);
bar = (Bar) barSet.getEntry(i);
barSize = Math.abs(zeroPosition - bar.getX());
// If:
// Bar not visible OR
// Bar value equal to 0 OR
// Size of bar < 2 (Due to the loss of precision)
// Then no need to have region
if (!barSet.isVisible() || bar.getValue() == 0 || barSize < 2)
continue;
if (bar.getValue() > 0) {
x1 = zeroPosition + (barSize - offset);
result.get(j).add(new Region(
(int) currBottom,
(int) (bar.getY() - barWidth / 2),
(int) x1,
(int) (bar.getY() + barWidth / 2)));
currBottom = x1;
offset -= barSize - 2;
} else {
x1 = zeroPosition - (barSize + negOffset);
result.get(j).add(new Region(
(int) x1,
(int) (bar.getY() - barWidth / 2),
(int) negCurrBottom,
(int) (bar.getY() + barWidth / 2)));
negCurrBottom = x1;
negOffset += barSize;
}
}
}
return result;
}
}