/* 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)
}
}
}