Skip to content

Commit fb18a1a

Browse files
Use optimized argonaut pretty printer
From almond, originally from the jupyter-kernel project Same optimization in circe
1 parent 6a8f629 commit fb18a1a

File tree

3 files changed

+151
-5
lines changed

3 files changed

+151
-5
lines changed

render/js/src/main/scala/plotly/Plotly.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import argonaut.Argonaut._
66
import argonaut.{Json, PrettyParams}
77
import plotly.Codecs._
88
import plotly.element.Color
9+
import plotly.internals.BetterPrinter
910
import plotly.layout._
1011

1112
import scala.scalajs.js
@@ -14,10 +15,10 @@ import scala.scalajs.js.JSON
1415

1516
object Plotly {
1617

17-
private val printer = PrettyParams.nospace.copy(dropNullKeys = true)
18+
private val printer = BetterPrinter(PrettyParams.nospace.copy(dropNullKeys = true))
1819
private def stripNulls(json: Json): js.Any = {
1920
// Remove empty objects
20-
JSON.parse(printer.pretty(json))
21+
JSON.parse(printer.render(json))
2122
}
2223

2324
def plot(div: String, data: Seq[Trace], layout: Layout): Unit = {

render/jvm/src/main/scala/plotly/Plotly.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import java.nio.file.Files
1010

1111
import argonaut.Argonaut._
1212
import argonaut.PrettyParams
13+
import plotly.internals.BetterPrinter
1314

1415
import scala.annotation.tailrec
1516

1617
object Plotly {
1718

18-
private val printer = PrettyParams.nospace.copy(dropNullKeys = true)
19+
private val printer = BetterPrinter(PrettyParams.nospace.copy(dropNullKeys = true))
1920

2021
def jsSnippet(div: String, data: Seq[Trace], layout: Layout): String = {
2122

@@ -25,15 +26,15 @@ object Plotly {
2526

2627
for ((d, idx) <- data.zipWithIndex) {
2728
b ++= s" var data$idx = "
28-
b ++= printer.pretty(d.asJson)
29+
b ++= printer.render(d.asJson)
2930
b ++= ";\n"
3031
}
3132

3233
b ++= "\n "
3334
b ++= data.indices.map(idx => s"data$idx").mkString("var data = [", ", ", "];")
3435
b ++= "\n"
3536
b ++= " var layout = "
36-
b ++= printer.pretty(layout.asJson)
37+
b ++= printer.render(layout.asJson)
3738
b ++= ";\n\n Plotly.plot('"
3839
b ++= div.replaceAll("'", "\\'")
3940
b ++= "', data, layout);\n"
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package plotly.internals
2+
3+
import argonaut._
4+
import argonaut.PrettyParams.vectorMemo
5+
6+
final case class BetterPrinter(params: PrettyParams) {
7+
8+
import params._
9+
10+
private def addIndentation(s: String): Int => String = {
11+
val lastNewLineIndex = s.lastIndexOf("\n")
12+
if (lastNewLineIndex < 0) {
13+
_ => s
14+
} else {
15+
val afterLastNewLineIndex = lastNewLineIndex + 1
16+
val start = s.substring(0, afterLastNewLineIndex)
17+
val end = s.substring(afterLastNewLineIndex)
18+
n => start + indent * n + end
19+
}
20+
}
21+
22+
private val openBraceText = "{"
23+
private val closeBraceText = "}"
24+
private val openArrayText = "["
25+
private val closeArrayText = "]"
26+
private val commaText = ","
27+
private val colonText = ":"
28+
private val nullText = "null"
29+
private val trueText = "true"
30+
private val falseText = "false"
31+
private val stringEnclosureText = "\""
32+
33+
private val _lbraceLeft = addIndentation(lbraceLeft)
34+
private val _lbraceRight = addIndentation(lbraceRight)
35+
private val _rbraceLeft = addIndentation(rbraceLeft)
36+
private val _rbraceRight = addIndentation(rbraceRight)
37+
private val _lbracketLeft = addIndentation(lbracketLeft)
38+
private val _lbracketRight = addIndentation(lbracketRight)
39+
private val _rbracketLeft = addIndentation(rbracketLeft)
40+
private val _rbracketRight = addIndentation(rbracketRight)
41+
private val _lrbracketsEmpty = addIndentation(lrbracketsEmpty)
42+
private val _arrayCommaLeft = addIndentation(arrayCommaLeft)
43+
private val _arrayCommaRight = addIndentation(arrayCommaRight)
44+
private val _objectCommaLeft = addIndentation(objectCommaLeft)
45+
private val _objectCommaRight = addIndentation(objectCommaRight)
46+
private val _colonLeft = addIndentation(colonLeft)
47+
private val _colonRight = addIndentation(colonRight)
48+
49+
private val lbraceMemo = vectorMemo{depth: Int => "%s%s%s".format(_lbraceLeft(depth), openBraceText, _lbraceRight(depth + 1))}
50+
private val rbraceMemo = vectorMemo{depth: Int => "%s%s%s".format(_rbraceLeft(depth), closeBraceText, _rbraceRight(depth + 1))}
51+
52+
private val lbracketMemo = vectorMemo{depth: Int => "%s%s%s".format(_lbracketLeft(depth), openArrayText, _lbracketRight(depth + 1))}
53+
private val rbracketMemo = vectorMemo{depth: Int => "%s%s%s".format(_rbracketLeft(depth), closeArrayText, _rbracketRight(depth))}
54+
private val lrbracketsEmptyMemo = vectorMemo{depth: Int => "%s%s%s".format(openArrayText, _lrbracketsEmpty(depth), closeArrayText)}
55+
private val arrayCommaMemo = vectorMemo{depth: Int => "%s%s%s".format(_arrayCommaLeft(depth + 1), commaText, _arrayCommaRight(depth + 1))}
56+
private val objectCommaMemo = vectorMemo{depth: Int => "%s%s%s".format(_objectCommaLeft(depth + 1), commaText, _objectCommaRight(depth + 1))}
57+
private val colonMemo = vectorMemo{depth: Int => "%s%s%s".format(_colonLeft(depth + 1), colonText, _colonRight(depth + 1))}
58+
59+
/**
60+
* Returns a string representation of a pretty-printed JSON value.
61+
*/
62+
def render(j: Json): String = {
63+
64+
import Json._
65+
import StringEscaping._
66+
67+
def appendJsonString(builder: StringBuilder, jsonString: String): StringBuilder = {
68+
for (c <- jsonString) {
69+
if (isNormalChar(c))
70+
builder += c
71+
else
72+
builder.append(escape(c))
73+
}
74+
75+
builder
76+
}
77+
78+
def encloseJsonString(builder: StringBuilder, jsonString: JsonString): StringBuilder = {
79+
appendJsonString(builder.append(stringEnclosureText), jsonString).append(stringEnclosureText)
80+
}
81+
82+
def trav(builder: StringBuilder, depth: Int, k: Json): StringBuilder = {
83+
84+
def lbrace(builder: StringBuilder): StringBuilder = {
85+
builder.append(lbraceMemo(depth))
86+
}
87+
def rbrace(builder: StringBuilder): StringBuilder = {
88+
builder.append(rbraceMemo(depth))
89+
}
90+
def lbracket(builder: StringBuilder): StringBuilder = {
91+
builder.append(lbracketMemo(depth))
92+
}
93+
def rbracket(builder: StringBuilder): StringBuilder = {
94+
builder.append(rbracketMemo(depth))
95+
}
96+
def lrbracketsEmpty(builder: StringBuilder): StringBuilder = {
97+
builder.append(lrbracketsEmptyMemo(depth))
98+
}
99+
def arrayComma(builder: StringBuilder): StringBuilder = {
100+
builder.append(arrayCommaMemo(depth))
101+
}
102+
def objectComma(builder: StringBuilder): StringBuilder = {
103+
builder.append(objectCommaMemo(depth))
104+
}
105+
def colon(builder: StringBuilder): StringBuilder = {
106+
builder.append(colonMemo(depth))
107+
}
108+
109+
k.fold[StringBuilder](
110+
builder.append(nullText)
111+
, bool => builder.append(if (bool) trueText else falseText)
112+
, n => n match {
113+
case JsonLong(x) => builder append x.toString
114+
case JsonDecimal(x) => builder append x
115+
case JsonBigDecimal(x) => builder append x.toString
116+
}
117+
, s => encloseJsonString(builder, s)
118+
, e => if (e.isEmpty) {
119+
lrbracketsEmpty(builder)
120+
} else {
121+
rbracket(e.foldLeft((true, lbracket(builder))){case ((firstElement, builder), subElement) =>
122+
val withComma = if (firstElement) builder else arrayComma(builder)
123+
val updatedBuilder = trav(withComma, depth + 1, subElement)
124+
(false, updatedBuilder)
125+
}._2)
126+
}
127+
, o => {
128+
rbrace((if (preserveOrder) o.toList else o.toMap).foldLeft((true, lbrace(builder))){case ((firstElement, builder), (key, value)) =>
129+
val ignoreKey = dropNullKeys && value.isNull
130+
if (ignoreKey) {
131+
(firstElement, builder)
132+
} else {
133+
val withComma = if (firstElement) builder else objectComma(builder)
134+
(false, trav(colon(encloseJsonString(withComma, key)), depth + 1, value))
135+
}
136+
}._2)
137+
}
138+
)
139+
}
140+
141+
trav(new StringBuilder(), 0, j).toString()
142+
}
143+
144+
}

0 commit comments

Comments
 (0)