Skip to content

Commit

Permalink
Removed Overlay Glyphs to Improve cross platform rendering.
Browse files Browse the repository at this point in the history
Added a note in the demo about cross platform rendering quality.
  • Loading branch information
c committed May 30, 2023
1 parent 644d242 commit 39ebc49
Show file tree
Hide file tree
Showing 6 changed files with 814 additions and 1,005 deletions.
12 changes: 7 additions & 5 deletions cliviz/shared/src/main/scala/ai/dragonfly/viz/cli/CLImg.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import scala.collection.mutable
object CLImg {

val brailleBytes:NArray[String] = NArray[String](
//"⠈",
"⠀", "⠁", "⠂", "⠃", "⠄", "⠅", "⠆", "⠇", "⡀", "⡁", "⡂", "⡃", "⡄", "⡅", "⡆", "⡇", "⠈", "⠉", "⠊", "⠋", "⠌", "⠍", "⠎", "⠏", "⡈", "⡉", "⡊", "⡋", "⡌", "⡍", "⡎", "⡏", "⠐", "⠑", "⠒", "⠓", "⠔", "⠕", "⠖", "⠗", "⡐", "⡑", "⡒", "⡓", "⡔", "⡕", "⡖", "⡗", "⠘", "⠙", "⠚", "⠛", "⠜", "⠝", "⠞", "⠟", "⡘", "⡙", "⡚", "⡛", "⡜", "⡝", "⡞", "⡟", "⠠", "⠡", "⠢", "⠣", "⠤", "⠥", "⠦", "⠧", "⡠", "⡡", "⡢", "⡣", "⡤", "⡥", "⡦", "⡧", "⠨", "⠩", "⠪", "⠫", "⠬", "⠭", "⠮", "⠯", "⡨", "⡩", "⡪", "⡫", "⡬", "⡭", "⡮", "⡯", "⠰", "⠱", "⠲", "⠳", "⠴", "⠵", "⠶", "⠷", "⡰", "⡱", "⡲", "⡳", "⡴", "⡵", "⡶", "⡷", "⠸", "⠹", "⠺", "⠻", "⠼", "⠽", "⠾", "⠿", "⡸", "⡹", "⡺", "⡻", "⡼", "⡽", "⡾", "⡿", "⢀", "⢁", "⢂", "⢃", "⢄", "⢅", "⢆", "⢇", "⣀", "⣁", "⣂", "⣃", "⣄", "⣅", "⣆", "⣇", "⢈", "⢉", "⢊", "⢋", "⢌", "⢍", "⢎", "⢏", "⣈", "⣉", "⣊", "⣋", "⣌", "⣍", "⣎", "⣏", "⢐", "⢑", "⢒", "⢓", "⢔", "⢕", "⢖", "⢗", "⣐", "⣑", "⣒", "⣓", "⣔", "⣕", "⣖", "⣗", "⢘", "⢙", "⢚", "⢛", "⢜", "⢝", "⢞", "⢟", "⣘", "⣙", "⣚", "⣛", "⣜", "⣝", "⣞", "⣟", "⢠", "⢠", "⢢", "⢣", "⢤", "⢥", "⢦", "⢧", "⣠", "⣡", "⣢", "⣣", "⣤", "⣥", "⣦", "⣧", "⢨", "⢩", "⢪", "⢫", "⢬", "⢭", "⢮", "⢯", "⣨", "⣩", "⣪", "⣫", "⣬", "⣭", "⣮", "⣯", "⢰", "⢱", "⢲", "⢳", "⢴", "⢵", "⢶", "⢷", "⣰", "⣱", "⣲", "⣳", "⣴", "⣵", "⣶", "⣷", "⢸", "⢹", "⢺", "⢻", "⢼", "⢽", "⢾", "⢿", "⣸", "⣹", "⣺", "⣻", "⣼", "⣽", "⣾", "⣿",
)

Expand Down Expand Up @@ -87,7 +88,7 @@ class CLImg(val width:Int, val height:Int) {
val greenChannel:NArray[Int] = NArray.fill(pixelCount)(0)
val blueChannel:NArray[Int] = NArray.fill(pixelCount)(0)

val layer:NArray[List[Glyph]] = NArray.fill(pixelCount)(List[Glyph]())
val layer:NArray[List[Int]] = NArray.fill(pixelCount)(List[Int]())

inline def hasRed(color:Int):Boolean = color match {
case RED => true
Expand Down Expand Up @@ -119,7 +120,7 @@ class CLImg(val width:Int, val height:Int) {
if (out != WHITE && layer(i).nonEmpty) {
var gg:Boolean = false
for (g <- layer(i)) {
if (g.color != WHITE) out = out | g.color
if (g != WHITE) out = out | g
else gg = true
}
if (gg && out == BLACK) {
Expand Down Expand Up @@ -164,9 +165,9 @@ class CLImg(val width:Int, val height:Int) {

}

def setGlyph(x:Int, y:Int, glyph:Glyph):CLImg = {
def setGlyph(x:Int, y:Int, color:Int):CLImg = {
val i:Int = linearIndexOf(x, y)
if (i > -1) layer(i) = glyph :: layer(i)
if (i > -1) layer(i) = color :: layer(i)
this
}

Expand Down Expand Up @@ -233,7 +234,8 @@ class CLImg(val width:Int, val height:Int) {

/* Unicode Braille Characters for Plotting:
⠀⠈⠐⠘⠠⠨⠰⠸⢀⢈⢐⢘⢠⢨⢰⢸
𝄛⠈⠐⠘⠠⠨⠰⠸⢀⢈⢐⢘⢠⢨⢰⢸
⠁⠉⠑⠙⠡⠩⠱⠹⢁⢉⢑⢙⢠⢩⢱⢹
⠂⠊⠒⠚⠢⠪⠲⠺⢂⢊⢒⢚⢢⢪⢲⢺
⠃⠋⠓⠛⠣⠫⠳⠻⢃⢋⢓⢛⢣⢫⢳⢻
Expand Down
136 changes: 33 additions & 103 deletions cliviz/shared/src/main/scala/ai/dragonfly/viz/cli/Chart.scala
Original file line number Diff line number Diff line change
Expand Up @@ -108,147 +108,75 @@ case class Chart( conf:ChartConfig ) {
(scaleY * (v.y - range.min)) + aYa
)

val leftPaddingWidth: Int = Math.max(Math.max(verticalLabel.length, range.MAX.toString.length), range.min.toString.length)

private val legend: mutable.TreeMap[String, Int] = mutable.TreeMap[String, Int]()
legend.put("Axis", CLImg.WHITE)

def lookUpColor(name: String): Int = legend.getOrElseUpdate(
name, if (legend.size < 7) legend.size else 1 * legend.size % 7
)

val cimg:CLImg = CLImg(width, height)

// vertical axis ?
if (domain.rangeContains(0.0)) {

glyphLineSegment(
lineSegment(
Vec[2](0.0, range.min),
Vec[2](0.0, range.MAX),
Glyph.axis
"Axis"
)

}

// Horizontal Axis? "⃨⃛" "͞"
if (range.rangeContains(0.0)) {
glyphLineSegment(
lineSegment(
Vec[2](domain.min, 0.0),
Vec[2](domain.MAX, 0.0),
Glyph.axis
"Axis"
)
}

val leftPaddingWidth: Int = Math.max(Math.max(verticalLabel.length, range.MAX.toString.length), range.min.toString.length)

private val legend:mutable.TreeMap[String, Glyph] = mutable.TreeMap[String, Glyph]()


def glyphLineSegment(p1: Vec[2], p2: Vec[2], glyph:Glyph):Chart = {
val unitWidth:Int = 2
val unitHeight:Int = 4

val pi1 = mapToImageSpace(p1)
val start:Vec[2] = Vec[2]( pi1.x / unitWidth, pi1.y / unitHeight)

val pi2 = mapToImageSpace(p2)
val end:Vec[2] = Vec[2](pi2.x / unitWidth, pi2.y / unitHeight)

val hm:mutable.TreeMap[Int, Interval[Int]] = mutable.TreeMap[Int, Interval[Int]]()

ai.dragonfly.math.geometry.Line.trace2D(start, end, (dX:Int, dY:Int) => {
val xi:Int = dX * unitWidth
val yi:Int = (height - 1) - (dY * unitHeight)
val interval = hm.getOrElseUpdate(yi, `[]`(xi, xi))
hm.put(yi, `[]`[Int](Math.min(interval.min, xi), Math.max(interval.MAX, xi)))
})

for ((yi:Int, xiv:Interval[Int]) <- hm) {
val middle:Int = (xiv.min + xiv.MAX) / 2
var s:Int = 1
val step = 6
var left:Int = xiv.min + (s*step)
var right:Int = xiv.MAX - (s*step)

var drew = false
while (right - left > unitWidth) {
cimg.setGlyph(left, yi, glyph)
cimg.setGlyph(right, yi, glyph)

s = s + 1

left = xiv.min + (s*step)
right = xiv.MAX - (s*step)
drew = true
}
if (!drew) cimg.setGlyph(middle, yi, glyph)
}

this
}

def glyphLine(point: Vec[2], slope:Double, name:String, glyph:Glyph):Chart = {
val b:Double = (-point.x * slope) + point.y

val start:Vec[2] = Vec[2](domain.min, domain.min * slope + b)
val end:Vec[2] = Vec[2](domain.MAX, domain.MAX * slope + b)

glyphLineSegment(start, end, glyph)
}

private var maxItemNameLength:Int = 0

def lineSegment(p1: Vec[2], p2:Vec[2], name:String):Chart = {
val glyph = legend.getOrElseUpdate(name, Glyph(legend.size))
maxItemNameLength = Math.max(maxItemNameLength, name.length + 2)

if (glyph.overlay) {
glyphLineSegment(p1, p2, glyph)
} else {

val start:Vec[2] = mapToImageSpace(p1)
val end:Vec[2] = mapToImageSpace(p2)
val start:Vec[2] = mapToImageSpace(p1)
val end:Vec[2] = mapToImageSpace(p2)

ai.dragonfly.math.geometry.Line.trace2D(start, end, (dX:Int, dY:Int) => {
cimg.setPixel(dX, (cimg.height - 1) - dY, glyph.color)
})
this
}
ai.dragonfly.math.geometry.Line.trace2D(start, end, (dX:Int, dY:Int) => {
cimg.setPixel(dX, (cimg.height - 1) - dY, lookUpColor(name))
})
this
}

def line(point: Vec[2], slope:Double, name:String):Chart = {
val glyph = legend.getOrElseUpdate(name, Glyph(legend.size))
maxItemNameLength = Math.max(maxItemNameLength, name.length + 2)

if (glyph.overlay) {
glyphLine(point, slope, name, glyph)
} else {
val b:Double = (-point.x * slope) + point.y

val start:Vec[2] = Vec[2](domain.min, domain.min * slope + b)
val end:Vec[2] = Vec[2](domain.MAX, domain.MAX * slope + b)
val b:Double = (-point.x * slope) + point.y

lineSegment(start, end, name)
}
}
val start:Vec[2] = Vec[2](domain.min, domain.min * slope + b)
val end:Vec[2] = Vec[2](domain.MAX, domain.MAX * slope + b)

private def plotGlyph(glyph:Glyph)(p:Vec[2]): Unit = {
val pT = mapToImageSpace(p)
cimg.setGlyph(
pT.x.toInt,
(cimg.height - 1) - pT.y.toInt,
glyph
)
lineSegment(start, end, name)
}

private def plotPixel(glyph:Glyph)(p:Vec[2]):Unit = {
private def plotPixel(c:Int)(p:Vec[2]):Unit = {
val pT = mapToImageSpace(p)
cimg.setPixel(
pT.x.toInt,
(cimg.height - 1) - pT.y.toInt,
glyph.color
c
)
}

def scatter(name:String, points:Vec[2]*):Chart = {
val glyph = legend.getOrElseUpdate(name, Glyph(legend.size))
maxItemNameLength = Math.max(maxItemNameLength, name.length + 2)

points.foreach(
if (glyph.overlay) (p:Vec[2]) => plotGlyph(glyph)(p)
else (p:Vec[2]) => plotPixel(glyph)(p)
)
points.foreach( (p:Vec[2]) => plotPixel(lookUpColor(name))(p) )

this
}
Expand Down Expand Up @@ -304,6 +232,8 @@ case class Chart( conf:ChartConfig ) {
sb.append("")
}

inline def makeIcon(color:Int):String = s"${CLImg.colorBytes(color)}⠒⠀$RESET"

override def toString: String = {
given ss:SegmentedString = new SegmentedString().append(RESET)
//ss.append(RESET).append(s"$zeroX and $zeroY ${mapFromImageSpace(Vec[2](zeroX, zeroY))} and $domain x $range\n")
Expand All @@ -313,14 +243,14 @@ case class Chart( conf:ChartConfig ) {
topBorder.append("\n")
val lines:NArray[String] = cimg.lines()
padLeft(range.MAX.toString).append("").append(lines.head).append(RESET).append("\n")
val litr:Iterator[(String, Glyph)] = legend.iterator
val litr:Iterator[(String, Int)] = legend.iterator
val footerLegend:String = if (legend.size > lines.length) {
given lsb:SegmentedString = SegmentedString()
padLeft("")
var lineLength:Int = 0
while(litr.hasNext) {
val (itemName:String, itemGlyph:Glyph) = litr.next()
val legendItem:String = s" ${itemGlyph.asIcon} $itemName"
val (itemName:String, c:Int) = litr.next()
val legendItem:String = s" ${makeIcon(c)} $itemName"
if (lineLength + legendItem.length + 4 > width) { // maxItemNameLength
lsb.append("\n")
padLeft("")
Expand All @@ -337,8 +267,8 @@ case class Chart( conf:ChartConfig ) {
val l = if (i == lines.length / 2) verticalLabel else ""
padLeft(l).append("").append(lines(i)).append(RESET).append("⡇⠀")
if (litr.hasNext) {
val (itemName, itemGlyph) = litr.next()
ss.append(s" ${itemGlyph.asIcon} $itemName")
val (itemName, c) = litr.next()
ss.append(s" ${makeIcon(c)} $itemName")
}
ss.append("\n")
i = i + 1
Expand Down
95 changes: 0 additions & 95 deletions cliviz/shared/src/main/scala/ai/dragonfly/viz/cli/Glyph.scala

This file was deleted.

2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# cliviz
## About this Demo:
&nbsp;&nbsp;&nbsp;The Console Output below exercises various features of the cliviz library:<br />
&nbsp;&nbsp;&nbsp;The Console Output below exercises various features of the cliviz library, but rendering quality depends heavily on system specific unicode configuration:<br />

<div id="console"></div>

Expand Down
Loading

0 comments on commit 39ebc49

Please sign in to comment.