Customizing Pie Chart Labels And Orthogonal Connector Lines In Repertoire Designer 202x

Overview

Repertoire Designer 202x automatically determines where pie-chart labels and their connector lines are displayed. This automatic arrangement is appropriate for most charts, but reports containing closely positioned sections may require individual label adjustments.


Understanding The Connector Layout

The script draws each connector using three orthogonal segments:

Pie boundary ── firstRun ──┐
                           │
                           └── endRun ── labelXOffset ── Label
  • firstRun controls the first horizontal segment.
  • The vertical segment joins the first and final horizontal segments.
  • endRun controls the final horizontal line.
  • labelXOffset moves only the literal label, creating a gap without changing the connector length.

Step 1: Access The Chart Script

In Repertoire Designer 202x, select the required pie chart. Launch the Chart Wizard and navigate to the Script tab.


Step 2: Understand The Available Settings

Each pie section can have its own configuration.

Setting Description
side Forces the label to the "left" or "right" side.
anchorDegrees Defines where the connector begins on the pie boundary.
yRatio Positions the label vertically as a proportion of the available chart height.
yOffset Moves the connector and label vertically. Negative values move upward; positive values move downward.
firstRun Sets the exact length of the first horizontal connector segment.
endRun Sets the exact length of the final horizontal connector segment.
labelXOffset Moves only the literal label horizontally without changing the line length.

The angle values follow this approximate orientation:

             90°
              ↑

180°  ←   Pie chart   →  0°

              ↓
             270°

For example, 230.0 selects a point in the lower-left region of the pie.


Step 3: Add The Pie-Chart Script

Add the following script to the Script tab:

var chart = Object.getPrototypeOf(this);
var oldPlot = chart.getPlot();
var JMath = java.lang.Math;
var GeneralPath = java.awt.geom.GeneralPath;
var RectangleAnchor = Packages.org.jfree.ui.RectangleAnchor;

// Default settings used when a section does not define its own value.
var defaults = {
    firstRun: 15.0,
    endRun: 25.0,
    labelXOffset: 0.0,
    yOffset: 0.0
};

// Configure individual sections using their exact datasource keys.
var links = {
    "Product A": {
        yOffset: 20.0,
        firstRun: 40.0,
        endRun: 70.0,
        labelXOffset: 0.0
    },

    "Product B": {
        side: "left",
        anchorDegrees: 230.0,
        yRatio: 0.88,
        yOffset: 20.0,
        firstRun: 30.0,
        endRun: 70.0,
        labelXOffset: 0.0
    },

    "Product C": {
        yOffset: 20.0,
        firstRun: 30.0,
        endRun: 70.0,
        labelXOffset: 0.0
    },

    "Product D": {
        yOffset: -10.0,
        firstRun: 40.0,
        endRun: 70.0,
        labelXOffset: 0.0
    },

    "Other": {
        yOffset: -10.0,
        firstRun: 40.0,
        endRun: 70.0,
        labelXOffset: 0.0
    }
};

function setting(key, name) {
    var item = links[String(key)];

    return item != null && item[name] != null
        ? item[name]
        : defaults[name];
}

function drawLink(plotRef, g2, state, record, rightSide) {
    var key = String(record.getKey());
    var degrees = setting(key, "anchorDegrees");

    var theta = degrees == null
        ? record.getAngle()
        : JMath.toRadians(degrees);

    // Determine where the connector begins on the pie boundary.
    var linkX = state.getPieCenterX()
        + JMath.cos(theta)
        * state.getPieWRadius()
        * record.getLinkPercent();

    var linkY = state.getPieCenterY()
        - JMath.sin(theta)
        * state.getPieHRadius()
        * record.getLinkPercent();

    // Determine the vertical position of the connector and label.
    var ratio = setting(key, "yRatio");

    var targetY = ratio == null
        ? record.getAllocatedY()
        : state.getLinkArea().getMinY()
            + state.getLinkArea().getHeight() * ratio;

    targetY += Number(setting(key, "yOffset"));

    var labelOffset = Number(setting(key, "labelXOffset"));
    var firstRun = Number(setting(key, "firstRun"));
    var endRun = Number(setting(key, "endRun"));

    // Calculate the connector and label positions independently.
    var direction = rightSide ? 1.0 : -1.0;
    var elbowX = linkX + direction * firstRun;
    var lineEndX = elbowX + direction * endRun;
    var labelX = lineEndX + direction * labelOffset;

    if (plotRef.getLabelLinksVisible()) {
        var oldPaint = g2.getPaint();
        var oldStroke = g2.getStroke();
        var path = new GeneralPath();

        g2.setPaint(plotRef.getLabelLinkPaint());
        g2.setStroke(plotRef.getLabelLinkStroke());

        path.moveTo(linkX, linkY);
        path.lineTo(elbowX, linkY);
        path.lineTo(elbowX, targetY);
        path.lineTo(lineEndX, targetY);

        g2.draw(path);

        g2.setPaint(oldPaint);
        g2.setStroke(oldStroke);
    }

    record.getLabel().draw(
        g2,
        labelX,
        targetY,
        rightSide
            ? RectangleAnchor.LEFT
            : RectangleAnchor.RIGHT
    );
}

var newPlot = new JavaAdapter(
    Packages.org.jfree.chart.plot.PiePlot,
    {
        // Start each connector at the pie-section boundary.
        getLabelLinkDepth: function () {
            return 0.0;
        },

        drawLeftLabel: function (g2, state, record) {
            drawLink(
                this,
                g2,
                state,
                record,
                setting(record.getKey(), "side") == "right"
            );
        },

        drawRightLabel: function (g2, state, record) {
            drawLink(
                this,
                g2,
                state,
                record,
                setting(record.getKey(), "side") != "left"
            );
        }
    },
    oldPlot.getDataset()
);

// Retain essential settings configured through the Chart Wizard.
newPlot.setStartAngle(oldPlot.getStartAngle());
newPlot.setDirection(oldPlot.getDirection());
newPlot.setInteriorGap(oldPlot.getInteriorGap());

newPlot.setLabelGenerator(
    oldPlot.getLabelGenerator()
    || new Packages.org.jfree.chart.labels.StandardPieSectionLabelGenerator(
        "{0}: {1} ({2})"
    )
);

newPlot.setLabelFont(oldPlot.getLabelFont());
newPlot.setLabelPaint(oldPlot.getLabelPaint());
newPlot.setLabelBackgroundPaint(
    oldPlot.getLabelBackgroundPaint()
);
newPlot.setLabelOutlinePaint(
    oldPlot.getLabelOutlinePaint()
);
newPlot.setLabelLinkPaint(oldPlot.getLabelLinkPaint());
newPlot.setLabelLinkStroke(oldPlot.getLabelLinkStroke());

newPlot.setLabelLinksVisible(true);
newPlot.setSimpleLabels(false);

// Preserve the existing section colours.
var keys = oldPlot.getDataset().getKeys();

for (var i = 0; i < keys.size(); i++) {
    var key = keys.get(i);
    var paint = oldPlot.getSectionPaint(key);

    if (paint != null) {
        newPlot.setSectionPaint(key, paint);
    }
}

// Replace the existing PiePlot.
oldPlot.removeChangeListener(chart);
newPlot.addChangeListener(chart);

var field = chart.getClass().getDeclaredField("plot");
field.setAccessible(true);
field.set(chart, newPlot);

Step 4: Configure Individual Sections

Force A Label To One Side

Use side to override automatic label allocation:

"Product B": {
    side: "left"
}

Supported values are:

side: "left"
side: "right"

If side is omitted, the label remains on the side selected automatically by the chart.

Change The Connector Starting Point

Use anchorDegrees to select where the connector begins:

"Product B": {
    side: "left",
    anchorDegrees: 230.0
}

This changes the connector origin without rotating the chart or changing the positions of the pie sections.

Adjust The Vertical Position

Use yOffset for a relative adjustment:

"Product C": {
    yOffset: 20.0
}
  • Positive values move the label downward.
  • Negative values move the label upward.
  • 0.0 retains the calculated position.

Use yRatio when a more predictable position is required:

"Product B": {
    yRatio: 0.88
}

Typical values are between 0.0 and 1.0:

  • 0.0 represents the top of the available label area.
  • 0.5 represents the centre.
  • 1.0 represents the bottom.

The yOffset value is applied after yRatio.

Set The Connector Segment Lengths

Configure the two horizontal segments independently:

"Product A": {
    firstRun: 40.0,
    endRun: 70.0
}

Increasing firstRun lengthens the segment beginning at the pie boundary. Increasing endRun lengthens the final segment leading toward the label.

No automatic clamping is applied, so large values may place labels outside the chart area.

Move Only The Literal Label

Use labelXOffset to create a gap between the connector endpoint and the label:

"Product A": {
    firstRun: 40.0,
    endRun: 70.0,
    labelXOffset: 15.0
}

The connector length remains unchanged. Only the label moves horizontally.

Use:

labelXOffset: 0.0

to position the label directly at the connector endpoint.


Step 5: Use Default Values

The defaults object provides fallback values:

var defaults = {
    firstRun: 15.0,
    endRun: 25.0,
    labelXOffset: 0.0,
    yOffset: 0.0
};

A section only needs to define properties that differ from these defaults:

"Product D": {
    yOffset: -10.0
}

In this example, Product D uses firstRun, endRun, and labelXOffset from defaults.


Important Considerations

  • The script disables simple labels and uses outside labels with connector lines.
  • Individual positioning overrides the normal label presentation, values may require adjustment when the chart dimensions change.
  • Large firstRun, endRun, labelXOffset, or yOffset values may move content beyond the available chart area.
  • Labels forced to another side are positioned independently of the standard automatic distribution. Review them for possible overlaps.

What This Guide Achieves

  • Provides independent positioning for each pie-section label.
  • Controls both horizontal segments of every orthogonal connector.
  • Moves literal labels independently from their connector lines.
  • Prevents connector lines from beginning inside the pie.
  • Preserves the datasource, section colours, and essential label formatting.