Scala: An ASCII “current value in range” chart/plot (Sparkline)

I just wrote some Scala code to create an ASCII chart/plot that looks like this:

+-------------------------|-------------------------+
                 ^

In this chart, the left side of the chart represents a low value, the right side represents a high value, so the overall line represents that range, and then the ^ on the second line indicates the current value. This can be used to show the 52-week low and high values for a stock, along with its current range. It could also be used to show someone’s current batting average in a season, compared to their low and high values, and any other value that can be expressed as low, high, and current values.

I initially thought the correct name for this was a Sparkline chart, but it’s really something like a “current value in range” chart. I haven’t been able to find the exact correct name, but that’s close enough for now.

The Scala source code

Without any further ado, here’s Version 0.1 of my Scala source code to create this ASCII-style chart/plot:

package com.alvinalexander.utils

object PlotUtils:
    /**
     * Output looks like this:
     *
     *     +-------------------------|-------------------------+
     *                 ^
     *
     * Requirements that are *not* enforced in the code:
     *    1) `plotWidthInChars` must be an even number
     *    2) `high` must be > than `low`
     *    3) `current` must be >= `low` and <= `high`
     *
     * Sample values for thinking: hi=100, low=25, diff=75, current=33
     * (the sample line chart is not for these numbers)
     */
    def asciiValueInRangeChart(
        low: Double,
        high: Double,
        current: Double,
        plotWidthInChars: Int = 50, // must be an even number
        debug: Boolean = false
    ): String =
        val plotLine = createPlotLine(plotWidthInChars)
        val (diff, valuePerDash, halfwayValue, dashesForCurrentValue) = calculateNeededValues(
            low, high, current, plotWidthInChars
        )
        if debug then printDebugInfo(diff, valuePerDash, halfwayValue, dashesForCurrentValue)
        val indicatorLine = createIndicatorLine(current, halfwayValue, dashesForCurrentValue)
        s"$plotLine\n$indicatorLine"

    private def printDebugInfo(
        diff: Double,
        valuePerDash: Double,
        halfwayValue: Double,
        dashesForCurrentValue: Int
    ): Unit =
        System.err.println(s"diff=$diff, valuePerDash=$valuePerDash, halfwayValue=$halfwayValue, dashesForCurrentValue=$dashesForCurrentValue")

    private def calculateNeededValues(
        low: Double,
        high: Double,
        current: Double,
        plotWidthInChars: Int
    ): (Double, Double, Double, Int) =
        val diff = high - low // 75.0
        val valuePerDash = diff / plotWidthInChars // 75/20.0=3.75
        val halfwayValue = diff / 2 // 37.5
        val dashesForCurrentValue = (current / valuePerDash).round.toInt // 33/3.75 = 9
        (diff, valuePerDash, halfwayValue, dashesForCurrentValue)

    private def createPlotLine(plotWidthInChars: Int): String =
        val halfWidth = plotWidthInChars / 2
        val plotLine = "+" + "-" * halfWidth + "|" + "-" * halfWidth + "+"
        plotLine

    private def createIndicatorLine(
        currentValue: Double,
        halfwayValue: Double,
        dashesForCurrentValue: Int
    ): String =
        if currentValue < halfwayValue then
            " " * (dashesForCurrentValue) + "^"
        else
            // add a space for the middle "|" symbol
            " " * (dashesForCurrentValue) + " " + "^"

I hope to add more documentation to this over time --- as well as some needed testing --- but until then, if you want to create an ASCII-style chart like this, I hope this source code is helpful.