Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* <li>Missing point in series
* <li>Manually setting y-axis min and max values
* <li>Bar Chart Annotations
* <li>Data labels positioned on top of (outside) the bars
* <li>Horizontal Legend OutsideS
*/
public class BarChart04 implements ExampleChart<CategoryChart> {
Expand Down Expand Up @@ -47,6 +48,8 @@ public CategoryChart getChart() {
chart.getStyler().setYAxisMin(5.0);
chart.getStyler().setYAxisMax(70.0);
chart.getStyler().setLabelsVisible(true);
// A value greater than 1 places the labels on top of (outside) the bars
chart.getStyler().setLabelsPosition(1.05);
chart.getStyler().setPlotGridVerticalLinesVisible(false);
chart.getStyler().setLegendPosition(Styler.LegendPosition.OutsideS);
chart.getStyler().setLegendLayout(Styler.LegendLayout.Horizontal);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* <li>Single series
* <li>Place legend at Inside-NW position
* <li>Bar Chart Annotations
* <li>Data labels positioned outside (beyond the end of) the bars
*/
public class HorizontalBarChart01 implements ExampleChart<HorizontalBarChart> {

Expand All @@ -44,7 +45,9 @@ public HorizontalBarChart getChart() {

// Customize Chart
chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
chart.getStyler().setLabelsVisible(false);
chart.getStyler().setLabelsVisible(true);
// A value greater than 1 places the labels outside (beyond the end of) the bars
chart.getStyler().setLabelsPosition(1.1);
chart.getStyler().setPlotGridLinesVisible(false);

// Series
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.knowm.xchart.standalone.issues;

import org.knowm.xchart.CategoryChart;
import org.knowm.xchart.CategoryChartBuilder;
import org.knowm.xchart.SwingWrapper;
import org.knowm.xchart.demo.charts.ExampleChart;
import org.knowm.xchart.style.Styler.LegendPosition;

/**
* Issue #500: data labels can be placed outside (on top of) the bars again, as in 3.6.1.
*
* <p>A labels position greater than 1 draws the label outside the bar (above positive bars, below
* negative bars). The automatic label font color is computed against the plot background, so the
* labels stay legible without setting a color manually.
*/
public class TestForIssue500 implements ExampleChart<CategoryChart> {

public static void main(String[] args) {
ExampleChart<CategoryChart> exampleChart = new TestForIssue500();
CategoryChart chart = exampleChart.getChart();
new SwingWrapper<CategoryChart>(chart).displayChart();
}

@Override
public CategoryChart getChart() {

// Create Chart
CategoryChart chart =
new CategoryChartBuilder()
.width(800)
.height(600)
.title("TestForIssue500")
.xAxisTitle("x")
.yAxisTitle("y")
.build();

// Customize Chart
chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
chart.getStyler().setLabelsVisible(true);
// A value greater than 1 places the labels outside (above/below) the bars
chart.getStyler().setLabelsPosition(1.1);

// Series
chart.addSeries("test 1", new double[] {0, 1, 2, 3, 4}, new double[] {4, 5, -7, 6, -5});

return chart;
}

@Override
public String getExampleChartName() {

return getClass().getSimpleName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.knowm.xchart.standalone.issues;

import java.util.Arrays;
import org.knowm.xchart.HorizontalBarChart;
import org.knowm.xchart.HorizontalBarChartBuilder;
import org.knowm.xchart.SwingWrapper;
import org.knowm.xchart.demo.charts.ExampleChart;
import org.knowm.xchart.style.Styler.LegendPosition;

/**
* Issue #500: data labels can be placed outside the bars for horizontal bar charts too.
*
* <p>A labels position greater than 1 draws the label beyond the end of the bar. The automatic label
* font color is computed against the plot background, so the labels stay legible without setting a
* color manually.
*/
public class TestForIssue500_HorizontalBar implements ExampleChart<HorizontalBarChart> {

public static void main(String[] args) {
ExampleChart<HorizontalBarChart> exampleChart = new TestForIssue500_HorizontalBar();
HorizontalBarChart chart = exampleChart.getChart();
new SwingWrapper<>(chart).displayChart();
}

@Override
public HorizontalBarChart getChart() {

// Create Chart
HorizontalBarChart chart =
new HorizontalBarChartBuilder()
.width(800)
.height(600)
.title("TestForIssue500_HorizontalBar")
.yAxisTitle("Score")
.xAxisTitle("Number")
.build();

// Customize Chart
chart.getStyler().setLegendPosition(LegendPosition.InsideNW);
chart.getStyler().setPlotGridLinesVisible(false);
chart.getStyler().setLabelsVisible(true);
// A value greater than 1 places the labels outside (beyond the end of) the bars
chart.getStyler().setLabelsPosition(1.1);

// Series
chart.addSeries("test 1", Arrays.asList(4, 5, -7, 6, -5), Arrays.asList(0, 1, 2, 3, 4));

return chart;
}

@Override
public String getExampleChartName() {

return getClass().getSimpleName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

public class AxisPair<ST extends AxesChartStyler, S extends AxesChartSeries> implements ChartPart {

// Fraction of the axis range reserved as headroom when data labels are drawn outside the bars, so
// they are not clipped at the plot edge.
private static final double OUTSIDE_LABELS_AXIS_PADDING = 0.05;

private final AxesChart<ST, S> chart;

private final Axis_X<ST, S> xAxis;
Expand Down Expand Up @@ -255,12 +259,26 @@ private void overrideMinMaxForXAxis() {
double overrideXAxisMaxValue = xAxis.getMax();

if (chart.getStyler() instanceof HorizontalBarStyler) {
HorizontalBarStyler horizontalBarStyler = (HorizontalBarStyler) chart.getStyler();
if (xAxis.getMin() > 0.0) {
overrideXAxisMinValue = 0.0;
}
if (xAxis.getMax() < 0.0) {
overrideXAxisMaxValue = 0.0;
}

// When labels are drawn outside the bars, reserve axis headroom so they are not clipped at
// the plot edge.
if (horizontalBarStyler.isLabelsVisible() && horizontalBarStyler.getLabelsPosition() > 1) {
double extra =
(overrideXAxisMaxValue - overrideXAxisMinValue) * OUTSIDE_LABELS_AXIS_PADDING;
if (overrideXAxisMaxValue > 0.0) {
overrideXAxisMaxValue += extra;
}
if (overrideXAxisMinValue < 0.0) {
overrideXAxisMinValue -= extra;
}
}
}

// override min and maxValue if specified
Expand Down Expand Up @@ -354,6 +372,19 @@ private void overrideMinMaxForYAxis(Axis_Y<ST, S> yAxis) {
if (yAxis.getMax() < 0.0) {
overrideYAxisMaxValue = 0.0;
}

// When labels are drawn outside the bars, reserve axis headroom so they are not clipped at
// the plot edge.
if (categoryStyler.isLabelsVisible() && categoryStyler.getLabelsPosition() > 1) {
double extra =
(overrideYAxisMaxValue - overrideYAxisMinValue) * OUTSIDE_LABELS_AXIS_PADDING;
if (overrideYAxisMaxValue > 0.0) {
overrideYAxisMaxValue += extra;
}
if (overrideYAxisMinValue < 0.0) {
overrideYAxisMinValue -= extra;
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ public abstract class PlotContent_<ST extends Styler, S extends Series> implemen
static final BasicStroke ERROR_BAR_STROKE =
new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);

// Converts the fraction of a labels position above 1 into a fixed pixel gap outside the bar, so
// the gap does not scale with the bar's size (e.g. a position of 1.1 places the label 10px out).
// Also used when reserving axis headroom so outside labels are not clipped at the plot edge.
static final double OUTSIDE_LABELS_OFFSET_SCALE = 100;

/**
* Constructor
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -701,24 +701,39 @@ private void drawLabels(
} else {
labelX = xOffset + barWidth / 2 - labelRectangle.getWidth() / 2 - 1;
}
double labelsPosition = stylerCategory.getLabelsPosition();
double labelY;
if (showStackSum) {
labelY = yOffset - 4;
} else {
} else if (labelsPosition <= 1) {
// inside the bar: the position is a fraction of the bar's height
if (next >= 0.0) {
labelY =
yOffset
+ (zeroOffset - yOffset) * (1 - stylerCategory.getLabelsPosition())
+ labelRectangle.getHeight() * stylerCategory.getLabelsPosition();
+ (zeroOffset - yOffset) * (1 - labelsPosition)
+ labelRectangle.getHeight() * labelsPosition;
} else {
labelY =
zeroOffset
- (zeroOffset - yOffset) * (1 - stylerCategory.getLabelsPosition())
+ labelRectangle.getHeight() * (1 - stylerCategory.getLabelsPosition());
- (zeroOffset - yOffset) * (1 - labelsPosition)
+ labelRectangle.getHeight() * (1 - labelsPosition);
}
} else {
// outside the bar: a fixed pixel gap beyond the bar's edge, independent of the bar's height
double outsideOffset = (labelsPosition - 1) * OUTSIDE_LABELS_OFFSET_SCALE;
if (next >= 0.0) {
labelY = yOffset - outsideOffset;
} else {
labelY = zeroOffset + outsideOffset + labelRectangle.getHeight();
}
}
if (stylerCategory.isLabelsFontColorAutomaticEnabled()) {
g.setColor(stylerCategory.getLabelsFontColor(seriesColor));
// When the label is drawn outside the bar it sits on the plot background, not on the bar, so
// the automatic contrast color must be computed against the plot background color.
boolean labelOutsideBar = showStackSum || labelsPosition > 1;
Color contrastBackgroundColor =
labelOutsideBar ? stylerCategory.getPlotBackgroundColor() : seriesColor;
g.setColor(stylerCategory.getLabelsFontColor(contrastBackgroundColor));
} else {
g.setColor(stylerCategory.getLabelsFontColor());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,22 +226,37 @@ private void drawLabels(
} else {
labelY = yOffset + barHeight / 2 + labelRectangle.getHeight() / 2;
}
double labelsPosition = styler.getLabelsPosition();
double labelX;

if (next.doubleValue() >= 0.0) {
labelX =
xOffset
+ (zeroOffset - xOffset) * (1 - styler.getLabelsPosition())
- labelRectangle.getWidth() * styler.getLabelsPosition();
if (labelsPosition <= 1) {
// inside the bar: the position is a fraction of the bar's length
if (next.doubleValue() >= 0.0) {
labelX =
xOffset
+ (zeroOffset - xOffset) * (1 - labelsPosition)
- labelRectangle.getWidth() * labelsPosition;
} else {
labelX =
zeroOffset
- (zeroOffset - xOffset) * (1 - labelsPosition)
- labelRectangle.getWidth() * (1 - labelsPosition);
}
} else {
labelX =
zeroOffset
- (zeroOffset - xOffset) * (1 - styler.getLabelsPosition())
- labelRectangle.getWidth() * (1 - styler.getLabelsPosition());
// outside the bar: a fixed pixel gap beyond the bar's end, independent of the bar's length
double outsideOffset = (labelsPosition - 1) * OUTSIDE_LABELS_OFFSET_SCALE;
if (next.doubleValue() >= 0.0) {
labelX = xOffset + outsideOffset;
} else {
labelX = zeroOffset - outsideOffset - labelRectangle.getWidth();
}
}

if (styler.isLabelsFontColorAutomaticEnabled()) {
g.setColor(styler.getLabelsFontColor(seriesColor));
// When the label is drawn outside the bar it sits on the plot background, not on the bar, so
// the automatic contrast color must be computed against the plot background color.
Color contrastBackgroundColor =
labelsPosition > 1 ? styler.getPlotBackgroundColor() : seriesColor;
g.setColor(styler.getLabelsFontColor(contrastBackgroundColor));
} else {
g.setColor(styler.getLabelsFontColor());
}
Expand Down
10 changes: 7 additions & 3 deletions xchart/src/main/java/org/knowm/xchart/style/CategoryStyler.java
Original file line number Diff line number Diff line change
Expand Up @@ -211,16 +211,20 @@ public double getLabelsPosition() {
}

/**
* A number between 0 and 1 setting the vertical position of the data label. Default is 0.5
* Sets the vertical position of the data label relative to the bar. For values between 0 and 1 the
* label is drawn inside the bar as a fraction of its height: 0 places it at the baseline and 1 at
* the top of the bar. Values greater than 1 draw the label outside the bar (above positive bars,
* below negative bars) at a fixed pixel gap that does not scale with the bar's height, where the
* gap is (labelsPosition - 1) * 100 pixels (e.g. 1.1 places it 10px outside). Default is 0.5,
* placing it in the center.
*
* @param labelsPosition
* @return
*/
public CategoryStyler setLabelsPosition(double labelsPosition) {

if (labelsPosition < 0 || labelsPosition > 1) {
throw new IllegalArgumentException("Annotations position must between 0 and 1!!!");
if (labelsPosition < 0) {
throw new IllegalArgumentException("Labels position must be greater than or equal to 0!!!");
}
this.labelsPosition = labelsPosition;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,19 @@ public double getLabelsPosition() {
}

/**
* A number between 0 and 1 setting the vertical position of the data label. Default is 0.5
* placing it in the center.
* Sets the horizontal position of the data label relative to the bar. For values between 0 and 1
* the label is drawn inside the bar as a fraction of its length: 0 places it at the baseline and 1
* at the end of the bar. Values greater than 1 draw the label outside the bar (beyond the end) at
* a fixed pixel gap that does not scale with the bar's length, where the gap is (labelsPosition -
* 1) * 100 pixels (e.g. 1.1 places it 10px outside). Default is 0.5, placing it in the center.
*
* @param labelsPosition
* @return
*/
public HorizontalBarStyler setLabelsPosition(double labelsPosition) {

if (labelsPosition < 0 || labelsPosition > 1) {
throw new IllegalArgumentException("Annotations position must between 0 and 1!!!");
if (labelsPosition < 0) {
throw new IllegalArgumentException("Labels position must be greater than or equal to 0!!!");
}
this.labelsPosition = labelsPosition;
return this;
Expand Down
Loading