Search Results for

    Show / Hide Table of Contents

    Custom RenderableSeries API

    If the built-in chart types in SciChart are not enough, you can create your own RenderableSeries. Custom RenderableSeries should extend BaseRenderableSeries if you want to provide some custom data, or one of predefined base classes if you want to display data from one of default IDataSeries<TX,TY> implementations.

    Base class for Custom RenderableSeries When to use
    XyRenderableSeriesBase If you want to use XyDataSeries<TX,TY> as data source for custom RenderableSeries
    XyyRenderableSeriesBase If you want to use XyyDataSeries<TX,TY> as data source for custom RenderableSeries
    HlRenderableSeriesBase If you want to use HlDataSeries<TX,TY> as data source for custom RenderableSeries
    OhlcRenderableSeriesBase If you want to use OhlcDataSeries<TX,TY> as data source for custom RenderableSeries
    XyzRenderableSeriesBase If you want to use XyzDataSeries<TX,TY,TZ> as data source for custom RenderableSeries
    BaseRenderableSeries If default IDataSeries<TX,TY> implementations aren't suitable for data which should be displayed and you want to create custom DataSeries type

    Creating your Own Series

    For example, let's try to create a RenderableSeries, which draws PointMarker at specified [x, y] coordinates. Since, XyDataSeries<TX,TY> is enough to define [x, y] data we will extend XyRenderableSeriesBase:

    • Java
    • Java with Builders API
    • Kotlin
    class RoundedColumnsRenderableSeries extends FastColumnRenderableSeries {
    
        private final FloatValues topEllipseBuffer = new FloatValues();
        private final FloatValues rectsBuffer = new FloatValues();
        private final FloatValues bottomEllipseBuffer = new FloatValues();
    
        public RoundedColumnsRenderableSeries() {
            super(new ColumnRenderPassData(), new ColumnHitProvider(), new NearestColumnPointProvider());
        }
    
        @Override
        protected void disposeCachedData() {
            super.disposeCachedData();
    
            topEllipseBuffer.disposeItems();
            rectsBuffer.disposeItems();
            bottomEllipseBuffer.disposeItems();
        }
    
        @Override
        protected void internalDraw(IRenderContext2D renderContext, IAssetManager2D assetManager, ISeriesRenderPassData renderPassData) {
            // Don't draw transparent series
            final float opacity = getOpacity();
            if (opacity == 0) return;
    
            final BrushStyle fillBrush = getFillBrushStyle();
            if (fillBrush == null || !fillBrush.isVisible()) return;
    
            final ColumnRenderPassData rpd = (ColumnRenderPassData) renderPassData;
            final float diameter = rpd.columnPixelWidth;
            updateDrawingBuffers(rpd, diameter, rpd.zeroLineCoord);
    
            final IBrush2D brush = assetManager.createBrush(fillBrush);
            renderContext.fillRects(rectsBuffer.getItemsArray(), 0, rectsBuffer.size(), brush);
            renderContext.drawEllipses(topEllipseBuffer.getItemsArray(), 0, topEllipseBuffer.size(), diameter, diameter, brush);
            renderContext.drawEllipses(bottomEllipseBuffer.getItemsArray(), 0, bottomEllipseBuffer.size(), diameter, diameter, brush);
        }
    
        private void updateDrawingBuffers(ColumnRenderPassData renderPassData, float columnPixelWidth, float zeroLine) {
            final float halfWidth = columnPixelWidth / 2;
    
            topEllipseBuffer.setSize(renderPassData.pointsCount() * 2);
            rectsBuffer.setSize(renderPassData.pointsCount() * 4);
            bottomEllipseBuffer.setSize(renderPassData.pointsCount() * 2);
    
            final float[] topArray = topEllipseBuffer.getItemsArray();
            final float[] rectsArray = rectsBuffer.getItemsArray();
            final float[] bottomArray = bottomEllipseBuffer.getItemsArray();
    
            final float[] xCoordsArray = renderPassData.xCoords.getItemsArray();
            final float[] yCoordsArray = renderPassData.yCoords.getItemsArray();
            for (int i = 0, count = renderPassData.pointsCount(); i < count; i++) {
                final float x = xCoordsArray[i];
                final float y = yCoordsArray[i];
    
                topArray[i * 2] = x;
                topArray[i * 2 + 1] = y - halfWidth;
    
                rectsArray[i * 4] = x - halfWidth;
                rectsArray[i * 4 + 1] = y - halfWidth;
                rectsArray[i * 4 + 2] = x + halfWidth;
                rectsArray[i * 4 + 3] = zeroLine + halfWidth;
    
                bottomArray[i * 2] = x;
                bottomArray[i * 2 + 1] = zeroLine + halfWidth;
            }
        }
    }
    
    class RoundedColumnsRenderableSeries extends FastColumnRenderableSeries {
    
        private final FloatValues topEllipseBuffer = new FloatValues();
        private final FloatValues rectsBuffer = new FloatValues();
        private final FloatValues bottomEllipseBuffer = new FloatValues();
    
        public RoundedColumnsRenderableSeries() {
            super(new ColumnRenderPassData(), new ColumnHitProvider(), new NearestColumnPointProvider());
        }
    
        @Override
        protected void disposeCachedData() {
            super.disposeCachedData();
    
            topEllipseBuffer.disposeItems();
            rectsBuffer.disposeItems();
            bottomEllipseBuffer.disposeItems();
        }
    
        @Override
        protected void internalDraw(IRenderContext2D renderContext, IAssetManager2D assetManager, ISeriesRenderPassData renderPassData) {
            // Don't draw transparent series
            final float opacity = getOpacity();
            if (opacity == 0) return;
    
            final BrushStyle fillBrush = getFillBrushStyle();
            if (fillBrush == null || !fillBrush.isVisible()) return;
    
            final ColumnRenderPassData rpd = (ColumnRenderPassData) renderPassData;
            final float diameter = rpd.columnPixelWidth;
            updateDrawingBuffers(rpd, diameter, rpd.zeroLineCoord);
    
            final IBrush2D brush = assetManager.createBrush(fillBrush);
            renderContext.fillRects(rectsBuffer.getItemsArray(), 0, rectsBuffer.size(), brush);
            renderContext.drawEllipses(topEllipseBuffer.getItemsArray(), 0, topEllipseBuffer.size(), diameter, diameter, brush);
            renderContext.drawEllipses(bottomEllipseBuffer.getItemsArray(), 0, bottomEllipseBuffer.size(), diameter, diameter, brush);
        }
    
        private void updateDrawingBuffers(ColumnRenderPassData renderPassData, float columnPixelWidth, float zeroLine) {
            final float halfWidth = columnPixelWidth / 2;
    
            topEllipseBuffer.setSize(renderPassData.pointsCount() * 2);
            rectsBuffer.setSize(renderPassData.pointsCount() * 4);
            bottomEllipseBuffer.setSize(renderPassData.pointsCount() * 2);
    
            final float[] topArray = topEllipseBuffer.getItemsArray();
            final float[] rectsArray = rectsBuffer.getItemsArray();
            final float[] bottomArray = bottomEllipseBuffer.getItemsArray();
    
            final float[] xCoordsArray = renderPassData.xCoords.getItemsArray();
            final float[] yCoordsArray = renderPassData.yCoords.getItemsArray();
            for (int i = 0, count = renderPassData.pointsCount(); i < count; i++) {
                final float x = xCoordsArray[i];
                final float y = yCoordsArray[i];
    
                topArray[i * 2] = x;
                topArray[i * 2 + 1] = y - halfWidth;
    
                rectsArray[i * 4] = x - halfWidth;
                rectsArray[i * 4 + 1] = y - halfWidth;
                rectsArray[i * 4 + 2] = x + halfWidth;
                rectsArray[i * 4 + 3] = zeroLine + halfWidth;
    
                bottomArray[i * 2] = x;
                bottomArray[i * 2 + 1] = zeroLine + halfWidth;
            }
        }
    
        class Builder {
            private final DisplayMetrics displayMetrics;
            private final RoundedColumnsRenderableSeries renderableSeries;
    
            public Builder(Context context) {
                this.displayMetrics = context.getResources().getDisplayMetrics();
                this.renderableSeries = new RoundedColumnsRenderableSeries();
            }
    
            public Builder withDataSeries(IDataSeries<?,?> dataSeries) {
                renderableSeries.setDataSeries(dataSeries);
                return this;
            }
    
            public Builder withFillColor(int fillColor) {
                renderableSeries.setFillBrushStyle(new SolidBrushStyle(fillColor));
                return this;
            }
    
            public RoundedColumnsRenderableSeries build() {
                return renderableSeries;
            }
        }
    }
    
    class RoundedColumnsRenderableSeries : FastColumnRenderableSeries(ColumnRenderPassData(), ColumnHitProvider(), NearestColumnPointProvider()) {
        private val topEllipseBuffer = FloatValues()
        private val rectsBuffer = FloatValues()
        private val bottomEllipseBuffer = FloatValues()
    
        override fun disposeCachedData() {
            super.disposeCachedData()
    
            topEllipseBuffer.disposeItems()
            rectsBuffer.disposeItems()
            bottomEllipseBuffer.disposeItems()
        }
    
        override fun internalDraw(renderContext: IRenderContext2D, assetManager: IAssetManager2D, renderPassData: ISeriesRenderPassData) {
            // Don't draw transparent series
            if (opacity == 0f) return
    
            val fillBrush = fillBrushStyle
            if (fillBrush == null || !fillBrush.isVisible) return
    
            val rpd = renderPassData as ColumnRenderPassData
            val diameter = rpd.columnPixelWidth
            updateDrawingBuffers(rpd, diameter, rpd.zeroLineCoord)
    
            val brush = assetManager.createBrush(fillBrush)
            renderContext.fillRects(rectsBuffer.itemsArray, 0, rectsBuffer.size(), brush)
            renderContext.drawEllipses(topEllipseBuffer.itemsArray, 0, topEllipseBuffer.size(), diameter, diameter, brush)
            renderContext.drawEllipses(bottomEllipseBuffer.itemsArray, 0, bottomEllipseBuffer.size(), diameter, diameter, brush)
        }
    
        private fun updateDrawingBuffers(renderPassData: ColumnRenderPassData, columnPixelWidth: Float, zeroLine: Float) {
            val halfWidth = columnPixelWidth / 2
    
            topEllipseBuffer.setSize(renderPassData.pointsCount() * 2)
            rectsBuffer.setSize(renderPassData.pointsCount() * 4)
            bottomEllipseBuffer.setSize(renderPassData.pointsCount() * 2)
    
            val topArray = topEllipseBuffer.itemsArray
            val rectsArray = rectsBuffer.itemsArray
            val bottomArray = bottomEllipseBuffer.itemsArray
    
            val xCoordsArray = renderPassData.xCoords.itemsArray
            val yCoordsArray = renderPassData.yCoords.itemsArray
            for (i in 0 until renderPassData.pointsCount()) {
                val x = xCoordsArray[i]
                val y = yCoordsArray[i]
    
                topArray[i * 2] = x
                topArray[i * 2 + 1] = y - halfWidth
    
                rectsArray[i * 4] = x - halfWidth
                rectsArray[i * 4 + 1] = y - halfWidth
                rectsArray[i * 4 + 2] = x + halfWidth
                rectsArray[i * 4 + 3] = zeroLine + halfWidth
    
                bottomArray[i * 2] = x
                bottomArray[i * 2 + 1] = zeroLine + halfWidth
            }
        }
    }
    

    As showed in the code above, the main method, which has to be implemented is the internalDraw, which allows you to perform any custom drawings you want. The IRenderContext2D and IAssetManager2D are passed into it, which should be used to draw to the screen and which are the parts of the graphics context for this render pass.

    The current Data to Draw - ISeriesRenderPassData protocol

    The data to draw is contained in the ISeriesRenderPassData passed in to the internalDraw method. Using the RenderPassData object you can access the data values and coordinates to draw, the xPointRange (the indices of the data to draw, inclusive), the X and Y Coordinate Calculators, that transforms data to pixel coordinates. All the above accessed through the following properties:

    • yValues (other RenderPassData types might have other properties)
    • yCoords (other RenderPassData types might have other properties)
    • xPointRange
    • getXCoordinateCalculator()
    • getYCoordinateCalculator()

    Depending on DataSeries type you can have a different ISeriesRenderPassData type and different ways to access the data to draw.

    Series RenderPassData Type DataSeries type used
    XyRenderPassData If you want to use XyDataSeries<TX,TY> as data source for custom RenderableSeries
    XyyRenderPassData If you want to use XyyDataSeries<TX,TY> as data source for custom RenderableSeries
    XyzRenderPassData If you want to use XyzDataSeries<TX,TY,TZ> as data source for custom RenderableSeries
    OhlcRenderPassData If you want to use OhlcDataSeries<TX,TY> as data source for custom RenderableSeries
    HlRenderPassData If you want to use HlDataSeries<TX,TY> as data source for custom RenderableSeries
    UniformHeatmapRenderPassData If you want to use UniformHeatmapDataSeries<TX,TY,TZ> as data source for custom RenderableSeries

    The types above could be extended to add some additional information which is required for rendering, e.g. ColumnRenderPassData extends XyRenderPassData and adds fields for caching of column width in pixels and coordinate of zero line.

    Example: RoundedColumnRenderableSeries

    We have an example which shows how to create a rounded column series with this powerful API.

    Rounded Column Chart Example

    Note

    The full Rounded Column Chart example can be found in the SciChart Android Examples Suite as well as on GitHub:

    • Native Example
    • Xamarin Example
    Back to top © 2011-2025 SciChart. All rights reserved. | sitemap.xml