BarChart.java

package de.slothsoft.charts.barchart;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import de.slothsoft.charts.Area;
import de.slothsoft.charts.Chart;
import de.slothsoft.charts.GraphicContext;
import de.slothsoft.charts.PaintInstructions;
import de.slothsoft.charts.RefreshListener;

/**
 * This class represents a {@link Chart} that displays some bars to compare values. It's
 * structure looks like this:<br>
 * <img src=
 * "https://raw.githubusercontent.com/wiki/slothsoft/charts/images/bar-chart-structure.png"
 * alt="structure">
 *
 * @author Stef Schulz
 * @since 0.2.0
 */

public class BarChart extends Chart {

	final XYAxis axis = new XYAxis(this);
	final List<Bar> bars = new ArrayList<>();
	final RefreshListener refreshListener = e -> fireRefreshNeeded();

	int defaultBarColor = 0xFF22AA22;

	/**
	 * Default constructor.
	 */

	public BarChart() {
		addChartPart(this.axis);
	}

	@Override
	protected void paintGraph(GraphicContext gc, PaintInstructions instructions) {
		final Area area = instructions.getArea();

		final double width = area.calculateWidth();
		final double height = area.calculateHeight();
		final double startX = area.getStartX();

		final double maxValue = this.bars.stream().mapToDouble(Bar::getValue).max().orElse(1);
		final double widthPerBar = width / (2 * (this.bars.size() + 1));

		int index = 0;
		for (final Bar bar : this.bars) {
			gc.setColor(bar.color);
			final double barHeight = (bar.getValue()) / maxValue * height;

			gc.fillRectangle(startX + widthPerBar + 2 * index * widthPerBar, height - barHeight, widthPerBar,
					barHeight + 1);
			index++;
		}
	}

	/**
	 * Creates a new {@link Bar} and adds it to this chart.
	 *
	 * @param value the value of the bar
	 * @return the added bar
	 */

	public Bar addBar(double value) {
		final Bar result = doAddBar(value);
		fireRefreshNeeded();
		return result;
	}

	private Bar doAddBar(double value) {
		final Bar bar = new Bar(value).color(this.defaultBarColor);
		bar.addRefreshListener(this.refreshListener);
		this.bars.add(bar);
		return bar;
	}

	/**
	 * Creates some new {@link Bar}s and adds them to this chart.
	 *
	 * @param values the values of the bars
	 * @return the added bars
	 */

	public Bar[] addBars(double... values) {
		final Bar[] result = new Bar[values.length];
		for (int i = 0; i < result.length; i++) {
			result[i] = doAddBar(values[i]);
		}
		fireRefreshNeeded();
		return result;
	}

	/**
	 * Removes a {@link Bar} from this chart.
	 *
	 * @param bar the removed bar
	 */

	public void removeBar(Bar bar) {
		doRemoveBar(bar);
		fireRefreshNeeded();
	}

	private void doRemoveBar(Bar bar) {
		bar.removeRefreshListener(this.refreshListener);
		this.bars.remove(bar);
	}

	/**
	 * Removes some {@link Bar}s from this chart.
	 *
	 * @param removedBars the removed bars
	 */

	public void removeBars(Bar... removedBars) {
		Arrays.stream(removedBars).forEach(this::doRemoveBar);
		fireRefreshNeeded();
	}

	/**
	 * Returns the default color of the bars as ARGB int, e.g. red is
	 * <code>0xFFFF0000</code> and blue is <code>0xFF0000FF</code>.
	 *
	 * @return the color
	 */

	public int getDefaultBarColor() {
		return this.defaultBarColor;
	}

	/**
	 * Sets the default color of the bars as ARGB int, e.g. red is <code>0xFFFF0000</code>
	 * and blue is <code>0xFF0000FF</code>.
	 *
	 * @param newDefaultBarColor the color
	 * @return this instance
	 */

	public Chart defaultBarColor(int newDefaultBarColor) {
		setDefaultBarColor(newDefaultBarColor);
		return this;
	}

	/**
	 * Sets the default color of the bars as ARGB int, e.g. red is <code>0xFFFF0000</code>
	 * and blue is <code>0xFF0000FF</code>.
	 *
	 * @param defaultBarColor the color
	 */

	public void setDefaultBarColor(int defaultBarColor) {
		final int oldDefaultBarColor = this.defaultBarColor;
		this.defaultBarColor = defaultBarColor;
		if (oldDefaultBarColor != this.defaultBarColor) {
			fireRefreshNeeded();
		}
	}

	/**
	 * Returns the axis of this line chart.
	 *
	 * @return the axis
	 */

	public XYAxis getAxis() {
		return this.axis;
	}
}