Category talk:Wren-plot

From Rosetta Code

Source code

/* Module "plot.wren" */

import "graphics" for Canvas, Color, Point

/* Axes represents a two dimensional Cartesian coordinate system
   for plotting points or drawing simple charts. The use of
   an 8 x 8 font (the default) is assumed throughout. */
class Axes {
    // Converts a list of Points to a list of coordinate pairs.
    static PointsToPairs(points) { points.map { |p| [p.x, p.y] }.toList }

    // Constructs a new Axes object with:
    // 1. the origin at absolute coordinates (originX, originY);
    // 2. lengths of the x and y axes: lengthX and lengthY in pixels; and
    // 3. ranges of x and y values: rangeX and rangeY.
    // If x-values are non-numeric or discrete, null should be passed for rangeX.
    // In that event a range of 0..lengthX will be used by those methods that need it.
    construct new(originX, originY, lengthX, lengthY, rangeX, rangeY) {
        _ox = originX
        _oy = originY
        _lx = lengthX
        _ly = lengthY
        _rn = (rangeX == null)
        _rx = _rn ? 0.._lx : rangeX
        _ry = rangeY
    }

    // Self explanatory read-only properties.
    originX { _ox }
    originY { _oy }
    lengthX { _lx }
    lengthY { _ly }
    rangeX  { _rx }
    rangeY  { _ry }

    // Properties for scaling x and y coordinates to the lengths of their axes.
    scaleX  { _lx / (_rx.to - _rx.from) }
    scaleY  { _ly / (_ry.to - _ry.from) }

    // Draws the axes in a given color and line thickness.
    draw(color,thickness) {
        Canvas.line(_ox, _oy, _ox + _lx, _oy, color, thickness)
        Canvas.line(_ox, _oy, _ox, _oy - _ly, color, thickness)
    }

    // Prints 'text' centered at unscaled coordinates (x, y)
    // relative to the origin in a given color.
    print(x, y, text, color) {
        Canvas.print(text, _ox + x - 4, _oy - y - 4, color)
    }

    // Adds marks to x and y axes at the appropriate scaled coordinates.
    // The marks themselves are drawn in a given color and line thickness.
    // The label is printed in 'lblColor'. Before being displayed the labels
    // are scaled by dividing them by xDiv and yDiv for the respective axes.
    label(xMarks, yMarks, color, thickness, lblColor, xDiv, yDiv) {
        for (xm in xMarks) {
            var x = (xm - _rx.from) * scaleX
            Canvas.line(_ox + x, _oy, _ox + x, _oy + 10, color, thickness)
            var label = (xm/xDiv).toString
            var v = Canvas.getPrintArea(label)
            Canvas.print(label, _ox + x - v.x/2, _oy + 10 + v.y, lblColor) 
        }
        for (ym in yMarks) {
            var y = (ym  - _ry.from) * scaleY
            Canvas.line(_ox, _oy - y, _ox - 10, _oy - y, color, thickness)
            var label = (ym/yDiv).toString
            var v = Canvas.getPrintArea(label)
            Canvas.print(label, _ox - 14 - v.x, _oy - y - v.y/2, lblColor)
        }
    }

    // Convenience version of 'label' which doesn't scale the labels prior to display.
    label(xMarks, yMarks, color, thickness, lblColor) {
        label(xMarks, yMarks, color, thickness, lblColor, 1, 1)
    }

    // Adds marks to x and y axes at the appropriate scaled coordinates
    // drawn in a given color and line thickness. No labels are drawn.
    mark(xMarks, yMarks, color, thickness) {
        for (xm in xMarks) {
            var x = (xm - _rx.from) * scaleX
            Canvas.line(_ox + x, _oy, _ox + x, _oy + 10, color, thickness)
        }
        for (ym in yMarks) {
            var y = (ym  - _ry.from) * scaleY
            Canvas.line(_ox, _oy - y, _ox - 10, _oy - y, color, thickness)
        }
    }

    // Draws a line between scaled points (x1, y1) and (x2, y2)
    // relative to the origin in a given color and line thickness.
    line(x1, y1, x2, y2, color, thickness) {
        x1 = (x1 - _rx.from) * scaleX
        x2 = (x2 - _rx.from) * scaleX
        y1 = (y1 - _ry.from) * scaleY
        y2 = (y2 - _ry.from) * scaleY
        Canvas.line(_ox + x1, _oy - y1, _ox + x2, _oy - y2, color, thickness)
    }

    // Draws a rectangle with the top-left corner at scaled point (x, y)
    // relative to the origin and a width of w and height of h in color c.
    rect(x, y, w, h, c) {
        x = (x - _rx.from) * scaleX
        y = (y - _ry.from) * scaleY
        Canvas.rect(_ox + x, _oy - y, w, h, c)
    }

    // Draws a filled rectangle with the top-left corner at scaled point (x, y)
    // relative to the origin and a width of w and height of h in color c.
    rectfill(x, y, w, h, c) {
        x = (x - _rx.from) * scaleX
        y = (y - _ry.from) * scaleY
        Canvas.rectfill(_ox + x, _oy - y, w, h, c)
    }

    // Draws grid lines from scaled coordinates on the x and y axes
    // in a given color and line thickness.
    grid(xCoords, yCoords, color, thickness) {
        for (xc in xCoords) {
            var x = (xc - _rx.from) * scaleX
            Canvas.line(_ox + x, _oy, _ox + x, _oy - _ly, color, thickness)
        }
        for (yc in yCoords) {
            var y = (yc - _ry.from) * scaleY
            Canvas.line(_ox, _oy - y, _ox + _lx, _oy - y, color, thickness)
        }
    }

    // Plots a list of points using 'symbol' centred at scaled coordinates (x, y)
    // relative to the origin in a given color.
    plot(points, color, symbol) {
        if (points.count > 0 && (points[0] is Point)) {
            points = Axes.PointsToPairs(points)
        }
        for (p in points) {
            var px = (p[0] - _rx.from) * scaleX - 4
            var py = (p[1] - _ry.from) * scaleY + 4
            Canvas.print(symbol, _ox + px, _oy - py, color)
        }
    }

    // As the 'plot' method but prints a label 'dist' pixels after 'symbol'
    // in color 'lblColor'.
    plotWithLabels(points, color, symbol, labels, lblColor, dist) {
        if (points.count > 0 && (points[0] is Point)) {
            points = Axes.PointsToPairs(points)
        }
        for (i in 0...points.count) {
            var px = (points[i][0] - _rx.from) * scaleX - 4
            var py = (points[i][1] - _ry.from) * scaleY + 4
            Canvas.print(symbol, _ox + px, _oy - py, color)
            Canvas.print(labels[i], _ox + px + 8 + dist, _oy - py, lblColor)
        }
    }

    // As the 'plot' method but prints the unscaled coordinates of each point
    // 'dist' pixels after 'symbol' in color 'crdColor'.
    plotWithCoords(points, color, symbol, crdColor, dist) {
        if (points.count > 0 && (points[0] is Point)) {
            points = Axes.PointsToPairs(points)
        }
        for (i in 0...points.count) {
            var px = (points[i][0] - _rx.from) * scaleX - 4
            var py = (points[i][1] - _ry.from) * scaleY + 4
            var coords = "(%(points[i][0]),%(points[i][1]))"
            Canvas.print(symbol, _ox + px, _oy - py, color)
            Canvas.print(coords, _ox + px + 8 + dist, _oy - py, crdColor)
        }
    }

    // Draws a line graph between each scaled point in a list of points
    // relative to the origin in a given color and line thickness.
    lineGraph(points, color, thickness) {
        if (points.count > 0 && (points[0] is Point)) {
            points = Axes.PointsToPairs(points)
        }
        var px = (points[0][0] - _rx.from) * scaleX
        var py = (points[0][1] - _ry.from) * scaleY
        for (i in 1...points.count) {
            var qx = (points[i][0] - _rx.from) * scaleX
            var qy = (points[i][1] - _ry.from) * scaleY
            Canvas.line(_ox + px, _oy - py, _ox + qx, _oy - qy, color, thickness)
            px = qx
            py = qy
        }
    }

    // Draws a bar-chart representing 'data' which is a list of pairs, the first element of which
    // is the bar's label and the second is the bar's value.
    // The bars are drawn in order with a width of 'barWidth' using successive colors in the
    // 'barColors' list and with a border color of 'brdColor'.
    // The interval between bars is calculated by the method.
    // If _rn is true, the method prints the labels on the x-axis at the appropriate positions.
    // 'yMarks' are drawn on the y-axis at the appropriate positions in color 'mrkColor'
    // and thickness 'mrkThick'. Labels on both the x and y axes are printed in 'lblColor.'
    barChart(data, barWidth, barColors, brdColor, yMarks, mrkColor, mrkThick, lblColor) {
        var n = data.count
        var interval = (_lx - n * barWidth)/(n + 1)
        var x = _ox + interval
        var cix = 0
        for (d in data) {
            var v = (d[1] - _ry.from) * scaleY
            Canvas.rectfill(x, _oy - v, barWidth, v, barColors[cix])
            Canvas.rect(x, _oy - v, barWidth, v, brdColor)
            if (_rn) {
                var label = d[0].toString
                var w = Canvas.getPrintArea(label).x
                var h = (barWidth - w)/2
                Canvas.print(label, x + h, _oy + 14, lblColor)
            }
            x = x + barWidth + interval
            cix = (cix + 1) % barColors.count
        }
        draw(mrkColor, mrkThick)
        label([], yMarks, mrkColor, mrkThick, lblColor)
    }

    // Draws a histogram representing 'data' which is a list of pairs, the first element of which
    // is the bar's label and the second is the bar's value.
    // A histogram is essentially a bar-chart which covers the entire x-axis with no intervals
    // between the bars and 'barWidth' is therefore calculated by the method.
    // Otherwise the parameters and remarks are the same as for a bar-chart.
    // If _rn is false, 'xmarks' are deduced and drawn on the x-axis at the appropriate intervals.
    histogram(data, barColors, brdColor, yMarks, mrkColor, mrkThick, lblColor) {
        var n = data.count
        var rectWidth = _lx/n
        barChart(data, rectWidth, rectColors, brdColor, yMarks, mrkColor, mrkThick, lblColor)
        if (!_rn) { 
             var interval = (_rx.to - _rx.from) / n
             var xMarks = (0..n+1).map { |i| i * interval }.toList
             label(xMarks, [], mrkColor, mrkThick, lblColor)
        }
    }
}