SciChart supports axes plaсed inside the chart area, a.k.a in the center of the chart. This behavior is controlled by ISCIAxis.isCenterAxis. By default, ISCIAxis.isCenterAxis is set to false, and axes are drawing outside the chart. Here is the difference:
isCenterAxis == false
isCenterAxis == true
The above cases are quite simple, whereas you can create completely custom layouts and specify the exact axis position inside the chart area. It requires changes to the layout process in ISCILayoutManager. Please read on to learn how to do such an advanced layout.
The Layout System
So let’s dig into layout system a little bit. There is a Default implementation of ISCILayoutManager - SCIDefaultLayoutManager, and it’s responsible for positioning axes on a chart.
The layout process consists of two passes for every axis, a measure pass and a layout pass. The ISCILayoutManager conducts this process, doing calculations and providing all the necessary data to its ISCIAxisLayoutStrategy fields. Every AxisLayoutStrategy field handles a specific SCIAxisAlignment case and is responsible for placing axes with corresponding Alignment inside the boundaries provided by the ISCILayoutManager.
during the measure and layout passes and returns evaluated viewport size as the result.
The layout process is triggered by the SCIChartSurface in response to different state changes. It is an essential part of a render process.
Customizing Layout of Axes
Usually you don’t need to create a custom ISCILayoutManager, but rather to provide a custom ISCIAxisLayoutStrategy for a specific SCIAxisAlignment mode, e.g. for bottom axes layout or right axes layout, or both. It is possible to extend an existing ISCIAxisLayoutStrategy class and change its behavior to better serve your purposes. Please find a list of the strategies out of the box below:
You might want to have your axis exactly at the center of the chart. Let’s say the X-Axis. To do just that, we can extend the SCITopAlignmentOuterAxisLayoutStrategy class (or any suitable horizontal strategy) and make the stack of the top-aligned X axes to start exactly at the center of a chart. Let’s try to build that and apply the newly created layout strategy to a SCIDefaultLayoutManager.topInnerAxisLayoutStrategy and then - to the SCIChartSurface:
@interface CenteredAxisLayoutStrategy: SCITopAlignmentInnerAxisLayoutStrategy
@end
@implementation CenteredAxisLayoutStrategy {
CGFloat _chartAreaWidth;
CGFloat _chartAreaHeight;
}
- (void)measureAxesWithAvailableWidth:(CGFloat)width height:(CGFloat)height andChartLayoutState:(SCIChartLayoutState *)chartLayoutState {
[super measureAxesWithAvailableWidth:width height:height andChartLayoutState:chartLayoutState];
_chartAreaWidth = width;
_chartAreaHeight = height;
}
- (void)layoutWithLeft:(CGFloat)left top:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom {
// pin the stack of the top-aligned X Axes to the center of a chart
CGFloat topCoord = _chartAreaHeight / 2;
[SCIHorizontalAxisLayoutStrategy layoutAxesFromTopToBottom:self.axes withLeft:left top:topCoord right:right];
}
@end
…
// Set place axis appropriately for “Top-Inner” Axis Strategy
xAxis.isCenterAxis = YES;
xAxis.axisAlignment = SCIAxisAlignment_Top;
…
// Create and set new Layout Strategy
SCIDefaultLayoutManager *layoutManager = [SCIDefaultLayoutManager new];
layoutManager.topInnerAxisLayoutStrategy = [CenteredAxisLayoutStrategy new];
self.surface.layoutManager = layoutManager;
class CenteredAxisLayoutStrategy: SCITopAlignmentInnerAxisLayoutStrategy {
private var chartAreaWidth: CGFloat!
private var chartAreaHeight: CGFloat!
override func measureAxes(withAvailableWidth width: CGFloat, height: CGFloat, andChartLayoutState chartLayoutState: SCIChartLayoutState!) {
super.measureAxes(withAvailableWidth: width, height: height, andChartLayoutState: chartLayoutState)
self.chartAreaWidth = width
self.chartAreaHeight = height
}
override func layout(withLeft left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat) {
// pin the stack of the top-aligned X Axes to the center of a chart
let topCoord = chartAreaHeight / 2
SCIHorizontalAxisLayoutStrategy.layoutAxesFromTop(toBottom: self.axes as? [ISCIAxis], withLeft: left, top: topCoord, right: right)
}
}
…
// Set place axis appropriately for “Top-Inner” Axis Strategy
xAxis.isCenterAxis = true
xAxis.axisAlignment = .top
…
// Create and set new Layout Strategy
let layoutManager = SCIDefaultLayoutManager()
layoutManager.topInnerAxisLayoutStrategy = CenteredAxisLayoutStrategy()
surface.layoutManager = layoutManager
class CenteredAxisLayoutStrategy : SCITopAlignmentInnerAxisLayoutStrategy
{
private nfloat chartAreaWidth;
private nfloat chartAreaHeight;
public override void MeasureAxesWithAvailableWidth(nfloat width, nfloat height, SCIChartLayoutState chartLayoutState)
{
base.MeasureAxesWithAvailableWidth(width, height, chartLayoutState);
chartAreaWidth = width;
chartAreaHeight = height;
}
public override void LayoutWithLeft(nfloat left, nfloat top, nfloat right, nfloat bottom)
{
// pin the stack of the top-aligned X Axes to the center of a chart
nfloat topCoord = chartAreaHeight / 2;
SCIHorizontalAxisLayoutStrategy.LayoutAxesFromTopToBottom(Axes, left, topCoord, right);
}
}
…
// Set place axis appropriately for “Top-Inner” Axis Strategy
xAxis.IsCenterAxis = true;
xAxis.AxisAlignment = SCIAxisAlignment.Top;
…
// Create and set new Layout Strategy
var layoutManager = new SCIDefaultLayoutManager();
layoutManager.TopInnerAxisLayoutStrategy = new CenteredAxisLayoutStrategy();
Surface.LayoutManager = layoutManager;
As the result, the X Axis should be placed at the center of a chart. It should look very similar to this:
In this case each axis is pinned to 0 value of the other axis. To achieve this, the pixel coordinate of the 0 data value is looked for during every layout pass, using the Coordinate Transformation API:
@interface CenteredTopAlignmentInnerAxisLayoutStrategy : SCITopAlignmentInnerAxisLayoutStrategy
- (instancetype)initWithYAxis:(id)yAxis;
@end
@implementation CenteredTopAlignmentInnerAxisLayoutStrategy {
id _yAxis;
}
- (instancetype)initWithYAxis:(id)yAxis {
self = [super init];
if (self) {
_yAxis = yAxis;
}
return self;
}
- (void)layoutWithLeft:(CGFloat)left top:(CGFloat)top right:(CGFloat)right bottom:(CGFloat)bottom {
// find the coordinate of 0 on the Y Axis in pixels
// place the stack of the top-aligned X Axes at this coordinate
CGFloat topCoord = [_yAxis.currentCoordinateCalculator getCoordinate:0];
[SCIHorizontalAxisLayoutStrategy layoutAxesFromTopToBottom:self.axes withLeft:left top:topCoord right:right];
}
@end
class CenteredTopAlignmentInnerAxisLayoutStrategy: SCITopAlignmentInnerAxisLayoutStrategy {
let yAxis: ISCIAxis;
init(yAxis: ISCIAxis) {
self.yAxis = yAxis
}
override func layout(withLeft left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat) {
// find the coordinate of 0 on the Y Axis in pixels
// place the stack of the top-aligned X Axes at this coordinate
let topCoord = CGFloat(yAxis.currentCoordinateCalculator.getCoordinateFrom(0))
SCIHorizontalAxisLayoutStrategy.layoutAxesFromTop(toBottom: self.axes as? [ISCIAxis], withLeft: left, top: topCoord, right: right)
}
}
class CenteredTopAlignmentInnerAxisLayoutStrategy : SCITopAlignmentInnerAxisLayoutStrategy
{
private IISCIAxis yAxis;
CenteredTopAlignmentInnerAxisLayoutStrategy(IISCIAxis yAxis)
{
this.yAxis = yAxis;
}
public override void LayoutWithLeft(nfloat left, nfloat top, nfloat right, nfloat bottom)
{
// find the coordinate of 0 on the Y Axis in pixels
// place the stack of the top-aligned X Axes at this coordinate
var topCoord = yAxis.CurrentCoordinateCalculator.GetCoordinate(0);
SCIHorizontalAxisLayoutStrategy.LayoutAxesFromTopToBottom(Axes, left, topCoord, right);
}
}
As it can be seen in the example, both axes are pinned to a given data value, not to a specific position inside the chart area. Thus they will adjust their positions accordingly to VisibleRange changes. Please refer to the Shifted Axes Example to find the complete code sample.