Search Results for

    Show / Hide Table of Contents

    Axis Labels - Formatting for trading charts

    If you develop some trading application, most likely, your chart will display some OHLC prices and indicators along with the corresponding date labels on X-Axis. In SciChart Axis Labels - LabelProvider API helps you to present these labels in the desired format. Quite a popular scenario in trading charts is when date labels formatting changes depending on the current zoom level, so the deeper zoom level is, the more detailed dates appear.

    Note

    Example of the Trading Charts Label Formatter usage can be found in the Multi-Pane Stock Chart example in the SciChart Android Examples Suite as well as on GitHub:

    • Native Example
    • Xamarin Example

    The most suitable type of X-Axis for trading charts is CategoryDateAxis. It uses TradeChartAxisLabelProvider to dynamically change its Text and Cursor Labels depending on Data-value and current zoom.

    To format labels the TradeChartAxisLabelProvider class uses TradeChartAxisLabelFormatter<T> which incapsulates two formatters:

    • CalendarUnitDateFormatter - for axis labels
    • CursorCalendarUnitDateFormatter - for CursorModifier with axis labels.

    Each of them formats dates depending on the current granularity using format(Date date, int calendarUnit) method. By default, there are few predefined formatters, so you will get dynamically changing axis labels out-of-the-box. In case, the default one doesn't meet your requirements, you can provide your own and customize your label provider. Please, continue reading to find out, how to do it.

    Custom Trade Chart Label Provider

    As an example of a Trade Chart Label Provider customization, let's create a custom one with some different formatters.

    First, we need to subclass CalendarUnitDateFormatter and implement a protected method createFormatterForCalendarUnit(int calendarUnit) which will return a formatter with the desired date format depending on the current calendar unit. Here is, how it might look in code:

    • Java
    • Java with Builders API
    • Kotlin
    class CustomCalendarUnitDateFormatter extends CalendarUnitDateFormatter {
        @Override
        protected SynchronizedSimpleDataFormatWrapper createFormatterForCalendarUnit(int calendarUnit) {
            String dateFormat = "";
    
            if ((calendarUnit & CalendarUnit.YEAR) != 0) {
                dateFormat = dateFormat.concat(" yyyy");
            }
            if ((calendarUnit & CalendarUnit.MONTH) != 0) {
                dateFormat = dateFormat.concat(" MMM");
            }
            if ((calendarUnit & CalendarUnit.DAY) != 0) {
                dateFormat = dateFormat.concat(" dd");
            }
    
            final SynchronizedSimpleDataFormatWrapper formatter = SimpleDateFormatUtil.initWrapperFromFormatString(
                    dateFormat,
                    getLocale(),
                    getTimeZone()
            );
    
            return formatter;
        }
    }
    
    class CustomCalendarUnitDateFormatter extends CalendarUnitDateFormatter {
        @Override
        protected SynchronizedSimpleDataFormatWrapper createFormatterForCalendarUnit(int calendarUnit) {
            String dateFormat = "";
    
            if ((calendarUnit & CalendarUnit.YEAR) != 0) {
                dateFormat = dateFormat.concat(" yyyy");
            }
            if ((calendarUnit & CalendarUnit.MONTH) != 0) {
                dateFormat = dateFormat.concat(" MMM");
            }
            if ((calendarUnit & CalendarUnit.DAY) != 0) {
                dateFormat = dateFormat.concat(" dd");
            }
    
            final SynchronizedSimpleDataFormatWrapper formatter = SimpleDateFormatUtil.initWrapperFromFormatString(dateFormat, getLocale(), getTimeZone());
    
            return formatter;
        }
    }
    
    class CustomCalendarUnitDateFormatter : CalendarUnitDateFormatter() {
        override fun createFormatterForCalendarUnit(calendarUnit: Int): SynchronizedSimpleDataFormatWrapper {
            var dateFormat = ""
            if (calendarUnit and CalendarUnit.YEAR != 0) {
                dateFormat = "$dateFormat yyyy"
            }
            if (calendarUnit and CalendarUnit.MONTH != 0) {
                dateFormat = "$dateFormat MMM"
            }
            if (calendarUnit and CalendarUnit.DAY != 0) {
                dateFormat = "$dateFormat dd"
            }
            return SimpleDateFormatUtil.initWrapperFromFormatString(
                dateFormat,
                locale, timeZone
            )
        }
    }
    
    Note

    In case using createFormatterForCalendarUnit method to return your custom formatter doesn’t fit your needs and you want to have full control on formatting your axis labels, use format(Date date, int calendarUnit) method. Please, continue reading to see how you can use this method.

    Let's also create some custom CursorModifier axis label formatter which will format cursor axis label depending on current granularity. So, for example, by default we want our Cursor to show a date in a format, like “2018 Jun 17” but when we zoom close enough - it should become something like “Monday, 10 June”.

    Similar to how we create our CustomCalendarUnitDateFormatter, we will subclass CursorCalendarUnitDateFormatter and create two formatters with different date formats. Then, we will use format(Date date, int calendarUnit) method to return a string for our Cursor axis label. Here is how it will look in code:

    • Java
    • Java with Builders API
    • Kotlin
    class CustomCursorCalendarUnitDateFormatter extends CursorCalendarUnitDateFormatter {
        final SynchronizedSimpleDataFormatWrapper cursorDefaultFormatter = SimpleDateFormatUtil.initWrapperFromFormatString(
                "yyyy MMM dd",
                getLocale(),
                getTimeZone()
        );
    
        final SynchronizedSimpleDataFormatWrapper dayCursorFormatter = SimpleDateFormatUtil.initWrapperFromFormatString(
                "EEEE, dd MMMM",
                getLocale(),
                getTimeZone()
        );
    
        @Override
        public CharSequence format(Date date, int calendarUnit) {
            SynchronizedSimpleDataFormatWrapper formatter = calendarUnit < CalendarUnit.DAY ? cursorDefaultFormatter : dayCursorFormatter;
    
            return formatter.format(date);
        }
    }
    
    class CustomCursorCalendarUnitDateFormatter extends CursorCalendarUnitDateFormatter {
        final SynchronizedSimpleDataFormatWrapper cursorDefaultFormatter = SimpleDateFormatUtil.initWrapperFromFormatString("yyyy MMM dd", getLocale(), getTimeZone());
        final SynchronizedSimpleDataFormatWrapper dayCursorFormatter = SimpleDateFormatUtil.initWrapperFromFormatString("EEEE, dd MMMM", getLocale(), getTimeZone());
    
        @Override
        public CharSequence format(Date date, int calendarUnit) {
            SynchronizedSimpleDataFormatWrapper formatter = calendarUnit < CalendarUnit.DAY ? cursorDefaultFormatter : dayCursorFormatter;
    
            return formatter.format(date);
        }
    }
    
    class CustomCursorCalendarUnitDateFormatter :
        CursorCalendarUnitDateFormatter() {
        val cursorDefaultFormatter = SimpleDateFormatUtil.initWrapperFromFormatString(
            "yyyy MMM dd",
            locale, timeZone
        )
        val dayCursorFormatter = SimpleDateFormatUtil.initWrapperFromFormatString(
            "EEEE, dd MMMM",
            locale, timeZone
        )
    
        override fun format(date: Date, calendarUnit: Int): CharSequence {
            val formatter =
                if (calendarUnit < CalendarUnit.DAY) cursorDefaultFormatter else dayCursorFormatter
            return formatter.format(date)
        }
    }
    

    Next, we need to subclass TradeChartAxisLabelFormatter<T> and pass our custom formatters to its init, like this:

    • Java
    • Java with Builders API
    • Kotlin
    class CustomTradeChartLabelFormatter extends TradeChartAxisLabelFormatter {
        CustomTradeChartLabelFormatter() {
            super(new CustomCalendarUnitDateFormatter(), new CustomCursorCalendarUnitDateFormatter());
        }
    }
    
    class CustomTradeChartLabelFormatter extends TradeChartAxisLabelFormatter {
        CustomTradeChartLabelFormatter() {
            super(new CustomCalendarUnitDateFormatter(), new CustomCursorCalendarUnitDateFormatter());
        }
    }
    
    class CustomTradeChartLabelFormatter :
        TradeChartAxisLabelFormatter<CategoryDateAxis>(
            CustomCalendarUnitDateFormatter(),
            CustomCursorCalendarUnitDateFormatter()
        )
    

    Finally, create a TradeChartAxisLabelProvider subclass with our CustomTradeChartLabelFormatter and assign it to the labelProvider. Also, you need to add to your surface PinchZoomModifier to be able to zoom and CursorModifier to see the Cursor axis labels formatting in action. See the code below:

    • Java
    • Java with Builders API
    • Kotlin
    class CustomTradeChartLabelProvider extends TradeChartAxisLabelProvider {
        CustomTradeChartLabelProvider() {
            super(new CustomTradeChartLabelFormatter());
        }
    }
    
    CategoryDateAxis xAxis = new CategoryDateAxis(getContext());
    xAxis.setLabelProvider(new CustomTradeChartLabelProvider());
    
    PinchZoomModifier pinchZoomModifier = new PinchZoomModifier();
    pinchZoomModifier.setDirection(Direction2D.XDirection);
    
    CursorModifier cursorModifier = new CursorModifier();
    cursorModifier.setReceiveHandledEvents(true);
    
    Collections.addAll(surface.getChartModifiers(), pinchZoomModifier, cursorModifier);
    
    class CustomTradeChartLabelProvider extends TradeChartAxisLabelProvider {
        CustomTradeChartLabelProvider() {
            super(new CustomTradeChartLabelFormatter());
        }
    }
    
    CategoryDateAxis xAxis = sciChartBuilder.newCategoryDateAxis()
            .withLabelProvider(new CustomTradeChartLabelProvider())
            .build();
    
    final ModifierGroup modifierGroup = sciChartBuilder
            .newModifierGroup()
            .withPinchZoomModifier()
            .withXyDirection(Direction2D.XDirection)
            .build()
            .withCursorModifier()
            .withReceiveHandledEvents(true)
            .build()
            .build();
    
    surface.getChartModifiers().add(modifierGroup);
    
    class CustomTradeChartLabelProvider :
        TradeChartAxisLabelProvider(CustomTradeChartLabelFormatter())
    
    val xAxis = CategoryDateAxis(context)
    xAxis.labelProvider = CustomTradeChartLabelProvider()
    
    val pinchZoomModifier = PinchZoomModifier()
    pinchZoomModifier.direction = Direction2D.XDirection
    
    val cursorModifier = CursorModifier()
    cursorModifier.receiveHandledEvents = true
    
    Collections.addAll(surface.chartModifiers, pinchZoomModifier, cursorModifier)
    

    Here are the results in the normal and slightly zoomed states: TradeChartAxisLabelProvider TradeChartAxisLabelProvider

    In case such a customization doesn't fit your needs and you need some completely different Label Provider you can always create your own. You will find more details in Axis Labels - LabelProvider API article.

    Back to top © 2011-2025 SciChart. All rights reserved. | sitemap.xml