SciChart® the market leader in Fast WPF Charts, WPF 3D Charts, and iOS Chart & Android Chart Components



We have an app where we need to specify a different width for each column. I have attached an image of the result we want. We are currently using “SCIFastColumnRenderableSeries” for the diagram, but all columns have a fixed width and I couldn’t find any way to modify it.

Do you have any suggestions how to accomplish this?


  • You must to post comments

Achieved the desired behaviour by implementing our custom renderable series. The key idea is to have the series plotted on a value based X-axis (numeric, date, etc.). The custom renderable series will use the default “SCIXyRenderPassData” to get the X axis tick coordinates. It will then draw the bars from one tick to the next one.

The implementation:

class FlexibleColumnWidthRenderableSeries: SCIXyRenderableSeriesBase {

/// In default constructor we will use:
/// - SCIXyRenderPassData which will store points to draw ( If you need to store some additional data for drawing you can extend it and add additional fields )
/// - SCIPointMarkerHitProvider which performs hit checks on points rendered by series. In our case we just check if point marker is hit
/// - SCINearestXyPointProvider allows to locate nearest (x, y) point to specified point on screen
override init() {
        renderPassData: SCIXyRenderPassData(),
        hitProvider: SCIPointMarkerHitProvider(),
        nearestPointProvider: SCINearestXyPointProvider()

override func internalDraw(
    with renderContext: ISCIRenderContext2D!,
    assetManager: ISCIAssetManager2D!,
    renderPassData: ISCISeriesRenderPassData!) {

    guard self.opacity != 0 else { return }

    guard let fillProvider = paletteProvider as? ISCIFillPaletteProvider else {
        // this can be changed in the future if we want to support `strokeStyle`
        assertionFailure("this renderable series expects a fill pallete provider")

    /// The render pass data which we created in constructor
    let rdp: SCIXyRenderPassData! = renderPassData as? SCIXyRenderPassData
    let rects = getRectsForDrawing(renderPassData: rdp)

    rects.enumerated().forEach { index, rect in
        let color = fillProvider.fillColors.getValueAt(index)
        let brush = assetManager.brush(with: SCISolidBrushStyle(colorCode: color))
        renderContext.fill(rect, with: brush)

private func getRectsForDrawing(renderPassData: SCIXyRenderPassData) -> [CGRect] {

    /// Ensures the chart's smallest bar has at least a minimum height
    let maxY = getMaxY(forRenderPassData: renderPassData)

    func computeRect(startingAtPoint point: Int) -> CGRect {

        let xCoord = CGFloat(renderPassData.xCoords.getValueAt(point))
        let yCoord = min(CGFloat(renderPassData.yCoords.getValueAt(point)), maxY)
        let nextXCoord = CGFloat(renderPassData.xCoords.getValueAt(point + 1))
        let height = renderPassData.viewportSize.height - yCoord
        let columnWidth = nextXCoord - xCoord

        // here we might want to support `zeroLineY` into the computation for  Y position
        return CGRect(
            x: xCoord,
            y: yCoord,
            width: columnWidth,
            height: height

    return (0 ..< renderPassData.pointsCount - 1).map { computeRect(startingAtPoint: $0) }

/// Computes the maximum value an Y coordinate can have in the chart such that we don't have one pixel bars.
/// SciChart computes the Y coordinates by comparing the data we provide in the `dataSeries`.
/// Therefore, the minimum and maximum Y values in the `dataSeries` will have a corresponding Y coordinate to the top and bottom of the viewport.
/// This is how they are internally computed by SciChart's `SCIXyRenderPassData` which we used in our `init()`.
/// - Parameter rdp: The render pass data we provided in the init
/// - Returns: The max Y coordinate allowed on the chart to ensure we don't have 1 pixel-height columns.
private func getMaxY(forRenderPassData rdp: SCIXyRenderPassData) -> CGFloat {
    let yCoords = Set(rdp.yCoords.itemsArray).sorted()
    let columnHeightDelta: CGFloat = 1
    var maxY = CGFloat(yCoords.last ?? 0)
    if rdp.viewportSize.height - maxY <= columnHeightDelta, yCoords.count > 1 {
        let nextY = CGFloat(yCoords.suffix(2).first!)
        maxY -= (maxY - nextY) / 3
    return maxY


  • You must to post comments
Showing 1 result
Your Answer

Please first to submit.