├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.sbt
├── codecov.yml
├── core
└── src
│ ├── main
│ ├── resources
│ │ └── webjars.csv
│ └── scala
│ │ └── vegas
│ │ ├── DSL
│ │ ├── AxisDSL.scala
│ │ ├── BinDSL.scala
│ │ ├── ConfigDSL.scala
│ │ ├── DataDSL.scala
│ │ ├── EncoderDSL.scala
│ │ ├── LegendDSL.scala
│ │ ├── Opt.scala
│ │ ├── ScaleDSL.scala
│ │ ├── SortDSL.scala
│ │ ├── SpecDSL.scala
│ │ ├── TransformDSL.scala
│ │ └── package.scala
│ │ ├── data
│ │ ├── External.scala
│ │ ├── FieldExtractor.scala
│ │ ├── SimpleTypeUtils.scala
│ │ └── ValueTransformer.scala
│ │ ├── package.scala
│ │ ├── render
│ │ ├── BaseHTMLRenderer.scala
│ │ ├── ShowRender.scala
│ │ ├── StaticHTMLRenderer.scala
│ │ ├── WindowRenderer.scala
│ │ └── package.scala
│ │ └── spec
│ │ ├── Spec.scala
│ │ └── package.scala
│ └── test
│ ├── resources
│ ├── example-specs
│ │ ├── area.vl.json
│ │ ├── area_vertical.vl.json
│ │ ├── bar.vl.json
│ │ ├── bar_1d.vl.json
│ │ ├── bar_1d_bandsize_config.vl.json
│ │ ├── bar_aggregate.vl.json
│ │ ├── bar_aggregate_size.vl.json
│ │ ├── bar_aggregate_vertical.vl.json
│ │ ├── bar_filter_calc.vl.json
│ │ ├── bar_grouped.vl.json
│ │ ├── bar_grouped_horizontal.vl.json
│ │ ├── bar_layered_transparent.vl.json
│ │ ├── bar_size_bandsize_small.vl.json
│ │ ├── bar_size_default.vl.json
│ │ ├── bar_size_explicit.vl.json
│ │ ├── bar_size_explicit_bad.vl.json
│ │ ├── bar_size_fit.vl.json
│ │ ├── bar_yearmonth.vl.json
│ │ ├── box_plot.vl.json
│ │ ├── bubble_health_income.vl.json
│ │ ├── circle.vl.json
│ │ ├── errorbar_aggregate.vl.json
│ │ ├── errorbar_horizontal_aggregate.vl.json
│ │ ├── field_spaces.vl.json
│ │ ├── github_punchcard.vl.json
│ │ ├── histogram.vl.json
│ │ ├── layer_bar_line.vl.json
│ │ ├── layer_bar_line_union.vl.json
│ │ ├── layer_histogram.vl.json
│ │ ├── layer_line_color_rule.vl.json
│ │ ├── line.vl.json
│ │ ├── line_color.vl.json
│ │ ├── line_detail.vl.json
│ │ ├── line_monotone.vl.json
│ │ ├── line_month.vl.json
│ │ ├── line_quarter.vl.json
│ │ ├── line_quarter_legend.vl.json
│ │ ├── line_slope.vl.json
│ │ ├── line_step.vl.json
│ │ ├── minimal.vl.json
│ │ ├── overlay_area_full.vl.json
│ │ ├── overlay_area_short.vl.json
│ │ ├── overlay_line_full.vl.json
│ │ ├── overlay_line_short.vl.json
│ │ ├── point_1d.vl.json
│ │ ├── point_color.vl.json
│ │ ├── point_dot_timeunit_color.vl.json
│ │ ├── point_filled.vl.json
│ │ ├── point_ordinal_color.vl.json
│ │ ├── scatter.vl.json
│ │ ├── scatter_aggregate_detail.vl.json
│ │ ├── scatter_binned.vl.json
│ │ ├── scatter_binned_color.vl.json
│ │ ├── scatter_binned_size.vl.json
│ │ ├── scatter_bubble.vl.json
│ │ ├── scatter_color.vl.json
│ │ ├── scatter_color_custom.vl.json
│ │ ├── scatter_color_order.vl.json
│ │ ├── scatter_color_ordinal.vl.json
│ │ ├── scatter_color_ordinal_custom.vl.json
│ │ ├── scatter_color_quantitative.vl.json
│ │ ├── scatter_color_shape_constant.vl.json
│ │ ├── scatter_colored_with_shape.vl.json
│ │ ├── scatter_connected.vl.json
│ │ ├── scatter_log.vl.json
│ │ ├── scatter_opacity.vl.json
│ │ ├── scatter_shape_custom.vl.json
│ │ ├── square.vl.json
│ │ ├── stacked_area.vl.json
│ │ ├── stacked_area_binned.vl.json
│ │ ├── stacked_area_normalize.vl.json
│ │ ├── stacked_area_ordinal.vl.json
│ │ ├── stacked_area_stream.vl.json
│ │ ├── stacked_bar_1d.vl.json
│ │ ├── stacked_bar_h.vl.json
│ │ ├── stacked_bar_h_order.vl.json
│ │ ├── stacked_bar_normalize.vl.json
│ │ ├── stacked_bar_population.vl.json
│ │ ├── stacked_bar_size.vl.json
│ │ ├── stacked_bar_sum_opacity.vl.json
│ │ ├── stacked_bar_v.vl.json
│ │ ├── stacked_bar_weather.vl.json
│ │ ├── text_scatter_colored.vl.json
│ │ ├── text_table_heatmap.vl.json
│ │ ├── tick_dot.vl.json
│ │ ├── tick_dot_thickness.vl.json
│ │ ├── tick_strip.vl.json
│ │ ├── trellis_anscombe.vl.json
│ │ ├── trellis_bar.vl.json
│ │ ├── trellis_bar_histogram.vl.json
│ │ ├── trellis_barley.vl.json
│ │ ├── trellis_row_column.vl.json
│ │ ├── trellis_scatter.vl.json
│ │ ├── trellis_scatter_binned_row.vl.json
│ │ └── trellis_stacked_bar.vl.json
│ └── log4j.properties
│ └── scala
│ └── vegas
│ ├── BaseSpec.scala
│ ├── DSL
│ ├── AllDSLSpec.scala
│ ├── DSLSpec.scala
│ ├── DataDSLSpec.scala
│ ├── OptSpec.scala
│ └── TransformDSLSpec.scala
│ ├── JsonMatchers.scala
│ ├── WebMatchers.scala
│ ├── data
│ ├── FieldExtractorSpec.scala
│ ├── SimpleTypeUtilsSpec.scala
│ └── ValueTransformerSpec.scala
│ ├── fixtures
│ ├── BasicPlots.scala
│ └── VegasPlots.scala
│ ├── integration
│ └── PlotHtml.scala
│ ├── macros
│ └── AliasWithLensSpec.scala
│ ├── render
│ ├── ShowSpec.scala
│ ├── StaticHTMLRendererSpec.scala
│ └── WindowRendererSpec.scala
│ ├── spec
│ └── SpecSpec.scala
│ └── util
│ ├── NotebookGenerator.scala
│ ├── Time.scala
│ └── WebGenerators.scala
├── flink
└── src
│ └── main
│ └── scala
│ └── vegas
│ └── flink
│ └── Flink.scala
├── macros
└── src
│ └── main
│ └── scala
│ └── vegas
│ └── macros
│ ├── AliasWithLens.scala
│ └── ShowRenderMacros.scala
├── notebooks
├── jupyter_example.ipynb
└── zeppelin_example.json
├── project
├── build.properties
└── plugins.sbt
├── spark
└── src
│ ├── main
│ └── scala
│ │ └── vegas
│ │ └── sparkExt
│ │ └── package.scala
│ └── test
│ └── scala
│ └── vegas
│ └── sparkExt
│ └── SparkExtSpec.scala
├── spec
└── src
│ ├── main
│ ├── resources
│ │ └── vega-lite-schema.json
│ └── scala
│ │ └── vegas
│ │ └── spec
│ │ └── Spec.scala
│ └── test
│ └── scala
│ └── FromSchemaSpec.scala
└── version.sbt
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .ipynb_checkpoints/
3 | core/target/
4 | project/project/
5 | project/target/
6 | spark/target/
7 | target/
8 | derby.log
9 | metastore_db/
10 | .swp
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 |
3 | dist: trusty
4 |
5 | # required: until bug is fixed: https://github.com/travis-ci/travis-ci/issues/3259
6 | sudo: required
7 | addons:
8 | apt:
9 | packages:
10 | - oracle-java8-installer
11 | - google-chrome-stable
12 | - unzip
13 |
14 | before_install:
15 | # Install Chrome driver
16 | - wget http://chromedriver.storage.googleapis.com/2.24/chromedriver_linux64.zip
17 | - unzip chromedriver_linux64.zip
18 | - sudo chmod u+x chromedriver
19 | - sudo mv chromedriver /usr/bin/
20 |
21 | # For selenium in headless linux system
22 | - "export DISPLAY=:99.0"
23 | - "sh -e /etc/init.d/xvfb start"
24 | - sleep 3 # give xvfb some time to start
25 |
26 | scala:
27 | - 2.11.7
28 | - 2.11.8
29 | - 2.11.12
30 | - 2.12.10
31 |
32 | jdk:
33 | - oraclejdk8
34 |
35 | script:
36 | - sbt clean coverage test coverageReport
37 | - sbt vegaLiteSpec/clean vegaLiteSpec/test mkVegaModel
38 |
39 | after_success:
40 | - bash <(curl -s https://codecov.io/bash)
41 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions welcome!
4 |
5 | * Submit contributions as pull requests. The PR should include a brief
6 | note describing the changes.
7 |
8 | * Any new functionality should include a unit test. Unit tests should
9 | be written in FlatSpec style, with each clause being small and testing a
10 | single thing.
11 |
12 | # Test setup and run
13 |
14 | The tests make use of ChromeDriver. First, you obviously need Chrome installed.
15 | Next you need to install ChromeDriver, which you can do on mac with the
16 | following:
17 |
18 | ```bash
19 | brew install chromedriver
20 | ```
21 |
22 | This also places chromeddriver on the shell path, which should be all you need.
23 |
24 | * Running the unit tests
25 |
26 | ```bash
27 | sbt test
28 | ```
29 |
30 | * Looking at example plots. The unit tests can only go so far (although
31 | we try to make them complete), so this command renders all the example
32 | plots out an HTML page and opens it in your browser.
33 |
34 | ```bash
35 | sbt look
36 | ```
37 |
38 | # Dev and debugging tips
39 |
40 | * Generate the vegas model and json codecs. Vegas generates most of the
41 | vega-lite model and json codecs from the vega-lite-schema.json file. To
42 | re-generate it run the following. This generates the code (in the vegaLiteSpec
43 | project) and copies the Spec.scala file into the Vegas src dir.
44 |
45 | ```bash
46 | sbt vegaLiteSpec/clean mkVegaModel
47 | ```
48 |
49 | * Updating vega-lite dependency version. If you want to pull in a new version
50 | of vega-lite. First, in build.sbt, update the vegaLiteVersion setting in
51 | commonSettings. Second, run the following sbt command to download the new
52 | vega-lite release.
53 |
54 | ```bash
55 | sbt updateVegaDeps
56 | ```
57 |
58 | # Releasing
59 |
60 | Releases are managed by the core team, but documenting the process here
61 | because we sometimes forget too :)
62 |
63 | The workflow for releases are managed through sbt. You need to have ```~/.sbt/0.13/sonatype.sbt```
64 | configured with the Sonatype credientials (which core contributors should have)
65 |
66 | 1. Run the following and follow prompts for version bumps, etc. This script
67 | runs the tests, bumps the version, tags the release, commits those changes,
68 | releases the package to Sonatype, and completes the Sontatype release
69 | process.
70 |
71 | ```bash
72 | sbt release
73 | ```
74 |
75 | 2. Go to the Vegas github [repo](https://github.com/aishfenton/Vegas) and
76 | add release notes under "releases"
77 |
78 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Aish Fenton, Sudeep Das, DB Tsai
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vegas
2 |
3 |
4 |
5 | [](https://travis-ci.org/vegas-viz/Vegas)
6 | [](https://codecov.io/gh/vegas-viz/Vegas)
7 |
8 | Vegas aims to be the missing MatPlotLib for the Scala and Spark world. Vegas wraps around [Vega-Lite](https://vega.github.io/vega-lite/) but provides syntax more familiar (and type checked) for use within Scala.
9 |
10 |
11 |
12 | ## Quick start
13 |
14 | Add the following jar as an SBT dependency
15 |
16 | ```sbt
17 | libraryDependencies += "org.vegas-viz" %% "vegas" % {vegas-version}
18 | ```
19 |
20 | And then use the following code to render a plot into a pop-up window (see below for more details on controlling how and where Vegas renders).
21 |
22 | ```scala
23 | import vegas._
24 | import vegas.render.WindowRenderer._
25 |
26 | val plot = Vegas("Country Pop").
27 | withData(
28 | Seq(
29 | Map("country" -> "USA", "population" -> 314),
30 | Map("country" -> "UK", "population" -> 64),
31 | Map("country" -> "DK", "population" -> 80)
32 | )
33 | ).
34 | encodeX("country", Nom).
35 | encodeY("population", Quant).
36 | mark(Bar)
37 |
38 | plot.show
39 | ```
40 |
41 | 
42 |
43 | See further examples [here](http://nbviewer.jupyter.org/github/aishfenton/Vegas/blob/master/notebooks/jupyter_example.ipynb)
44 |
45 | ## Rendering
46 |
47 | Vegas provides several options for rendering plots. The primary focus is using Vegas within interactive notebook environments, such as Jupyter and Zeppelin.
48 | Rendering is provided via an implicit instance of `ShowRender`, which tells Vegas how to display the plot in a particular environment. The default instance
49 | of `ShowRender` uses a macro which attempts to guess your environment, but if for some reason that fails, you can specify your own instance:
50 |
51 | ```scala
52 | // for outputting HTML, provide a function String => Unit which will receive the HTML for the plot
53 | // and use vegas.render.ShowHTML to create an instance for it
54 | implicit val renderer = vegas.render.ShowHTML(str => println(s"The HTML is $str"))
55 |
56 | // to specify a function that receives the SpecBuilder instead, use vegas.render.ShowRender.using
57 | implicit val renderer = vegas.render.ShowRender.using(sb => println(s"The SpecBuilder is $sb"))
58 | ```
59 |
60 | The following examples describe some common cases; these *should* be handled by the default macro, but are useful to
61 | see (in case you need to construct your own instance of `ShowRender`):
62 |
63 | ### Notebooks
64 |
65 | #### Jupyter - Scala
66 |
67 | If you're using [jupyter-scala](https://github.com/alexarchambault/jupyter-scala), then can include the following in your notebook before using Vegas.
68 |
69 | ```scala
70 | import $ivy.`org.vegas-viz::vegas:{vegas-version}`
71 | ```
72 |
73 | ```scala
74 | implicit val render = vegas.render.ShowHTML(publish(_))
75 | ```
76 |
77 | #### Jupyter - Apache Toree
78 |
79 | And if you're using [Apache Toree](https://toree.incubator.apache.org/), then this:
80 |
81 | ```
82 | %AddDeps org.vegas-viz vegas_2.11 {vegas-version} --transitive
83 | ```
84 |
85 | ```scala
86 | implicit val render = vegas.render.ShowHTML(kernel.display.content("text/html", _))
87 | ```
88 |
89 | #### Zeppelin
90 |
91 | If you're using [Apache Zeppelin](https://zeppelin.incubator.apache.org/):
92 |
93 | ```
94 | %dep
95 | z.load("org.vegas-viz:vegas_2.11:{vegas-version}")
96 | ```
97 | ```scala
98 | implicit val render = vegas.render.ShowHTML(s => print("%html " + s))
99 | ```
100 |
101 | The last line in each of the above is required to connect Vegas to the notebook's HTML renderer (so that the returned HTML is rendered instead of displayed as a string).
102 |
103 | See a comprehensive list example notebook of plots [here](http://nbviewer.jupyter.org/github/aishfenton/Vegas/blob/master/notebooks/jupyter_example.ipynb)
104 |
105 | ### Standalone
106 |
107 | Vegas can also be used to produce standalone HTML or even render plots within a built-in display app (useful if you wanted to display plots for a command-line-app).
108 |
109 | The construction of the plot is **independent from the rendering strategy**: the same plot can be rendered as HTML or in a Window simply by importing a different renderer in the scope.
110 |
111 | *Note that the rendering examples below are wrapped in separate functions to avoid ambiguous implicit conversions if they were imported in the same scope.*
112 |
113 | A plot is defined as:
114 |
115 | ```scala
116 | import vegas._
117 |
118 | val plot = Vegas("Country Pop").
119 | withData(
120 | Seq(
121 | Map("country" -> "USA", "population" -> 314),
122 | Map("country" -> "UK", "population" -> 64),
123 | Map("country" -> "DK", "population" -> 80)
124 | )
125 | ).
126 | encodeX("country", Nom).
127 | encodeY("population", Quant).
128 | mark(Bar)
129 | ```
130 |
131 | #### HTML
132 |
133 | The following renders the plot as HTML (which prints to the console).
134 |
135 | ```scala
136 | def renderHTML = {
137 | println(plot.html.pageHTML) // a complete HTML page containing the plot
138 | println(plot.html.frameHTML("foo")) // an iframe containing the plot
139 | }
140 | ```
141 |
142 | #### Window
143 |
144 | Vegas also contains a self-contained display app for displaying plots (internally it uses JavaFX's HTML renderer). The following demonstrates this and can be used from the command line.
145 |
146 | ```scala
147 | def renderWindow = {
148 | plot.window.show
149 | }
150 | ```
151 |
152 | Make sure JavaFX is installed on your system or ships with your JDK distribution.
153 |
154 | #### JSON
155 |
156 | You can print the JSON containing the Vega-lite spec, without importing any renderer in the scope.
157 |
158 | ```scala
159 | println(plot.toJson)
160 | ```
161 |
162 | The output JSON can be copy-pasted into the Vega-lite [editor](https://vega.github.io/vega-editor/?mode=vega-lite&spec=bar).
163 |
164 | ## Spark integration
165 |
166 | Vegas comes with an optional extension package that makes it easier to work with Spark DataFrames. First, you'll need an extra import
167 |
168 | ```sbt
169 | libraryDependencies += "org.vegas-viz" %% "vegas-spark" % "{vegas-version}"
170 | ```
171 |
172 | ```scala
173 | import vegas.sparkExt._
174 | ```
175 |
176 | This adds the following new method:
177 |
178 | ```scala
179 | withDataFrame(df: DataFrame)
180 | ```
181 |
182 | Each DataFrame column is exposed as a field keyed using the column's name.
183 |
184 | ## Flink integration
185 |
186 | Vegas also comes with an optional extension package that makes it easier to work with Flink DataSets. You'll also need to import:
187 | ```sbt
188 | libraryDependencies += "org.vegas-viz" %% "vegas-flink" % "{vegas-version}"
189 | ```
190 |
191 | To use:
192 | ```scala
193 | import vegas.flink.Flink._
194 | ```
195 |
196 | This adds the following method:
197 |
198 | ```scala
199 | withData[T <: Product](ds: DataSet[T])
200 | ```
201 | Similarly, to the RDD concept in Spark, a DataSet of _case classes_ or _tuples_ is expected and reflection is used to map the case class' fields to fields within Vegas. In the case of tuples you can encode the fields using `"_1", "_2"` and so on.
202 |
203 | ## Plot Options
204 |
205 | TODO
206 |
207 | ## Contributing
208 |
209 | See [the contributing guide](CONTRIBUTING.md) for more information on contributing bug fixes and features.
210 |
211 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | import ReleaseTransformations._
2 |
3 | import scala.language.postfixOps
4 | import scala.sys.process._
5 |
6 | // ---------
7 | // Setting / Task Definitions
8 | // ---------
9 |
10 | lazy val vegaLiteVersion = settingKey[String]("The release version of vega-lite to build off")
11 |
12 | lazy val updateVegaDeps = taskKey[Unit]("Download and replace the vega-lite json schema and examples with the latest versions from their github repo")
13 | updateVegaDeps := {
14 | val specResources = (resourceDirectory in Compile in vegaLiteSpec).value
15 | val coreResources = (resourceDirectory in Compile in vegas).value
16 | val coreTestResources = (resourceDirectory in Test in vegas).value
17 |
18 | val vegaDir = target.value / ("vega-lite-" + vegaLiteVersion.value)
19 | IO.unzipURL(new URL("https://github.com/vega/vega-lite/archive/v" + vegaLiteVersion.value + ".zip"), target.value)
20 | IO.copyDirectory(vegaDir / "examples" / "specs", coreTestResources / "example-specs", true)
21 | IO.copyFile(vegaDir / "vega-lite-schema.json", specResources / "vega-lite-schema.json")
22 |
23 | // Write WebJar.csv to resources, based on ivy-deps
24 | val deps = (externalDependencyClasspath in vegas in Compile).value
25 | val webJars = deps
26 | .map { artifact =>
27 | val m = artifact.get(Keys.moduleID.key).get
28 | (m.organization, m.name, m.revision)
29 | }
30 | .filter(_._1 == "org.webjars.bower")
31 | .map { case(_,n,v) => s"$n,$v" }
32 | .mkString("\n")
33 | IO.write(coreResources / "webjars.csv", webJars)
34 | }
35 |
36 | addCommandAlias("look", "vegas/test:runMain vegas.util.Look")
37 |
38 | lazy val mkVegaModel = taskKey[Unit]("Compiles and copies the vega-lite model and codec to the Vegas project")
39 |
40 | mkVegaModel := {
41 | val src = (scalaBinaryVersion.value match {
42 | case "2.11" => file("spec/target/scala-2.11/Spec.scala")
43 | case "2.12" => file("spec/target/scala-2.12/Spec.scala")
44 | })
45 | val dest = file("core/src/main/scala/vegas/spec/Spec.scala")
46 | IO.write(dest, """
47 | |// This file was automatically generated by the `sbt mkVegaModel` command.
48 | |// Do NOT edit manually.
49 | |
50 | |package vegas.spec
51 | |
52 | |""".stripMargin
53 | )
54 | IO.append(dest, IO.readBytes(src))
55 | }
56 |
57 | mkVegaModel := mkVegaModel
58 | .dependsOn(compile in vegaLiteSpec in Compile)
59 | .value
60 |
61 | lazy val lastReleaseVersion = taskKey[String]("Gets (using git tag) the version number of the last release")
62 | lastReleaseVersion := {
63 | ("git tag" !!).split("\n").head.tail
64 | }
65 |
66 | lazy val mkNotebooks = inputKey[Unit]("Generates /notebook examples based on example plots in the test fixtures")
67 | mkNotebooks := (Def.inputTaskDyn {
68 | val ver = version.value
69 | val baseDir = file("core/src/test/scala/vegas/fixtures")
70 | val files = (baseDir / "BasicPlots.scala") :: (baseDir / "VegasPlots.scala") :: Nil
71 | val dest = file("notebooks")
72 | val args = (ver :: files) :+ dest
73 | Def.taskDyn {
74 | (runMain in vegas in Test).toTask(" vegas.util.GenerateNotebooks " + args.mkString(" "))
75 | }
76 | }).evaluated
77 |
78 | // -------
79 | // Build Config
80 | // -------
81 |
82 | lazy val circeVersion = "0.7.0"
83 |
84 | lazy val commonSettings = Seq(
85 | description := "The missing matplotlib for Scala and Spark",
86 | organization := "org.vegas-viz",
87 | crossScalaVersions := Seq("2.11.8", "2.12.10"),
88 | scalaVersion := "2.12.10",
89 | vegaLiteVersion := "1.2.0",
90 | scalacOptions ++= Seq("-target:jvm-1.7", "-Ywarn-unused-import"),
91 | homepage := Some(url("http://vegas-viz.org")),
92 | licenses := Seq("MIT License" -> url("http://www.opensource.org/licenses/MIT")),
93 | // parallelExecution in Test := false,
94 |
95 | addCompilerPlugin("org.scalamacros" %% "paradise" % "2.1.0" cross CrossVersion.full),
96 | publishMavenStyle := true,
97 | publishTo := {
98 | val nexus = "https://oss.sonatype.org/"
99 | if (isSnapshot.value)
100 | Some("snapshots" at nexus + "content/repositories/snapshots")
101 | else
102 | Some("releases" at nexus + "service/local/staging/deploy/maven2")
103 | },
104 | publishArtifact in Test := false,
105 | pomIncludeRepository := { _ => false },
106 | sonatypeProfileName := "org.vegas-viz",
107 | pomExtra := (
108 |
109 | git@github.com:vegas-viz/Vegas.git
110 | scm:git:git@github.com:vegas-viz/Vegas.git
111 |
112 |
113 |
114 | aishfenton
115 | Aish Fenton
116 |
117 |
118 | datamusing
119 | Sudeep Das
120 |
121 |
122 | dbtsai
123 | DB Tsai
124 |
125 |
126 | rogermenezes
127 | Roger Menezes
128 |
129 |
130 | ),
131 | testOptions in Test += Tests.Argument("-oDF"),
132 | releaseProcess := Seq[ReleaseStep](
133 | checkSnapshotDependencies,
134 | inquireVersions,
135 | runClean,
136 | runTest,
137 | setReleaseVersion,
138 | releaseStepInputTask(mkNotebooks),
139 | commitReleaseVersion,
140 | tagRelease,
141 | // ReleaseStep(action = Command.process("publishSigned", _)),
142 | ReleaseStep(action = st => st.copy(remainingCommands = Exec("publishSigned", None) +: st.remainingCommands)),
143 | setNextVersion,
144 | commitNextVersion,
145 | // ReleaseStep(action = Command.process("sonatypeReleaseAll", _)),
146 | ReleaseStep(action = st => st.copy(remainingCommands = Exec("sonatypeReleaseAll", None) +: st.remainingCommands)),
147 | pushChanges
148 | )
149 | )
150 |
151 | lazy val noPublishSettings = Seq(
152 | publish := {},
153 | publishLocal := {},
154 | publishArtifact := false
155 | )
156 |
157 |
158 | lazy val macros = project.
159 | settings(moduleName := "vegas-macros").
160 | settings(commonSettings: _*).
161 | settings(
162 | libraryDependencies ++= Seq(
163 | "org.scala-lang" % "scala-reflect" % scalaVersion.value,
164 | "org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided",
165 | "org.typelevel" %% "macro-compat" % "1.1.1",
166 | "com.github.julien-truffaut" %% "monocle-core" % (scalaBinaryVersion.value match {
167 | case "2.11" => "1.1.0"
168 | case "2.12" => "1.3.2"
169 | })
170 | )
171 | )
172 |
173 |
174 | // This project exists just to generate the Vega-Lite Json model + codecs
175 | lazy val vegaLiteSpec = project.in(file("spec")).
176 | settings(commonSettings: _*).
177 | settings(noPublishSettings: _*).
178 | settings(
179 | libraryDependencies ++= Seq(
180 | "io.circe" %% "circe-core" % circeVersion,
181 | "io.circe" %% "circe-generic" % circeVersion,
182 | "io.circe" %% "circe-parser" % circeVersion,
183 | "com.github.aishfenton" %% "argus" % "0.2.7",
184 | "org.scalactic" %% "scalactic" % "3.0.5" % "test",
185 | "org.scalatest" %% "scalatest" % "3.0.5" % "test"
186 | )
187 | )
188 | // settings(sourceGenerators in Compile <+= (sourceManaged in Compile) map genCode)
189 |
190 | // Determine OS version of JavaFX binaries
191 | lazy val osName = System.getProperty("os.name") match {
192 | case n if n.startsWith("Linux") => "linux"
193 | case n if n.startsWith("Mac") => "mac"
194 | case n if n.startsWith("Windows") => "win"
195 | case _ => throw new Exception("Unknown platform!")
196 | }
197 |
198 | lazy val javaFXModules = Seq("base", "controls", "fxml", "graphics", "media", "swing", "web")
199 |
200 | lazy val vegas = project.in(file("core")).
201 | settings(moduleName := "vegas").
202 | dependsOn(macros).
203 | settings(commonSettings: _*).
204 | settings(
205 | libraryDependencies ++= Seq(
206 | "io.circe" %% "circe-core" % circeVersion,
207 | "io.circe" %% "circe-generic" % circeVersion,
208 | "io.circe" %% "circe-parser" % circeVersion,
209 | "com.github.julien-truffaut" %% "monocle-macro" % (scalaBinaryVersion.value match {
210 | case "2.11" => "1.1.0"
211 | case "2.12" => "1.3.2"
212 | }),
213 | "com.github.julien-truffaut" %% "monocle-core" % (scalaBinaryVersion.value match {
214 | case "2.11" => "1.1.0"
215 | case "2.12" => "1.3.2"
216 | }),
217 | "org.scalafx" %% "scalafx" % "12.0.2-R18",
218 | "org.scala-lang.modules" %% "scala-xml" % "1.0.6",
219 |
220 | // JS deps. Also used to generate "webjars.csv" file for CDN loading.
221 | "org.webjars.bower" % "vega-lite" % vegaLiteVersion.value,
222 |
223 | // Test deps
224 | "com.github.aishfenton" %% "argus" % "0.2.7" % "test",
225 | "org.scalactic" %% "scalactic" % "3.0.5" % "test",
226 | "org.scalatest" %% "scalatest" % "3.0.5" % "test",
227 | "org.seleniumhq.selenium" % "selenium-java" % "3.13.0" % "test"
228 | ) ++ javaFXModules.map( m =>
229 | "org.openjfx" % s"javafx-$m" % "11" classifier osName
230 | )
231 | )
232 |
233 | // https://stackoverflow.com/questions/48653876/aggregate-different-modules-based-on-scala-binary-version
234 |
235 | lazy val spark = project.
236 | settings(moduleName := "vegas-spark").
237 | dependsOn(vegas % "compile->compile; test->test").
238 | settings(commonSettings: _*).
239 | settings(
240 | libraryDependencies ++= Seq(
241 | "org.apache.spark" %% "spark-sql" % "[2.0,)" % "provided"
242 | )
243 | ).
244 | // remove spark dep and skip compile if version is 2.12
245 | settings(
246 | libraryDependencies := (if (scalaBinaryVersion.value == "2.12") Seq.empty
247 | else libraryDependencies.value),
248 | skip in compile := scalaBinaryVersion.value == "2.12",
249 | skip in publish := scalaBinaryVersion.value == "2.12"
250 | )
251 |
252 |
253 | lazy val flink = project.
254 | settings(moduleName := "vegas-flink").
255 | dependsOn(vegas).
256 | settings(commonSettings: _*).
257 | settings(
258 | libraryDependencies ++= Seq(
259 | "org.apache.flink" %% "flink-scala" % "[1.1.1,)" % "provided",
260 | "org.apache.flink" %% "flink-clients" % "[1.1.1,)" % "provided"
261 | )
262 | ).
263 | settings(
264 | libraryDependencies := (if (scalaBinaryVersion.value == "2.12") Seq.empty
265 | else libraryDependencies.value),
266 | skip in compile := scalaBinaryVersion.value == "2.12",
267 | skip in publish := scalaBinaryVersion.value == "2.12"
268 | )
269 |
270 | lazy val root = (project in file(".")).
271 | aggregate(vegas, spark, flink, macros).
272 | settings(commonSettings: _*).
273 | settings(noPublishSettings: _*)
274 |
275 |
276 | // Clears screen between refreshes in continuous mode
277 | maxErrors := 5
278 | triggeredMessage := Watched.clearWhenTriggered
279 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - "core/src/main/scala/vegas/spec/Spec.scala"
3 |
--------------------------------------------------------------------------------
/core/src/main/resources/webjars.csv:
--------------------------------------------------------------------------------
1 | vega-lite,1.2.0
2 | vega,2.6.3
3 | d3,3.5.17
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/DSL/AxisDSL.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import vegas.spec.Spec
4 |
5 | object AxisDSL {
6 |
7 | def apply(labelAngle: OptArg[Double] = NoArg,
8 | format: OptArg[String] = NoArg,
9 | orient: OptArg[Spec.AxisOrient] = NoArg,
10 | title: OptArg[String] = NoArg,
11 | values: OptArg[List[Double]] = NoArg,
12 | axisWidth: OptArg[Double] = NoArg,
13 | layer: OptArg[String] = NoArg,
14 | offset: OptArg[Double] = NoArg,
15 | axisColor: OptArg[String] = NoArg,
16 | grid: OptArg[Boolean] = NoArg,
17 | gridColor: OptArg[String] = NoArg,
18 | gridDash: OptArg[List[Double]] = NoArg,
19 | gridOpacity: OptArg[Double] = NoArg,
20 | gridWidth: OptArg[Double] = NoArg,
21 | labels: OptArg[Boolean] = NoArg,
22 | labelAlign: OptArg[String] = NoArg,
23 | labelBaseline: OptArg[String] = NoArg,
24 | labelMaxLength: OptArg[Double] = NoArg,
25 | shortTimeLabels: OptArg[Boolean] = NoArg,
26 | subdivide: OptArg[Double] = NoArg,
27 | ticks: OptArg[Double] = NoArg,
28 | tickColor: OptArg[String] = NoArg,
29 | tickLabelColor: OptArg[String] = NoArg,
30 | tickLabelFont: OptArg[String] = NoArg,
31 | tickLabelFontSize: OptArg[Double] = NoArg,
32 | tickPadding: OptArg[Double] = NoArg,
33 | tickSize: OptArg[Double] = NoArg,
34 | tickSizeMajor: OptArg[Double] = NoArg,
35 | tickSizeMinor: OptArg[Double] = NoArg,
36 | tickSizeEnd: OptArg[Double] = NoArg,
37 | tickWidth: OptArg[Double] = NoArg,
38 | titleColor: OptArg[String] = NoArg,
39 | titleFont: OptArg[String] = NoArg,
40 | titleFontSize: OptArg[Double] = NoArg,
41 | titleFontWeight: OptArg[String] = NoArg,
42 | titleOffset: OptArg[Double] = NoArg,
43 | titleMaxLength: OptArg[Double] = NoArg,
44 | characterWidth: OptArg[Double] = NoArg,
45 | properties: OptArg[Spec.Axis.Properties] = NoArg) = {
46 |
47 | Spec.Axis(
48 | labelAngle = labelAngle,
49 | format = format,
50 | orient = orient,
51 | title = title,
52 | values = values,
53 | axisWidth = axisWidth,
54 | layer = layer,
55 | offset = offset,
56 | axisColor = axisColor,
57 | grid = grid,
58 | gridColor = gridColor,
59 | gridDash = gridDash,
60 | gridOpacity = gridOpacity,
61 | gridWidth = gridWidth,
62 | labels = labels,
63 | labelAlign = labelAlign,
64 | labelBaseline = labelBaseline,
65 | labelMaxLength = labelMaxLength,
66 | shortTimeLabels = shortTimeLabels,
67 | subdivide = subdivide,
68 | ticks = ticks,
69 | tickColor = tickColor,
70 | tickLabelColor = tickLabelColor,
71 | tickLabelFont = tickLabelFont,
72 | tickLabelFontSize = tickLabelFontSize,
73 | tickPadding = tickPadding,
74 | tickSize = tickSize,
75 | tickSizeMajor = tickSizeMajor,
76 | tickSizeMinor = tickSizeMinor,
77 | tickSizeEnd = tickSizeEnd,
78 | tickWidth = tickWidth,
79 | titleColor = titleColor,
80 | titleFont = titleFont,
81 | titleFontSize = titleFontSize,
82 | titleFontWeight = titleFontWeight,
83 | titleOffset = titleOffset,
84 | titleMaxLength = titleMaxLength,
85 | characterWidth = characterWidth,
86 | properties = properties)
87 | }
88 |
89 | }
90 |
91 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/DSL/BinDSL.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import vegas.spec.Spec
4 |
5 | object BinDSL {
6 |
7 | def apply(min: OptArg[Double] = NoArg,
8 | max: OptArg[Double] = NoArg,
9 | base: OptArg[Double] = NoArg,
10 | step: OptArg[Double] = NoArg,
11 | steps: OptArg[List[Double]] = NoArg,
12 | minstep: OptArg[Double] = NoArg,
13 | div: OptArg[List[Double]] = NoArg,
14 | maxbins: OptArg[Double] = NoArg) = {
15 |
16 | Spec.Bin(min=min,
17 | max=max,
18 | base=base,
19 | step=step,
20 | steps=steps,
21 | minstep=minstep,
22 | div=div,
23 | maxbins=maxbins
24 | )
25 | }
26 |
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/DSL/DataDSL.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import monocle.macros.GenLens
4 | import monocle.Lens
5 | import vegas.spec.Spec._
6 |
7 | import vegas.data.{ FieldExtractor, ValueTransformer }
8 |
9 | /**
10 | * @tparam T the base builder type. Needs to be generic since this can be mixed into different places
11 | */
12 | trait DataDSL[T] {
13 | self: T =>
14 |
15 | protected[this] def _data: Lens[T, Option[Data]]
16 |
17 | private val _values = GenLens[Data](_.values)
18 |
19 | /**
20 | * Uses data from an external source as specified by the given URL.
21 | * @param url The URL for the external data source.
22 | * @param formatType The type of the data (i.e. DataFormat.Json, DataFormat.Csv, etc).
23 | */
24 | def withURL(url: String, formatType: OptArg[DataFormatType] = NoArg): T = {
25 | val data = Data(
26 | url = Some(url),
27 | format = formatType.map( t => DataFormat(`type`= Some(t)))
28 | )
29 | _data.set(Some(data))(this)
30 | }
31 |
32 | /**
33 | * Specifies data as a Seq of rows, where each row is specified using a Map of column -> value pairs.
34 | * @param values A Seq of Maps, with each Map specifying the column -> value pairs of a row.
35 | * @param vt. Since values are of type Any, we need to transform these into something
36 | * vega-lite can handle. By default we turn anything that isn't a primitive type
37 | * into a String, and format Dates as ISO-8601.
38 | */
39 | def withData(values: Seq[Map[String, Any]])(implicit vt: ValueTransformer): T = {
40 | // Transform first
41 | val data = Data(
42 | values = Some(values.toList.map { row =>
43 | val newRow = vt.transform(row)
44 | Data.Values(newRow)
45 | })
46 | )
47 |
48 | _data.set(Some(data))(this)
49 | }
50 |
51 | /**
52 | * Specifies data as a Seq of values (i.e. Array(1.2, 4.2, 5,6)). The array indices are used to create a column "x",
53 | * and the array's values to create a column "y". To encode this data you'd use:
54 | *
55 | * encodeX("x", Ordinal)
56 | * encodeX("y", ...)
57 | *
58 | * @param values A Seq[Any] containing the values to use.
59 | */
60 | def withValues(values: Seq[Any])(implicit vt: ValueTransformer): T = {
61 | val data = values.zipWithIndex.map { case(y, i) => Map("x" -> i, "y" -> y) }
62 | withData(data)
63 | }
64 |
65 | /**
66 | * Specifies data as a Seq of x,y values represented by the tuple (Any, Any). Each column is named "x", "y". To
67 | * encode the data you'd use:
68 | *
69 | * encodeX("x", ...).
70 | * encodeY("y", ...)
71 | *
72 | * @param values A Seq of (Any, Any) tuples
73 | */
74 | def withXY(values: Seq[(Any, Any)])(implicit vt: ValueTransformer): T = {
75 | val data = values.map { case(x, y) => Map("x" -> x, "y" -> y) }
76 | withData(data)
77 | }
78 |
79 | /**
80 | * Specifies data as a Seq of Seq data (i.e Array(Array(1,2,3), Array(4,5,6)), where each inner Seq represents a row of
81 | * data. Column names within the rows are named after their array indexes (0 based). So, for example, to encode this data
82 | * you'd write:
83 | *
84 | * encodeX("0", Quant).
85 | * encodeY("1", Quant).
86 | * encodeSize("2", Ord).
87 | *
88 | * @param values A Seq[Seq[Any]] where each inner sequence is treated as a row of data.
89 | */
90 | def withSeqValues(values: Seq[Seq[Any]])(implicit vt: ValueTransformer): T = {
91 | val v = values.map(_.zipWithIndex.map { case(v,i) => (i.toString,v) }.toMap)
92 | withData(v)
93 | }
94 |
95 | /**
96 | * Specifies data as a Seq of case-classes. Each field within the case class becomes a row within the data. And each
97 | * column is named after the field names within the case class.
98 | * @param values: Expects an array of case classes, but no way to enforce this. Uses reflection to pull out
99 | * fields.
100 | */
101 | def withCaseClasses(values: Seq[Product])(implicit vt: ValueTransformer): T = {
102 | val v = values.map(FieldExtractor.extractFields)
103 | withData(v)
104 | }
105 |
106 | }
107 |
108 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/DSL/EncoderDSL.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import monocle.{Lens, Optional}
4 | import monocle.macros.GenLens
5 | import vegas.data.SimpleTypeUtils
6 | import vegas.spec.Spec._
7 | import vegas.macros.{alias_with_lens, aliased}
8 |
9 | @aliased
10 | trait EncoderDSL[T] extends BaseEncoderDSL[T] {
11 | self: T =>
12 |
13 | protected[this] def _encoding: Lens[T, Option[Encoding]]
14 |
15 | private val _column = GenLens[Encoding](_.column)
16 | private val _row = GenLens[Encoding](_.row)
17 | private val _x = GenLens[Encoding](_.x)
18 | private val _y = GenLens[Encoding](_.y)
19 | private val _x2 = GenLens[Encoding](_.x2)
20 | private val _y2 = GenLens[Encoding](_.y2)
21 |
22 | private val _color = GenLens[Encoding](_.color)
23 | private val _opacity = GenLens[Encoding](_.opacity)
24 | private val _size = GenLens[Encoding](_.size)
25 | private val _shape = GenLens[Encoding](_.shape)
26 |
27 | private val _text = GenLens[Encoding](_.text)
28 | private val _label = GenLens[Encoding](_.label)
29 | private val _detail = GenLens[Encoding](_.detail)
30 |
31 | // TODO
32 | private val _path = GenLens[Encoding](_.path)
33 | private val _order = GenLens[Encoding](_.order)
34 |
35 | @alias_with_lens("encodeColumn", _column)
36 | @alias_with_lens("encodeRow", _row)
37 | @alias_with_lens("encodeX", _x)
38 | @alias_with_lens("encodeY", _y)
39 | private def encodePCD_(l: Lens[Encoding, Option[PositionChannelDef]])
40 | (field: OptArg[String] = NoArg, dataType: OptArg[Type] = NoArg,
41 | aggregate: OptArg[AggregateOp] = NoArg, value: OptArg[Any] = NoArg,
42 | axis: OptArg[Axis] = NoArg, hideAxis: OptArg[Boolean] = NoArg,
43 | scale: OptArg[Scale] = NoArg, timeUnit: OptArg[TimeUnit] = NoArg, title: OptArg[String] = NoArg,
44 | bin: OptArg[Bin] = NoArg, enableBin: OptArg[Boolean] = NoArg,
45 | sortField: OptArg[SortField] = NoArg, sortOrder: OptArg[SortOrder] = NoArg) = {
46 |
47 | val lens = (_encoding composePrism _orElse(Encoding()) composeLens l)
48 |
49 | baseEncodePCD(lens)(field, dataType, aggregate, value, axis, hideAxis, scale, timeUnit, title, bin, enableBin,
50 | sortField, sortOrder)
51 |
52 | }
53 |
54 | @alias_with_lens("encodeColor", _color)
55 | @alias_with_lens("encodeOpacity", _opacity)
56 | @alias_with_lens("encodeSize", _size)
57 | @alias_with_lens("encodeShape", _shape)
58 | private def encodeCDWL_(l: Lens[Encoding, Option[ChannelDefWithLegend]])
59 | (field: OptArg[String] = NoArg, dataType: OptArg[Type] = NoArg,
60 | aggregate: OptArg[AggregateOp] = NoArg, value: OptArg[Any] = NoArg, scale:OptArg[Scale] = NoArg,
61 | timeUnit: OptArg[TimeUnit] = NoArg, title: OptArg[String] = NoArg, legend: OptArg[Legend] = NoArg,
62 | bin: OptArg[Bin] = NoArg, enableBin: OptArg[Boolean] = NoArg,
63 | sortField: OptArg[SortField] = NoArg, sortOrder: OptArg[SortOrder] = NoArg) = {
64 |
65 | val lens = (_encoding composePrism _orElse(Encoding()) composeLens l)
66 |
67 | baseEncodeCDWL(lens)(field, dataType, aggregate, value, scale, timeUnit, title, legend, bin, enableBin,
68 | sortField, sortOrder)
69 |
70 | }
71 |
72 | @alias_with_lens("encodeText", _text)
73 | @alias_with_lens("encodeLabel", _label)
74 | @alias_with_lens("encodeX2", _x2)
75 | @alias_with_lens("encodeY2", _y2)
76 | private def encodeFD_(l: Lens[Encoding, Option[FieldDef]])
77 | (field: OptArg[String] = NoArg, dataType: OptArg[Type] = NoArg, value: OptArg[Any] = NoArg,
78 | timeUnit: OptArg[TimeUnit] = NoArg, bin: OptArg[Bin] = NoArg, enableBin: OptArg[Boolean] = NoArg,
79 | aggregate: OptArg[AggregateOp] = NoArg,
80 | title: OptArg[String] = NoArg) = {
81 |
82 | val lens = (_encoding composePrism _orElse(Encoding()) composeLens l)
83 |
84 | baseEncoderFD(lens)(field, dataType, value, timeUnit, bin, enableBin, aggregate, title)
85 |
86 | }
87 |
88 | def encodeDetailFields(fields: FieldDef*): T = {
89 | val detailU = if (fields.size == 1) Encoding.DetailFieldDef(fields.head) else Encoding.DetailListFieldDef(fields.toList)
90 | val lens = (_encoding composePrism _orElse(Encoding()) composeLens _detail)
91 | lens.set(Some(detailU))(this)
92 | }
93 |
94 |
95 | }
96 |
97 |
98 | @aliased
99 | trait UnitEncoderDSL[T] extends BaseEncoderDSL[T] {
100 | self: T =>
101 |
102 | protected[this] def _encoding: Lens[T, Option[UnitEncoding]]
103 |
104 | private val _x = GenLens[UnitEncoding](_.x)
105 | private val _y = GenLens[UnitEncoding](_.y)
106 | private val _x2 = GenLens[UnitEncoding](_.x2)
107 | private val _y2 = GenLens[UnitEncoding](_.y2)
108 |
109 | private val _color = GenLens[UnitEncoding](_.color)
110 | private val _opacity = GenLens[UnitEncoding](_.opacity)
111 | private val _size = GenLens[UnitEncoding](_.size)
112 | private val _shape = GenLens[UnitEncoding](_.shape)
113 |
114 | private val _text = GenLens[UnitEncoding](_.text)
115 | private val _label = GenLens[UnitEncoding](_.label)
116 | private val _detail = GenLens[UnitEncoding](_.detail)
117 |
118 | // TODO
119 | private val _path = GenLens[UnitEncoding](_.path)
120 | private val _order = GenLens[UnitEncoding](_.order)
121 |
122 | @alias_with_lens("encodeX", _x)
123 | @alias_with_lens("encodeY", _y)
124 | private def encodePCD_(l: Lens[UnitEncoding, Option[PositionChannelDef]])
125 | (field: OptArg[String] = NoArg, dataType: OptArg[Type] = NoArg,
126 | aggregate: OptArg[AggregateOp] = NoArg, value: OptArg[Any] = NoArg,
127 | axis: OptArg[Axis] = NoArg, hideAxis: OptArg[Boolean] = NoArg,
128 | scale: OptArg[Scale] = NoArg, timeUnit: OptArg[TimeUnit] = NoArg, title: OptArg[String] = NoArg,
129 | bin: OptArg[Bin] = NoArg, enableBin: OptArg[Boolean] = NoArg,
130 | sortField: OptArg[SortField] = NoArg, sortOrder: OptArg[SortOrder] = NoArg) = {
131 |
132 | val lens = (_encoding composePrism _orElse(UnitEncoding()) composeLens l)
133 | baseEncodePCD(lens)(field, dataType, aggregate, value, axis, hideAxis, scale, timeUnit, title, bin, enableBin,
134 | sortField, sortOrder)
135 | }
136 |
137 | @alias_with_lens("encodeColor", _color)
138 | @alias_with_lens("encodeOpacity", _opacity)
139 | @alias_with_lens("encodeSize", _size)
140 | @alias_with_lens("encodeShape", _shape)
141 | private def encodeCDWL_(l: Lens[UnitEncoding, Option[ChannelDefWithLegend]])
142 | (field: OptArg[String] = NoArg, dataType: OptArg[Type] = NoArg,
143 | aggregate: OptArg[AggregateOp] = NoArg, value: OptArg[Any] = NoArg, scale:OptArg[Scale] = NoArg,
144 | timeUnit: OptArg[TimeUnit] = NoArg, title: OptArg[String] = NoArg, legend: OptArg[Legend] = NoArg,
145 | bin: OptArg[Bin] = NoArg, enableBin: OptArg[Boolean] = NoArg,
146 | sortField: OptArg[SortField] = NoArg, sortOrder: OptArg[SortOrder] = NoArg) = {
147 |
148 | val lens = (_encoding composePrism _orElse(UnitEncoding()) composeLens l)
149 | baseEncodeCDWL(lens)(field, dataType, aggregate, value, scale, timeUnit, title, legend, bin, enableBin,
150 | sortField, sortOrder)
151 | }
152 |
153 | @alias_with_lens("encodeText", _text)
154 | @alias_with_lens("encodeLabel", _label)
155 | @alias_with_lens("encodeX2", _x2)
156 | @alias_with_lens("encodeY2", _y2)
157 | private def encodeFD_(l: Lens[UnitEncoding, Option[FieldDef]])
158 | (field: OptArg[String] = NoArg, dataType: OptArg[Type] = NoArg, value: OptArg[Any] = NoArg,
159 | timeUnit: OptArg[TimeUnit] = NoArg, bin: OptArg[Bin] = NoArg, enableBin: OptArg[Boolean] = NoArg,
160 | aggregate: OptArg[AggregateOp] = NoArg,
161 | title: OptArg[String] = NoArg) = {
162 |
163 | val lens = (_encoding composePrism _orElse(UnitEncoding()) composeLens l)
164 |
165 | baseEncoderFD(lens)(field, dataType, value, timeUnit, bin, enableBin, aggregate, title)
166 | }
167 |
168 | def encodeDetailFields(fields: FieldDef*): T = {
169 | val detailU = if (fields.size == 1) UnitEncoding.DetailFieldDef(fields.head) else UnitEncoding.DetailListFieldDef(fields.toList)
170 | val lens = (_encoding composePrism _orElse(UnitEncoding()) composeLens _detail)
171 | lens.set(Some(detailU))(this)
172 | }
173 |
174 | }
175 |
176 | trait BaseEncoderDSL[T] {
177 | self: T =>
178 |
179 | protected def baseEncodePCD(lens: Optional[T, Option[PositionChannelDef]])
180 | (field: OptArg[String] = NoArg, dataType: OptArg[Type] = NoArg,
181 | aggregate: OptArg[AggregateOp] = NoArg, value: OptArg[Any] = NoArg,
182 | axis: OptArg[Axis] = NoArg, hideAxis: OptArg[Boolean] = NoArg,
183 | scale: OptArg[Scale] = NoArg, timeUnit: OptArg[TimeUnit] = NoArg, title: OptArg[String] = NoArg,
184 | bin: OptArg[Bin] = NoArg, enableBin: OptArg[Boolean] = NoArg,
185 | sortField: OptArg[SortField] = NoArg, sortOrder: OptArg[SortOrder] = NoArg) = {
186 |
187 | val valueU = value.map {
188 | case b: Boolean => PositionChannelDef.ValueBoolean(b)
189 | case s: String => PositionChannelDef.ValueString(s)
190 | case x@_ => SimpleTypeUtils.toDouble(x).map(PositionChannelDef.ValueDouble(_))
191 | .getOrElse(throw new Exception("Value must be AnyVal, Boolean, or String"))
192 | }
193 | val axisU = (axis.map(PositionChannelDef.AxisAxis(_)) orElse hideAxis.map(b => PositionChannelDef.AxisBoolean( !b )))
194 | val binU = (bin.map(PositionChannelDef.BinBin(_)) orElse enableBin.map(b => PositionChannelDef.BinBoolean( b )))
195 |
196 | val sortU = (sortField.map(PositionChannelDef.SortSortField(_)) orElse sortOrder.map(PositionChannelDef.SortSortOrder(_)))
197 |
198 | val cd = PositionChannelDef(field=field, `type`=dataType, aggregate=aggregate, axis=axisU, scale=scale, value=valueU,
199 | timeUnit=timeUnit, title=title, bin=binU, sort=sortU)
200 |
201 | lens.set(Some(cd))(this)
202 | }
203 |
204 | protected def baseEncodeCDWL(lens: Optional[T, Option[ChannelDefWithLegend]])
205 | (field: OptArg[String] = NoArg, dataType: OptArg[Type] = NoArg,
206 | aggregate: OptArg[AggregateOp] = NoArg, value: OptArg[Any] = NoArg,
207 | scale: OptArg[Scale] = NoArg, timeUnit: OptArg[TimeUnit] = NoArg,
208 | title: OptArg[String] = NoArg, legend: OptArg[Legend]= NoArg,
209 | bin: OptArg[Bin] = NoArg, enableBin: OptArg[Boolean] = NoArg,
210 | sortField: OptArg[SortField] = NoArg, sortOrder: OptArg[SortOrder] = NoArg) = {
211 |
212 | val valueU = value.map {
213 | case b: Boolean => ChannelDefWithLegend.ValueBoolean(b)
214 | case s: String => ChannelDefWithLegend.ValueString(s)
215 | case x@_ => SimpleTypeUtils.toDouble(x).map(ChannelDefWithLegend.ValueDouble(_))
216 | .getOrElse(throw new Exception("Value must be AnyVal, Boolean, or String"))
217 | }
218 | val binU = (bin.map(ChannelDefWithLegend.BinBin(_)) orElse enableBin.map(b => ChannelDefWithLegend.BinBoolean( b )))
219 | val sortU = (sortField.map(ChannelDefWithLegend.SortSortField(_)) orElse sortOrder.map(ChannelDefWithLegend.SortSortOrder(_)))
220 |
221 | val cd = ChannelDefWithLegend(field = field, `type` = dataType, aggregate = aggregate, value = valueU, scale = scale,
222 | timeUnit = timeUnit, title = title, legend=legend, bin=binU, sort=sortU)
223 | lens.set(Some(cd))(this)
224 | }
225 |
226 | protected def baseEncoderFD(lens: Optional[T, Option[FieldDef]])
227 | (field: OptArg[String] = NoArg,
228 | dataType: OptArg[Type] = NoArg,
229 | value: OptArg[Any] = NoArg,
230 | timeUnit: OptArg[TimeUnit] = NoArg,
231 | bin: OptArg[Bin] = NoArg,
232 | enableBin: OptArg[Boolean] = NoArg,
233 | aggregate: OptArg[AggregateOp] = NoArg,
234 | title: OptArg[String] = NoArg) = {
235 | val valueU = value.map {
236 | case b: Boolean => FieldDef.ValueBoolean(b)
237 | case s: String => FieldDef.ValueString(s)
238 | case x@_ => SimpleTypeUtils.toDouble(x).map(FieldDef.ValueDouble(_))
239 | .getOrElse(throw new Exception("Value must be AnyVal, Boolean, or String"))
240 | }
241 |
242 | val binU = (bin.map(FieldDef.BinBin(_)) orElse enableBin.map(b => FieldDef.BinBoolean( b )))
243 |
244 | val fd = FieldDef(field = field, `type` = dataType, aggregate = aggregate, value = valueU, timeUnit = timeUnit,
245 | bin = binU, title=title)
246 |
247 | lens.set(Some(fd))(this)
248 |
249 | }
250 |
251 | def encodeDetailFields(fields: FieldDef*): T
252 |
253 | def encodeDetail(fields: String*): T = {
254 | val fieldDefs = fields.map { f => FieldDef.apply(field = Some(f)) }
255 | encodeDetailFields(fieldDefs: _*)
256 | }
257 |
258 | }
259 |
260 | object FieldDSL {
261 |
262 | def apply(field: OptArg[String] = NoArg,
263 | dataType: OptArg[Type] = NoArg,
264 | value: OptArg[Any] = NoArg,
265 | timeUnit: OptArg[TimeUnit] = NoArg,
266 | bin: OptArg[Bin] = NoArg,
267 | enableBin: OptArg[Boolean] = NoArg,
268 | aggregate: OptArg[AggregateOp] = NoArg,
269 | title: OptArg[String] = NoArg) = {
270 |
271 | val valueU = value.map {
272 | case b: Boolean => FieldDef.ValueBoolean(b)
273 | case s: String => FieldDef.ValueString(s)
274 | case x@_ => SimpleTypeUtils.toDouble(x).map(FieldDef.ValueDouble(_))
275 | .getOrElse(throw new Exception("Value must be AnyVal, Boolean, or String"))
276 | }
277 |
278 | val binU = (bin.map(FieldDef.BinBin(_)) orElse enableBin.map(b => FieldDef.BinBoolean( b )))
279 |
280 | FieldDef(
281 | field = field,
282 | `type` = dataType,
283 | value = valueU,
284 | timeUnit = timeUnit,
285 | bin = binU,
286 | aggregate = aggregate
287 | )
288 |
289 | }
290 |
291 | }
292 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/DSL/LegendDSL.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import vegas.spec.Spec
4 |
5 | object LegendDSL {
6 |
7 | def apply(format: OptArg[String] = NoArg,
8 | title: OptArg[String] = NoArg,
9 | values: OptArg[List[Spec.Legend.Values]] = NoArg,
10 | orient: OptArg[String] = NoArg,
11 | offset: OptArg[Double] = NoArg,
12 | padding: OptArg[Double] = NoArg,
13 | margin: OptArg[Double] = NoArg,
14 | gradientStrokeColor: OptArg[String] = NoArg,
15 | gradientStrokeWidth: OptArg[Double] = NoArg,
16 | gradientHeight: OptArg[Double] = NoArg,
17 | gradientWidth: OptArg[Double] = NoArg,
18 | labelAlign: OptArg[String] = NoArg,
19 | labelBaseline: OptArg[String] = NoArg,
20 | labelColor: OptArg[String] = NoArg,
21 | labelFont: OptArg[String] = NoArg,
22 | labelFontSize: OptArg[Double] = NoArg,
23 | labelOffset: OptArg[Double] = NoArg,
24 | shortTimeLabels: OptArg[Boolean] = NoArg,
25 | symbolColor: OptArg[String] = NoArg,
26 | symbolShape: OptArg[String] = NoArg,
27 | symbolSize: OptArg[Double] = NoArg,
28 | symbolStrokeWidth: OptArg[Double] = NoArg,
29 | titleColor: OptArg[String] = NoArg,
30 | titleFont: OptArg[String] = NoArg,
31 | titleFontSize: OptArg[Double] = NoArg,
32 | titleFontWeight: OptArg[String] = NoArg,
33 | properties: OptArg[Spec.Legend.Properties] = NoArg) = {
34 |
35 | Spec.Legend(
36 | format = format,
37 | title = title,
38 | values = values,
39 | orient = orient,
40 | offset = offset,
41 | padding = padding,
42 | margin= margin,
43 | gradientStrokeColor = gradientStrokeColor,
44 | gradientStrokeWidth = gradientStrokeWidth,
45 | gradientHeight = gradientHeight,
46 | gradientWidth = gradientWidth,
47 | labelAlign = labelAlign,
48 | labelBaseline = labelBaseline,
49 | labelColor = labelColor,
50 | labelFont = labelFont,
51 | labelFontSize = labelFontSize,
52 | labelOffset = labelOffset,
53 | shortTimeLabels = shortTimeLabels,
54 | symbolColor = symbolColor,
55 | symbolShape = symbolShape,
56 | symbolSize = symbolSize,
57 | symbolStrokeWidth= symbolStrokeWidth,
58 | titleColor = titleColor,
59 | titleFont = titleFont,
60 | titleFontSize = titleFontSize,
61 | titleFontWeight = titleFontWeight,
62 | properties = properties
63 | )
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/DSL/Opt.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | sealed trait OptArg[+T] { def get: T }
4 | case object NoArg extends OptArg[Nothing] { def get = throw new NoSuchElementException("No value in NoOpt") }
5 | case class SomeArg[T](v: T) extends OptArg[T] { def get = v }
6 |
7 | object OptArg {
8 |
9 | def apply[T](v: T) = if (v == null) NoArg else SomeArg(v)
10 |
11 | implicit def optToOption[T](o: OptArg[T]) = o match {
12 | case NoArg => None
13 | case SomeArg(v) => Option(v)
14 | }
15 | implicit def optionToOpt[T](o: Option[T]) = o match {
16 | case None => NoArg
17 | case Some(v) => SomeArg(v)
18 | }
19 | implicit def anyToOpt[T](v: T) = apply[T](v)
20 |
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/DSL/ScaleDSL.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import vegas.spec.Spec
4 | import vegas.spec.Spec.{BandSize, Scale, ScaleType}
5 |
6 | object ScaleDSL {
7 |
8 | def apply(scaleType: OptArg[ScaleType] = NoArg,
9 | domainValues: OptArg[List[Double]] = NoArg,
10 | domainNominals: OptArg[List[String]] = NoArg,
11 | rangeValues: OptArg[List[Double]] = NoArg,
12 | rangeNominals: OptArg[List[String]] = NoArg,
13 | rangePreset: OptArg[String] = NoArg,
14 | round: OptArg[Boolean] = NoArg,
15 | bandSize: OptArg[Double] = NoArg,
16 | bandSizePreset: OptArg[BandSize] = NoArg,
17 | padding: OptArg[Double] = NoArg,
18 | clamp: OptArg[Boolean] = NoArg,
19 | nice: OptArg[Spec.NiceTime] = NoArg,
20 | niceEnable: OptArg[Boolean] = NoArg,
21 | exponent: OptArg[Double] = NoArg,
22 | zero: OptArg[Boolean] = NoArg,
23 | useRawDomain: OptArg[Boolean] = NoArg) = {
24 |
25 | val domainU = (domainValues.map(Scale.DomainListDouble(_)) orElse domainNominals.map(Scale.DomainListString(_)))
26 |
27 | val rangeU = (rangeValues.map(Scale.RangeListDouble(_)) orElse rangeNominals.map(Scale.RangeListString(_))
28 | orElse rangePreset.map(Scale.RangeString(_)))
29 |
30 | val niceU = (nice.map(Scale.NiceNiceTime(_)) orElse niceEnable.map(Scale.NiceBoolean(_)))
31 |
32 | val bandSizeU = (bandSize.map(Scale.BandSizeDouble(_)) orElse bandSizePreset.map(Scale.BandSizeBandSize(_)))
33 |
34 | Spec.Scale(`type`=scaleType, domain=domainU, range=rangeU, round=round, bandSize=bandSizeU, padding=padding,
35 | clamp=clamp, nice=niceU, exponent=exponent, zero=zero, useRawDomain=useRawDomain)
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/DSL/SortDSL.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import vegas.spec.Spec
4 | import vegas.spec.Spec.{AggregateOp, SortOrder}
5 |
6 | object SortDSL {
7 |
8 | def apply(field: String,
9 | op: AggregateOp,
10 | order: OptArg[SortOrder] = NoArg) = {
11 | Spec.SortField(field, op, order)
12 | }
13 |
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/DSL/SpecDSL.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import monocle.macros.GenLens
4 | import vegas.spec.Spec._
5 | import io.circe.Json
6 | import io.circe.syntax._
7 | import vegas.render.{ShowRender, StaticHTMLRenderer, WindowRenderer}
8 |
9 | object Vegas {
10 |
11 | /**
12 | * Creates a Builder DSL for typical (non-layered) specs
13 | */
14 | def apply(description: OptArg[String] = NoArg, name: OptArg[String] = NoArg, width: OptArg[Double] = NoArg,
15 | height: OptArg[Double] = NoArg) = ExtendedUnitSpecBuilder(ExtendedUnitSpec(
16 | width=width,
17 | height=height,
18 | name=name,
19 | description=description,
20 | mark=MarkEnums.Circle
21 | ))
22 |
23 | /**
24 | * Creates a Builder DSL for LayeredSpecs
25 | */
26 | def layered(description: OptArg[String] = NoArg, name: OptArg[String] = NoArg, width: OptArg[Double] = NoArg,
27 | height: OptArg[Double] = NoArg) = LayerSpecBuilder(LayerSpec(
28 | width=width,
29 | height=height,
30 | name=name,
31 | description=description,
32 | layers = Nil
33 | ))
34 |
35 |
36 | }
37 |
38 | object Layer {
39 | def apply(description: OptArg[String] = NoArg, name: OptArg[String] = NoArg, width: OptArg[Double] = NoArg,
40 | height: OptArg[Double] = NoArg) = UnitSpecBuilder(UnitSpec(
41 | width=width,
42 | height=height,
43 | name=name,
44 | description=description,
45 | mark=MarkEnums.Circle
46 | ))
47 | }
48 |
49 | /**
50 | * Builds a typical (non-layered) spec.
51 | */
52 | case class ExtendedUnitSpecBuilder(spec: ExtendedUnitSpec) extends SpecBuilder with ExtendedUnitSpecDSL {
53 | import vegas.spec.Spec.Implicits._
54 |
55 | def toJson = vegas.spec.toJson(spec)
56 | def asCirceJson = spec.asJson
57 | }
58 |
59 | /**
60 | * Builds a LayeredSpec
61 | */
62 | case class LayerSpecBuilder(spec: LayerSpec) extends SpecBuilder with LayerSpecDSL {
63 | import vegas.spec.Spec.Implicits._
64 |
65 | def toJson = vegas.spec.toJson(spec)
66 | def asCirceJson = spec.asJson
67 | }
68 |
69 | /**
70 | * Each layer's sub-spec
71 | */
72 | case class UnitSpecBuilder(spec: UnitSpec) extends SpecBuilder with UnitSpecDSL {
73 | import vegas.spec.Spec.Implicits._
74 |
75 | def toJson = vegas.spec.toJson(spec)
76 | def asCirceJson = spec.asJson
77 | }
78 |
79 |
80 | trait SpecBuilder {
81 |
82 | /**
83 | * Returns a Json string representation of this vega-lite spec
84 | */
85 | def toJson: String
86 |
87 | /**
88 | * Returns a Circe Json object that represents the spec. Also see [[toJson]]
89 | */
90 | def asCirceJson: Json
91 |
92 | }
93 |
94 | object SpecBuilder {
95 |
96 | implicit class SpecBuilderRenderOps(val sb: SpecBuilder) extends AnyVal {
97 |
98 | // directly show by having ShowRender guess which display mechanism is appropriate
99 | def show(implicit showRender: ShowRender): Unit = showRender(sb)
100 |
101 | // give a window rendered
102 | def window: WindowRenderer = new WindowRenderer(sb.toJson)
103 |
104 | // give an HTML renderer
105 | def html: StaticHTMLRenderer = new StaticHTMLRenderer(sb.toJson)
106 | }
107 |
108 | }
109 |
110 | trait ExtendedUnitSpecDSL extends EncoderDSL[ExtendedUnitSpecBuilder] with DataDSL[ExtendedUnitSpecBuilder]
111 | with TransformDSL[ExtendedUnitSpecBuilder] with ConfigDSL[ExtendedUnitSpecBuilder] {
112 |
113 | self: ExtendedUnitSpecBuilder =>
114 |
115 | protected[this] val _spec = GenLens[ExtendedUnitSpecBuilder](_.spec)
116 | protected[this] def _encoding = _spec composeLens GenLens[ExtendedUnitSpec](_.encoding)
117 | protected[this] val _data = _spec composeLens GenLens[ExtendedUnitSpec](_.data)
118 | protected[this] val _transform = _spec composeLens GenLens[ExtendedUnitSpec](_.transform)
119 | protected[this] val _config = _spec composeLens GenLens[ExtendedUnitSpec](_.config)
120 |
121 | private val _mark = GenLens[ExtendedUnitSpec](_.mark)
122 |
123 | def mark(mark: Mark) = {
124 | (_spec composeLens _mark).set(mark)(this)
125 | }
126 |
127 | }
128 |
129 | trait LayerSpecDSL extends DataDSL[LayerSpecBuilder] with TransformDSL[LayerSpecBuilder] with ConfigDSL[LayerSpecBuilder] {
130 | self: LayerSpecBuilder =>
131 |
132 | protected[this] val _spec = GenLens[LayerSpecBuilder](_.spec)
133 | protected[this] val _layers = GenLens[LayerSpec](_.layers)
134 | protected[this] val _data = _spec composeLens GenLens[LayerSpec](_.data)
135 | protected[this] val _transform = _spec composeLens GenLens[LayerSpec](_.transform)
136 | protected[this] val _config = _spec composeLens GenLens[LayerSpec](_.config)
137 |
138 | def withLayers(layers: UnitSpecBuilder*) = {
139 | val layerSpecs = layers.map(_.spec).toList
140 | (_spec composeLens _layers).set(layerSpecs)(this)
141 | }
142 |
143 | }
144 |
145 | trait UnitSpecDSL extends UnitEncoderDSL[UnitSpecBuilder] with DataDSL[UnitSpecBuilder] with TransformDSL[UnitSpecBuilder]
146 | with ConfigDSL[UnitSpecBuilder] {
147 | self: UnitSpecBuilder =>
148 |
149 | protected[this] val _spec = GenLens[UnitSpecBuilder](_.spec)
150 | protected[this] def _encoding = _spec composeLens GenLens[UnitSpec](_.encoding)
151 | protected[this] val _data = _spec composeLens GenLens[UnitSpec](_.data)
152 | protected[this] val _transform = _spec composeLens GenLens[UnitSpec](_.transform)
153 | protected[this] val _config = _spec composeLens GenLens[UnitSpec](_.config)
154 |
155 | private val _mark = GenLens[UnitSpec](_.mark)
156 |
157 | def mark(mark: Mark) = {
158 | (_spec composeLens _mark).set(mark)(this)
159 | }
160 |
161 | }
162 |
163 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/DSL/TransformDSL.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import monocle.Lens
4 | import monocle.macros.GenLens
5 | import vegas.spec.Spec._
6 |
7 | trait TransformDSL[T] {
8 | self: T =>
9 |
10 | protected[this] def _transform: Lens[T, Option[Transform]]
11 |
12 | private val _calculate = GenLens[Transform](_.calculate)
13 | private val _filterInvalid = GenLens[Transform](_.filterInvalid)
14 | private val _filter = GenLens[Transform](_.filter)
15 |
16 | def addTransformCalculation(field: String, expr: String): T = {
17 | val formula = Formula(field, expr)
18 | (_transform composePrism _orElse(Transform()) composeLens _calculate
19 | composePrism _orElse(Nil)).modify((xs: List[Formula]) => xs :+ formula)(this)
20 | }
21 |
22 | def addTransform(field: String, expr: String) = addTransformCalculation(field, expr)
23 |
24 | def transformFilter(filter: String): T = {
25 | val filterU = Transform.FilterString(filter)
26 | (_transform composePrism _orElse(Transform()) composeLens _filter).set(Some(filterU))(this)
27 | }
28 |
29 | def filter(filter: String) = transformFilter(filter)
30 |
31 | def transformFilterInvalid(filterInvalid: Boolean = true) = {
32 | (_transform composePrism _orElse(Transform()) composeLens _filterInvalid).set(Some(filterInvalid))(this)
33 | }
34 |
35 | def filterInvalid(filterInvalid: Boolean) = transformFilterInvalid(filterInvalid)
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/DSL/package.scala:
--------------------------------------------------------------------------------
1 | package vegas
2 |
3 | import monocle.Prism
4 |
5 | /**
6 | * @author Aish Fenton.
7 | */
8 | package object DSL {
9 |
10 | // Util used for composing Lens with options (and returning a default)
11 | def _orElse[T](fn: => T) = Prism[Option[T], T]{ o:Option[T] => o.orElse(Some(fn)) }(Some.apply)
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/data/External.scala:
--------------------------------------------------------------------------------
1 | package vegas.data
2 |
3 | object External {
4 |
5 | val Population = "https://vega.github.io/vega-editor/app/data/population.json"
6 | val Cars = "https://vega.github.io/vega-editor/app/data/cars.json"
7 | val Unemployment = "https://vega.github.io/vega-editor/app/data/unemployment-across-industries.json"
8 | val Movies = "https://vega.github.io/vega-editor/app/data/movies.json"
9 | val Barley = "https://vega.github.io/vega-editor/app/data/barley.json"
10 | val Stocks = "https://vega.github.io/vega-editor/app/data/stocks.csv"
11 | val Github = "https://vega.github.io/vega-editor/app/data/github.csv"
12 | val Anscombe = "https://vega.github.io/vega-editor/app/data/anscombe.json"
13 | val SeattleWeather = "https://vega.github.io/vega-editor/app/data/seattle-weather.csv"
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/data/FieldExtractor.scala:
--------------------------------------------------------------------------------
1 | package vegas.data
2 |
3 | /**
4 | * Extracts fields from a case classes
5 | */
6 | object FieldExtractor {
7 |
8 | /**
9 | * Extracts fields from a case classes into a map of field -> value pairs.
10 | * @param cc A case-class
11 | * @return A Map[String, Any] where the keys are the field names and the values are field values.
12 | */
13 | def extractFields(cc: Product): Map[String, Any] = {
14 | import scala.reflect.runtime.universe._
15 |
16 | val mirror = runtimeMirror(cc.getClass.getClassLoader)
17 | val tipe = mirror.reflect(cc).symbol.asType
18 |
19 | val fields = tipe.typeSignature.members.collect {
20 | case m: MethodSymbol if m.isCaseAccessor => m
21 | }.toList
22 |
23 | fields.map { ms =>
24 | ms.name.toString -> mirror.reflect(cc).reflectMethod(ms)()
25 | }.toMap
26 | }
27 |
28 | }
29 |
30 |
31 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/data/SimpleTypeUtils.scala:
--------------------------------------------------------------------------------
1 | package vegas.data
2 |
3 | object SimpleTypeUtils {
4 |
5 | /**
6 | * Trys to convert x to a Double. If x is some type of number, then this Some(x: Double) will be returned
7 | * otherwise None is returned.
8 | */
9 | def toDouble(x: Any) = x match {
10 | case x: Byte => Some(x.toDouble)
11 | case x: Short => Some(x.toDouble)
12 | case x: Int => Some(x.toDouble)
13 | case x: Long => Some(x.toDouble)
14 | case x: Float => Some(x.toDouble)
15 | case x: Double => Some(x.toDouble)
16 | case _ => None
17 | }
18 |
19 | /**
20 | * Returns true if x is a number (byte, short, int, long, float, double). NB: Booleans are not defined as being
21 | * numbers here.
22 | */
23 | def isNumber(x: Any) = x match {
24 | case x: Byte => true
25 | case x: Short => true
26 | case x: Int => true
27 | case x: Long => true
28 | case x: Float => true
29 | case x: Double => true
30 | case _ => false
31 | }
32 |
33 | /**
34 | * Returns true if x is a "SimpleType", otherwise false. SimpleType is defined to be booleans, numbers, and
35 | * strings; basically the types that Json handles natively.
36 | */
37 | def isSimpleType(x: Any) = x match {
38 | case x if isNumber(x) => true
39 | case x: Boolean => true
40 | case x: String => true
41 | case _ => false
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/data/ValueTransformer.scala:
--------------------------------------------------------------------------------
1 | package vegas.data
2 |
3 | import java.text.SimpleDateFormat
4 |
5 | /**
6 | * Base trait for transforming Any values into primitive types that are accepted by vega-lite. Default implementation
7 | * does a pass through for primitives, converts dates to ISO8601, and uses toString for everything else.
8 | */
9 | trait ValueTransformer {
10 |
11 | def transform(values: Map[String, Any]): Map[String, Any] = values.map { case(k,v) => (k, transformValue(v)) }
12 |
13 | /**
14 | * Transforms Any values into one of the supported primitive types
15 | */
16 | def transformValue(value: Any): Any
17 |
18 | }
19 |
20 | object DefaultValueTransformer extends ValueTransformer {
21 | val df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
22 |
23 | def transformValue(v: Any): Any = v match {
24 | case null => null
25 | case st if SimpleTypeUtils.isSimpleType(st) => st
26 | case d: java.sql.Date => d.toString
27 | case d: java.util.Date => df.format(d)
28 | case Some(x: Any) => transformValue(x)
29 | case None => null
30 | case _ => v.toString
31 | }
32 |
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/package.scala:
--------------------------------------------------------------------------------
1 | import vegas.spec.Spec
2 |
3 | /**
4 | * Use package object to list public API
5 | */
6 | package object vegas {
7 |
8 | val Vegas = DSL.Vegas
9 | val Layer = DSL.Layer
10 |
11 | implicit val DefaultValueTransformer = vegas.data.DefaultValueTransformer
12 |
13 | val Axis = vegas.DSL.AxisDSL
14 | val Scale = vegas.DSL.ScaleDSL
15 | val Legend = vegas.DSL.LegendDSL
16 | val Bin = vegas.DSL.BinDSL
17 | val Sort = vegas.DSL.SortDSL
18 | val Field = vegas.DSL.FieldDSL
19 |
20 | // -------
21 | // Core Enums
22 | // -------
23 |
24 | val Quantitative = Spec.TypeEnums.Quantitative
25 | val Quant = Quantitative
26 | val Nominal = Spec.TypeEnums.Nominal
27 | val Nom = Nominal
28 | val Ordinal = Spec.TypeEnums.Ordinal
29 | val Ord = Ordinal
30 | val Temporal = Spec.TypeEnums.Temporal
31 | val Temp = Temporal
32 |
33 | val Bar = Spec.MarkEnums.Bar
34 | val Circle = Spec.MarkEnums.Circle
35 | val Square = Spec.MarkEnums.Square
36 | val Tick = Spec.MarkEnums.Tick
37 | val Line = Spec.MarkEnums.Line
38 | val Area = Spec.MarkEnums.Area
39 | val Point = Spec.MarkEnums.Point
40 | val Text = Spec.MarkEnums.Text
41 |
42 | val AggOps = Spec.AggregateOpEnums
43 | val DataFormat = Spec.DataFormatTypeEnums
44 | val ScaleType = Spec.ScaleTypeEnums
45 | val Orient = Spec.AxisOrientEnums
46 | val TimeUnit = Spec.TimeUnitEnums
47 | val Nice = Spec.NiceTimeEnums
48 |
49 | object SortOrder {
50 | val Ascending = Spec.SortOrderEnums.Ascending
51 | val Asc = Spec.SortOrderEnums.Ascending
52 | val Descending = Spec.SortOrderEnums.Descending
53 | val Desc = Spec.SortOrderEnums.Descending
54 | val None = Spec.SortOrderEnums.None
55 | }
56 |
57 | val Category10 = "category10"
58 | val Category20 = "category20"
59 | val Category20b = "category20b"
60 | val Category20c = "category20c"
61 |
62 | // ---
63 | // Config Enums
64 | // ---
65 |
66 | val StackOffset = Spec.StackOffsetEnums
67 | val MarkOrient = Spec.OrientEnums
68 | val Interpolate = Spec.InterpolateEnums
69 | val HorizontalAlign = Spec.HorizontalAlignEnums
70 | val VerticalAlign = Spec.VerticalAlignEnums
71 | val Shape = Spec.ShapeEnums
72 | val FontStyle = Spec.FontStyleEnums
73 | val FontWeight = Spec.FontWeightEnums
74 | val BandSize = Spec.BandSizeEnums
75 |
76 | val AreaOverlay = Spec.AreaOverlayEnums
77 |
78 | val AxisConfig = vegas.DSL.AxisConfigDSL
79 | val CellConfig = vegas.DSL.CellConfigDSL
80 | val MarkConfig = vegas.DSL.MarkConfigDSL
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/render/BaseHTMLRenderer.scala:
--------------------------------------------------------------------------------
1 | package vegas.render
2 |
3 | import scala.io.Source
4 |
5 | trait BaseHTMLRenderer {
6 |
7 | private val WebJars = Source.fromInputStream(getClass.getResourceAsStream("/webjars.csv"))
8 | .getLines
9 | .map { l => val row = l.split(","); (row(0), row(1)) }
10 | .toMap
11 |
12 | private def CDN(artifact: String, file: String) = s"https://cdn.jsdelivr.net/webjars/org.webjars.bower/$artifact/${WebJars(artifact)}/$file"
13 |
14 | def JSImports = List(
15 | CDN("d3", "d3.min.js"),
16 | CDN("vega", "vega.min.js"),
17 | CDN("vega-lite", "vega-lite.min.js"),
18 | "https://vega.github.io/vega-editor/vendor/vega-embed.js"
19 | )
20 |
21 | def defaultName = {
22 | "vegas-" + java.util.UUID.randomUUID.toString
23 | }
24 |
25 | def specJson: String
26 |
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/render/ShowRender.scala:
--------------------------------------------------------------------------------
1 | package vegas.render
2 |
3 | import vegas.macros.ShowRenderMacros
4 | import scala.language.experimental.macros
5 |
6 | import vegas.DSL.SpecBuilder
7 |
8 | trait ShowRender extends (SpecBuilder => Unit)
9 |
10 | object ShowRender {
11 | def using(f: SpecBuilder => Unit): ShowRender = new ShowRender {
12 | def apply(sb: SpecBuilder) = f(sb)
13 | }
14 |
15 | implicit def default: ShowRender = macro ShowRenderMacros.materializeDefault
16 | }
17 |
18 | case class ShowHTML(output: String => Unit) extends ShowRender {
19 | def apply(sb: SpecBuilder): Unit = output(StaticHTMLRenderer(sb.toJson).frameHTML())
20 | }
21 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/render/StaticHTMLRenderer.scala:
--------------------------------------------------------------------------------
1 | package vegas.render
2 |
3 | /**
4 | * @author Aish Fenton.
5 | */
6 | case class StaticHTMLRenderer(specJson: String) extends BaseHTMLRenderer {
7 |
8 | def importsHTML(additionalImports: String*) = (JSImports ++ additionalImports).map { s =>
9 | ""
10 | }.mkString("\n")
11 |
12 | def headerHTML(additionalImports: String*) =
13 | s"""
14 | |
15 | |
16 | | ${ importsHTML(additionalImports:_*) }
17 | |
18 | |
21 | |
22 | """.stripMargin
23 |
24 | def plotHTML(name: String = this.defaultName) =
25 | s"""
26 | |
27 | |
34 |
35 | """.stripMargin
36 |
37 | val footerHTML =
38 | """
39 | |
40 | |
41 | """.stripMargin
42 |
43 | def pageHTML(name: String = defaultName) = {
44 | headerHTML().trim + plotHTML(name) + footerHTML.trim
45 | }
46 |
47 | /**
48 | * Typically you'll want to use this method to render your chart. Returns a full page of HTML wrapped in an iFrame
49 | * for embedding within existing HTML pages (such as Jupyter).
50 | * XXX Also contains an ugly hack to resize iFrame height to fit chart, if anyone knows a better way open to suggestions
51 | * @param name The name of the chart to use as an HTML id. Defaults to a UUID.
52 | * @return HTML containing iFrame for embedding
53 | */
54 | def frameHTML(name: String = defaultName) = {
55 | val frameName = "frame-" + name
56 | s"""
57 | |
58 | |
68 | """.stripMargin
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/render/WindowRenderer.scala:
--------------------------------------------------------------------------------
1 | package vegas.render
2 |
3 | import java.util.concurrent.{Callable, FutureTask}
4 |
5 | import scalafx.application.Platform
6 | import scalafx.scene.Scene
7 | import scalafx.stage.Stage
8 | import scalafx.scene.web.WebView
9 | import javafx.embed.swing.JFXPanel
10 |
11 | import com.sun.javafx.webkit.WebConsoleListener
12 |
13 | import scala.collection.mutable
14 |
15 | class Window {
16 |
17 | Platform.implicitExit = false
18 |
19 | val jsErrors = mutable.Buffer[String]()
20 | val webView = new WebView {}
21 | private val webEngine = webView.engine
22 |
23 | private def html(specJson: String) = StaticHTMLRenderer(specJson).pageHTML()
24 |
25 | def close = stage.close
26 |
27 | def load(specJson: String) = {
28 | webEngine.loadContent(html(specJson))
29 | }
30 |
31 | // Log JS errors
32 | WebConsoleListener.setDefaultListener(new WebConsoleListener {
33 | def messageAdded(webView: javafx.scene.web.WebView, message: String, lineNumber: Int, sourceId: String) = {
34 | if (message.contains("Error")) jsErrors.append(message)
35 | println(jsErrors)
36 | }
37 | })
38 |
39 | val stage = new Stage {
40 | title.value = "Vegas"
41 | width = 300
42 | height = 300
43 | scene = new Scene {
44 | content = webView
45 | }
46 | }
47 |
48 | Platform.runLater {
49 | stage.showAndWait()
50 | }
51 |
52 | }
53 |
54 | case class WindowRenderer(specJson: String) {
55 | lazy val window = new Window()
56 |
57 | def onUIThread[T](op: => T): T = if (Platform.isFxApplicationThread) {
58 | op
59 | } else {
60 | val futureTask = new FutureTask(new Callable[T] {
61 | override def call: T = onUIThread(op)
62 | })
63 | Platform.runLater(futureTask)
64 | futureTask.get()
65 | }
66 |
67 | def errors: List[String] = onUIThread { window.jsErrors.toList }
68 |
69 | def close = onUIThread { window.close }
70 |
71 | def show = {
72 | val _ = WindowRenderer.init
73 | Platform.runLater { window.load(specJson) }
74 | }
75 |
76 | }
77 |
78 | object WindowRenderer {
79 | lazy val init = new JFXPanel()
80 | }
81 |
82 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/render/package.scala:
--------------------------------------------------------------------------------
1 | package vegas
2 |
3 | /**
4 | * @author Aish Fenton.
5 | */
6 | package object render {
7 |
8 | val HTMLRenderer = StaticHTMLRenderer
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/main/scala/vegas/spec/package.scala:
--------------------------------------------------------------------------------
1 | package vegas
2 |
3 | import io.circe._
4 | import io.circe.syntax._
5 |
6 | /**
7 | * @author Aish Fenton.
8 | */
9 | package object spec {
10 | val DropNullJsonPrinter = Printer.spaces2.copy(dropNullKeys = true)
11 |
12 | def toJson[T : Encoder](spec: T) = {
13 | spec.asJson.pretty(DropNullJsonPrinter)
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/area.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/unemployment-across-industries.json"},
3 | "mark": "area",
4 | "encoding": {
5 | "x": {
6 | "timeUnit": "yearmonth", "field": "date", "type": "temporal",
7 | "scale": {"nice": "month"},
8 | "axis": {"axisWidth": 0, "format": "%Y", "labelAngle": 0}
9 | },
10 | "y": {
11 | "aggregate": "sum", "field": "count","type": "quantitative"
12 | }
13 | },
14 | "config": {"cell": {"width": 300, "height": 200}}
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/area_vertical.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Area chart showing weight of cars over time (vertical).",
3 | "data": {"url": "data/cars.json"},
4 | "mark": "area",
5 | "encoding": {
6 | "x": {"aggregate": "sum", "field": "Weight_in_lbs", "type": "quantitative"},
7 | "y": {"timeUnit": "year", "field": "Year", "type": "temporal"}
8 | },
9 | "config": {"mark": {"interpolate": "monotone"}}
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A simple bar chart with embedded data.",
3 | "data": {
4 | "values": [
5 | {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43},
6 | {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53},
7 | {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52}
8 | ]
9 | },
10 | "mark": "bar",
11 | "encoding": {
12 | "x": {"field": "a", "type": "ordinal"},
13 | "y": {"field": "b", "type": "quantitative"}
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_1d.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": { "url": "data/population.json"},
3 | "transform": {
4 | "filter": "datum.year == 2000"
5 | },
6 | "mark": "bar",
7 | "encoding": {
8 | "x": {
9 | "aggregate": "sum", "field": "people", "type": "quantitative",
10 | "axis": {"title": "population"}}
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_1d_bandsize_config.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": { "url": "data/population.json"},
3 | "transform": {
4 | "filter": "datum.year == 2000"
5 | },
6 | "mark": "bar",
7 | "encoding": {
8 | "x": {
9 | "aggregate": "sum", "field": "people", "type": "quantitative",
10 | "axis": {"title": "population"}}
11 | },
12 | "config": {"scale": {"bandSize": 21}}
13 | }
14 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_aggregate.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A bar chart showing the US population distribution of age groups in 2000.",
3 | "data": { "url": "data/population.json"},
4 | "transform": {"filter": "datum.year == 2000"},
5 | "mark": "bar",
6 | "encoding": {
7 | "y": {
8 | "field": "age", "type": "ordinal",
9 | "scale": {"bandSize": 17}
10 | },
11 | "x": {
12 | "aggregate": "sum", "field": "people", "type": "quantitative",
13 | "axis": {"title": "population"}
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_aggregate_size.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A bar chart showing the US population distribution of age groups in 2000.",
3 | "data": { "url": "data/population.json"},
4 | "transform": {
5 | "filter": "datum.year == 2000"
6 | },
7 | "mark": "bar",
8 | "encoding": {
9 | "y": {
10 | "aggregate": "sum", "field": "people", "type": "quantitative",
11 | "axis": {"title": "population"}
12 | },
13 | "x": {
14 | "field": "age", "type": "ordinal",
15 | "scale": {"bandSize": 17}
16 | },
17 | "size": {"value": 10}
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_aggregate_vertical.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "bar",
4 | "encoding": {
5 | "x": {"field": "Cylinders", "type": "ordinal"},
6 | "y": {"aggregate": "mean", "field": "Acceleration", "type": "quantitative"}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_filter_calc.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A simple bar chart with embedded data that uses a filter and calculate.",
3 | "data": {
4 | "values": [
5 | {"a": "A","b": 28},
6 | {"a": "B","b": 55},
7 | {"a": "C","b": 43},
8 | {"a": "G","b": 19},
9 | {"a": "H","b": 87},
10 | {"a": "I","b": 52},
11 | {"a": "D","b": 91},
12 | {"a": "E","b": 81},
13 | {"a": "F","b": 53}
14 | ]
15 | },
16 | "transform": {
17 | "calculate": [{"field": "b2","expr": "2*datum.b"}],
18 | "filter": "datum.b2 > 60"
19 | },
20 | "mark": "bar",
21 | "encoding": {
22 | "y": {"field": "b2", "type": "quantitative"},
23 | "x": {"field": "a", "type": "ordinal"}
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_grouped.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": { "url": "data/population.json"},
3 | "transform": {
4 | "filter": "datum.year == 2000",
5 | "calculate": [{"field": "gender", "expr": "datum.sex == 2 ? \"Female\" : \"Male\""}]
6 | },
7 | "mark": "bar",
8 | "encoding": {
9 | "column": {
10 | "field": "age", "type": "ordinal",
11 | "scale": {"padding": 4},
12 | "axis": {"orient": "bottom", "axisWidth": 1, "offset": -8}
13 | },
14 | "y": {
15 | "aggregate": "sum", "field": "people", "type": "quantitative",
16 | "axis": {"title": "population", "grid": false}
17 | },
18 | "x": {
19 | "field": "gender", "type": "nominal",
20 | "scale": {"bandSize": 6},
21 | "axis": false
22 | },
23 | "color": {
24 | "field": "gender", "type": "nominal",
25 | "scale": {"range": ["#EA98D2", "#659CCA"]}
26 | }
27 | },
28 | "config": {"facet": {"cell": {"strokeWidth": 0}}}
29 | }
30 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_grouped_horizontal.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": { "url": "data/population.json"},
3 | "transform": {
4 | "filter": "datum.year == 2000",
5 | "calculate": [{"field": "gender", "expr": "datum.sex == 2 ? \"Female\" : \"Male\""}]
6 | },
7 | "mark": "bar",
8 | "encoding": {
9 | "row": {
10 | "field": "age", "type": "ordinal",
11 | "scale": {"padding": 4},
12 | "axis": {"orient": "left", "axisWidth": 1, "offset": -8}
13 | },
14 | "x": {
15 | "aggregate": "sum", "field": "people", "type": "quantitative",
16 | "axis": {"title": "population", "grid": false}
17 | },
18 | "y": {
19 | "field": "gender", "type": "nominal",
20 | "scale": {"bandSize": 6},
21 | "axis": false
22 | },
23 | "color": {
24 | "field": "gender", "type": "nominal",
25 | "scale": {"range": ["#EA98D2", "#659CCA"]}
26 | }
27 | },
28 | "config": {"facet": {"cell": {"strokeWidth": 0}}}
29 | }
30 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_layered_transparent.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A bar chart showing the US population distribution of age groups and gender in 2000.",
3 | "data": { "url": "data/population.json"},
4 | "transform": {
5 | "filter": "datum.year == 2000",
6 | "calculate": [{"field": "gender", "expr": "datum.sex == 2 ? \"Female\" : \"Male\""}]
7 | },
8 | "mark": "bar",
9 | "encoding": {
10 | "x": {
11 | "field": "age", "type": "ordinal",
12 | "scale": {"bandSize": 17}
13 | },
14 | "y": {
15 | "aggregate": "sum", "field": "people", "type": "quantitative",
16 | "axis": {"title": "population"}
17 | },
18 | "color": {
19 | "field": "gender", "type": "nominal",
20 | "scale": {"range": ["#e377c2","#1f77b4"]}
21 | }
22 | },
23 | "config": {
24 | "mark": {"opacity": 0.6, "stacked" : "none"}
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_size_bandsize_small.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A simple bar chart with embedded data.",
3 | "data": {
4 | "values": [
5 | {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43},
6 | {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53},
7 | {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52}
8 | ]
9 | },
10 | "mark": "bar",
11 | "encoding": {
12 | "x": {
13 | "field": "a", "type": "ordinal",
14 | "scale": {"bandSize": 11}
15 | },
16 | "y": {"field": "b", "type": "quantitative"}
17 | }
18 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_size_default.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "bar",
4 | "encoding": {
5 | "x": {
6 | "field": "Origin",
7 | "type": "nominal",
8 | "scale": {"bandSize": 25}
9 | },
10 | "y": {
11 | "aggregate": "count",
12 | "field": "*",
13 | "type": "quantitative"
14 | }
15 | },
16 | "config": {
17 | "cell": {"width": 200, "height": 200}
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_size_explicit.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "width": 120,
3 | "height": 120,
4 | "data": {"url": "data/cars.json"},
5 | "mark": "bar",
6 | "encoding": {
7 | "x": {
8 | "field": "Origin",
9 | "type": "nominal"
10 | },
11 | "y": {
12 | "aggregate": "count",
13 | "field": "*",
14 | "type": "quantitative"
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_size_explicit_bad.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "width": 120,
3 | "height": 120,
4 | "data": {"url": "data/cars.json"},
5 | "mark": "bar",
6 | "encoding": {
7 | "x": {
8 | "field": "Name",
9 | "type": "nominal",
10 | "scale": {"round": false}
11 | },
12 | "y": {
13 | "aggregate": "count",
14 | "field": "*",
15 | "type": "quantitative"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_size_fit.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "bar",
4 | "encoding": {
5 | "x": {
6 | "field": "Origin",
7 | "type": "nominal",
8 | "scale": {"bandSize": "fit"}
9 | },
10 | "y": {
11 | "aggregate": "count",
12 | "field": "*",
13 | "type": "quantitative"
14 | }
15 | },
16 | "config": {
17 | "cell": {"width": 200, "height": 200}
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bar_yearmonth.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Temperature in Seattle as a bar chart with yearmonth time unit.",
3 | "data": {"url": "data/seattle-temps.csv", "format": {"type": "csv"}},
4 | "mark": "bar",
5 | "encoding": {
6 | "x": {
7 | "timeUnit": "yearmonth", "field": "date", "type": "temporal",
8 | "axis": {"format": "%B of %Y"},
9 | "scale": {"type": "ordinal"}
10 | },
11 | "y": {"aggregate": "mean", "field": "temp", "type": "quantitative"}
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/box_plot.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A box plot showing mean, min, and max in the US population distribution of age groups in 2000.",
3 | "data": {"url": "data/population.json"},
4 | "layers": [
5 | {
6 | "mark": "rule",
7 | "encoding": {
8 | "x": {"field": "age","type": "ordinal"},
9 | "y": {
10 | "aggregate": "min",
11 | "field": "people",
12 | "type": "quantitative",
13 | "axis": {"title": "population"}
14 | },
15 | "y2": {
16 | "aggregate": "max",
17 | "field": "people",
18 | "type": "quantitative"
19 | }
20 | }
21 | },
22 | {
23 | "mark": "tick",
24 | "encoding": {
25 | "x": {"field": "age","type": "ordinal"},
26 | "y": {
27 | "aggregate": "min",
28 | "field": "people",
29 | "type": "quantitative"
30 | },
31 | "size": {"value": 5}
32 | }
33 | },
34 | {
35 | "mark": "tick",
36 | "encoding": {
37 | "x": {"field": "age","type": "ordinal"},
38 | "y": {
39 | "aggregate": "max",
40 | "field": "people",
41 | "type": "quantitative"
42 | },
43 | "size": {"value": 5}
44 | }
45 | },
46 | {
47 | "mark": "bar",
48 | "encoding": {
49 | "x": {"field": "age","type": "ordinal"},
50 | "y": {
51 | "aggregate": "q1",
52 | "field": "people",
53 | "type": "quantitative"
54 | },
55 | "y2": {
56 | "aggregate": "q3",
57 | "field": "people",
58 | "type": "quantitative"
59 | },
60 | "size": {"value": 5}
61 | }
62 | },
63 | {
64 | "mark": "tick",
65 | "encoding": {
66 | "x": {"field": "age","type": "ordinal"},
67 | "y": {
68 | "aggregate": "mean",
69 | "field": "people",
70 | "type": "quantitative"
71 | },
72 | "size": {"value": 5},
73 | "color": { "value" : "white" }
74 | }
75 | }
76 | ]
77 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/bubble_health_income.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A bubble plot showing the correlation between health and income for 187 countries in the world (modified from an example in Lisa Charlotte Rost's blog post 'One Chart, Twelve Charting Libraries' --http://lisacharlotterost.github.io/2016/05/17/one-chart-code/).",
3 | "data": {
4 | "url": "data/gapminder-health-income.csv",
5 | "format": {"type": "csv"}
6 | },
7 | "mark": "circle",
8 | "encoding": {
9 | "y": {
10 | "field": "health",
11 | "type": "quantitative",
12 | "scale": {"zero": false}
13 | },
14 | "x": {
15 | "field": "income",
16 | "type": "quantitative",
17 | "scale": {"type": "log"}
18 | },
19 | "size": {"field": "population","type": "quantitative"},
20 | "color": {"value": "#000"}
21 | },
22 | "config": {"cell": {"width": 500,"height": 300}}
23 | }
24 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/circle.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "circle",
4 | "encoding": {
5 | "x": {"field": "Horsepower", "type": "quantitative"},
6 | "y": {"field": "Miles_per_Gallon", "type": "quantitative"}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/errorbar_aggregate.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A error bar plot showing mean, min, and max in the US population distribution of age groups in 2000.",
3 | "data": {"url": "data/population.json"},
4 | "transform": {"filter": "datum.year == 2000"},
5 | "layers": [
6 | {
7 | "mark": "rule",
8 | "encoding": {
9 | "x": {"field": "age","type": "ordinal"},
10 | "y": {
11 | "aggregate": "min",
12 | "field": "people",
13 | "type": "quantitative",
14 | "axis": {"title": "population"}
15 | },
16 | "y2": {
17 | "aggregate": "max",
18 | "field": "people",
19 | "type": "quantitative"
20 | }
21 | }
22 | },
23 | {
24 | "mark": "tick",
25 | "encoding": {
26 | "x": {"field": "age","type": "ordinal"},
27 | "y": {
28 | "aggregate": "min",
29 | "field": "people",
30 | "type": "quantitative",
31 | "axis": {"title": "population"}
32 | },
33 | "size": {"value": 5}
34 | }
35 | },
36 | {
37 | "mark": "tick",
38 | "encoding": {
39 | "x": {"field": "age","type": "ordinal"},
40 | "y": {
41 | "aggregate": "max",
42 | "field": "people",
43 | "type": "quantitative",
44 | "axis": {"title": "population"}
45 | },
46 | "size": {"value": 5}
47 | }
48 | },
49 | {
50 | "mark": "point",
51 | "encoding": {
52 | "x": {"field": "age","type": "ordinal"},
53 | "y": {
54 | "aggregate": "mean",
55 | "field": "people",
56 | "type": "quantitative",
57 | "axis": {"title": "population"}
58 | },
59 | "size": {"value": 2}
60 | }
61 | }
62 | ]
63 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/errorbar_horizontal_aggregate.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A bar chart showing the US population distribution of age groups in 2000.",
3 | "data": {"url": "data/population.json"},
4 | "transform": {"filter": "datum.year == 2000"},
5 | "layers": [
6 | {
7 | "mark": "rule",
8 | "encoding": {
9 | "y": {"field": "age","type": "ordinal"},
10 | "x": {
11 | "aggregate": "min",
12 | "field": "people",
13 | "type": "quantitative",
14 | "axis": {"title": "population"}
15 | },
16 | "x2": {
17 | "aggregate": "max",
18 | "field": "people",
19 | "type": "quantitative"
20 | }
21 | }
22 | },
23 | {
24 | "mark": "tick",
25 | "encoding": {
26 | "y": {"field": "age","type": "ordinal"},
27 | "x": {
28 | "aggregate": "min",
29 | "field": "people",
30 | "type": "quantitative",
31 | "axis": {"title": "population"}
32 | },
33 | "size": {"value": 5}
34 | }
35 | },
36 | {
37 | "mark": "tick",
38 | "encoding": {
39 | "y": {"field": "age","type": "ordinal"},
40 | "x": {
41 | "aggregate": "max",
42 | "field": "people",
43 | "type": "quantitative",
44 | "axis": {"title": "population"}
45 | },
46 | "size": {"value": 5}
47 | }
48 | },
49 | {
50 | "mark": "point",
51 | "encoding": {
52 | "y": {"field": "age","type": "ordinal"},
53 | "x": {
54 | "aggregate": "mean",
55 | "field": "people",
56 | "type": "quantitative",
57 | "axis": {"title": "population"}
58 | },
59 | "size": {"value": 2}
60 | }
61 | }
62 | ]
63 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/field_spaces.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "values": [
4 | {"a b": 0.9228935641885049,"c d": 0.5982771352733822},
5 | {"a b": 0.6819537235661846,"c d": 0.4599852852378238},
6 | {
7 | "a b": 0.035533848836472814,
8 | "c d": 0.6863884491167203
9 | },
10 | {"a b": 0.3491133898504284,"c d": 0.7368153745410643},
11 | {"a b": 0.42475313594111874,"c d": 0.8129620247637963},
12 | {"a b": 0.5239172250991592,"c d": 0.5616266265239936},
13 | {"a b": 0.4668258532026234,"c d": 0.436625706645942},
14 | {"a b": 0.2692059264553106,"c d": 0.2746758424896977},
15 | {"a b": 0.6009825566170259,"c d": 0.2648775327299746},
16 | {"a b": 0.7457746080337384,"c d": 0.15435369511584507}
17 | ]
18 | },
19 | "encoding": {
20 | "x": {"field": "a b","type": "quantitative"},
21 | "y": {"field": "c d","type": "quantitative"}
22 | },
23 | "mark": "point"
24 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/github_punchcard.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "url": "data/github.csv",
4 | "format": { "type": "csv" }
5 | },
6 | "mark": "circle",
7 | "encoding": {
8 | "y": {
9 | "field": "time",
10 | "type": "temporal",
11 | "timeUnit": "day"
12 | },
13 | "x": {
14 | "field": "time",
15 | "type": "temporal",
16 | "timeUnit": "hours"
17 | },
18 | "size": {
19 | "field": "count",
20 | "type": "quantitative",
21 | "aggregate": "sum"
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/histogram.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/movies.json"},
3 | "mark": "bar",
4 | "encoding": {
5 | "x": {
6 | "bin": {"maxbins": 10},
7 | "field": "IMDB_Rating",
8 | "type": "quantitative"
9 | },
10 | "y": {
11 | "aggregate": "count",
12 | "field": "*",
13 | "type": "quantitative"
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/layer_bar_line.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "values": [
4 | {"a": "A","b": 28},
5 | {"a": "B","b": 55},
6 | {"a": "C","b": 43},
7 | {"a": "D","b": 91},
8 | {"a": "E","b": 81},
9 | {"a": "F","b": 53},
10 | {"a": "G","b": 19},
11 | {"a": "H","b": 87},
12 | {"a": "I","b": 52}
13 | ]
14 | },
15 | "layers": [
16 | {
17 | "mark": "bar",
18 | "encoding": {
19 | "x": {"field": "a","type": "ordinal"},
20 | "y": {"field": "b","type": "quantitative"}
21 | }
22 | },
23 | {
24 | "mark": "line",
25 | "encoding": {
26 | "x": {"field": "a","type": "ordinal"},
27 | "y": {"field": "b","type": "quantitative"},
28 | "color": {"value": "red"}
29 | }
30 | }
31 | ]
32 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/layer_bar_line_union.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "values": [
4 | {"a": "A","b": "B","c": 28},
5 | {"a": "B","b": "C","c": 55},
6 | {"a": "C","b": "D","c": 43},
7 | {"a": "D","b": "E","c": 91},
8 | {"a": "E","b": "F","c": 81},
9 | {"a": "F","b": "G","c": 53},
10 | {"a": "G","b": "H","c": 19},
11 | {"a": "H","b": "I","c": 87},
12 | {"a": "I","b": "J","c": 52}
13 | ]
14 | },
15 | "layers": [
16 | {
17 | "mark": "bar",
18 | "encoding": {
19 | "x": {"field": "a","type": "ordinal"},
20 | "y": {"field": "c","type": "quantitative"}
21 | }
22 | },
23 | {
24 | "mark": "line",
25 | "encoding": {
26 | "x": {"field": "b","type": "ordinal"},
27 | "y": {"field": "c","type": "quantitative"},
28 | "color": {"value": "red"}
29 | }
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/layer_histogram.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/flights-2k.json"},
3 | "layers": [
4 | {
5 | "mark": "bar",
6 | "encoding": {
7 | "x": {
8 | "field": "distance",
9 | "type": "quantitative",
10 | "bin": true
11 | },
12 | "y": {
13 | "aggregate": "count",
14 | "field": "*",
15 | "type": "quantitative"
16 | }
17 | }
18 | },
19 | {
20 | "transform": {"filter": "datum.delay < 5"},
21 | "mark": "bar",
22 | "encoding": {
23 | "x": {
24 | "field": "distance",
25 | "type": "quantitative",
26 | "bin": true
27 | },
28 | "y": {
29 | "aggregate": "count",
30 | "field": "*",
31 | "type": "quantitative"
32 | },
33 | "color": {"value": "goldenrod"}
34 | }
35 | }
36 | ]
37 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/layer_line_color_rule.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/stocks.csv","format": {"type": "csv"}},
3 | "layers": [
4 | {
5 | "mark": "line",
6 | "encoding": {
7 | "x": {"field": "date","type": "temporal"},
8 | "y": {"field": "price","type": "quantitative"},
9 | "color": {"field": "symbol","type": "nominal"}
10 | }
11 | },
12 | {
13 | "mark": "rule",
14 | "encoding": {
15 | "y": {
16 | "field": "price",
17 | "type": "quantitative",
18 | "aggregate": "mean"
19 | },
20 | "size": {"value": 2},
21 | "color": {"field": "symbol","type": "nominal"}
22 | },
23 | "config": {"mark": {"opacity": 0.5}}
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/line.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Google's stock price over time.",
3 | "data": {"url": "data/stocks.csv", "format": {"type": "csv"}},
4 | "transform": {"filter": "datum.symbol==='GOOG'"},
5 | "mark": "line",
6 | "encoding": {
7 | "x": {"field": "date", "type": "temporal"},
8 | "y": {"field": "price", "type": "quantitative"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/line_color.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Stock prices of 5 Tech Companies Over Time.",
3 | "data": {"url": "data/stocks.csv", "format": {"type": "csv"}},
4 | "mark": "line",
5 | "encoding": {
6 | "x": {"field": "date", "type": "temporal"},
7 | "y": {"field": "price", "type": "quantitative"},
8 | "color": {"field": "symbol", "type": "nominal"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/line_detail.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Stock prices of 5 Tech Companies Over Time.",
3 | "data": {"url": "data/stocks.csv", "format": {"type": "csv"}},
4 | "mark": "line",
5 | "encoding": {
6 | "x": {"field": "date", "type": "temporal"},
7 | "y": {"field": "price", "type": "quantitative"},
8 | "detail": {"field": "symbol", "type": "nominal"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/line_monotone.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/stocks.csv", "format": {"type": "csv"}},
3 | "transform": {"filter": "datum.symbol==='GOOG'"},
4 | "mark": "line",
5 | "encoding": {
6 | "x": {"field": "date", "type": "temporal"},
7 | "y": {"field": "price", "type": "quantitative"}
8 | },
9 | "config":{
10 | "mark": {"interpolate": "monotone"}
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/line_month.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/seattle-temps.csv","format": {"type": "csv"}},
3 | "mark": "line",
4 | "encoding": {
5 | "x": {
6 | "timeUnit": "month", "field": "date", "type": "temporal",
7 | "axis": {"shortTimeLabels": true}
8 | },
9 | "y": {"aggregate": "mean", "field": "temp", "type": "quantitative"}
10 | },
11 | "config": {"mark": {"interpolate": "monotone"}}
12 | }
13 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/line_quarter.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Stock price mean per quarter broken down by years.",
3 | "data": {"url": "data/stocks.csv"},
4 | "mark": "point",
5 | "encoding": {
6 | "x": {"field": "date", "type": "temporal", "timeUnit": "quarter"},
7 | "y": {"field": "price", "type": "quantitative", "aggregate": "mean"},
8 | "color": {"field": "symbol", "type": "nominal"},
9 | "column": {"field": "date", "type": "temporal", "timeUnit": "year"}
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/line_quarter_legend.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Stock price average broken down by quarter.",
3 | "data": {"url": "data/stocks.csv"},
4 | "mark": "line",
5 | "encoding": {
6 | "x": {"field": "date", "type": "temporal", "timeUnit": "year"},
7 | "y": {"field": "price", "type": "quantitative", "aggregate": "mean"},
8 | "color": {"field": "date", "type": "temporal", "timeUnit": "quarter"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/line_slope.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/barley.json"},
3 | "description": "Slope graph showing the change in yield for different barley sites. It shows the error in the year labels for the Morris site.",
4 | "mark": "line",
5 | "encoding": {
6 | "x": {
7 | "field": "year",
8 | "type": "ordinal",
9 | "scale": {"bandSize": 50, "padding": 0.5}
10 | },
11 | "y": {
12 | "aggregate": "median",
13 | "field": "yield",
14 | "type": "quantitative"
15 | },
16 | "color": {"field": "site", "type": "nominal"}
17 | }
18 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/line_step.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Google's stock price over time.",
3 | "data": {"url": "data/stocks.csv", "format": {"type": "csv"}},
4 | "transform": {"filter": "datum.symbol==='GOOG'"},
5 | "mark": "line",
6 | "encoding": {
7 | "x": {"field": "date", "type": "temporal"},
8 | "y": {"field": "price", "type": "quantitative"}
9 | },
10 | "config": {"mark": {"interpolate": "step-after"}}
11 | }
12 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/minimal.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "values": [{}]
4 | },
5 | "mark": "point"
6 | }
7 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/overlay_area_full.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Google's stock price over time.",
3 | "data": {"url": "data/stocks.csv","format": {"type": "csv"}},
4 | "transform": {"filter": "datum.symbol==='GOOG'"},
5 | "layers": [
6 | {
7 | "mark": "area",
8 | "encoding": {
9 | "x": {"field": "date","type": "temporal"},
10 | "y": {"field": "price","type": "quantitative"}
11 | }
12 | },
13 | {
14 | "mark": "line",
15 | "encoding": {
16 | "x": {"field": "date","type": "temporal"},
17 | "y": {"field": "price","type": "quantitative"}
18 | }
19 | },
20 | {
21 | "mark": "point",
22 | "encoding": {
23 | "x": {"field": "date","type": "temporal"},
24 | "y": {"field": "price","type": "quantitative"}
25 | },
26 | "config": {"mark": {"filled": true}}
27 | }
28 | ]
29 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/overlay_area_short.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Google's stock price over time.",
3 | "data": {"url": "data/stocks.csv", "format": {"type": "csv"}},
4 | "transform": {"filter": "datum.symbol==='GOOG'"},
5 | "mark": "area",
6 | "encoding": {
7 | "x": {"field": "date", "type": "temporal"},
8 | "y": {"field": "price", "type": "quantitative"}
9 | },
10 | "config": {"overlay": {"area": "linepoint"}}
11 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/overlay_line_full.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/stocks.csv","format": {"type": "csv"}},
3 | "transform": {"filter": "datum.symbol==='GOOG'"},
4 | "layers": [
5 | {
6 | "description": "Google's stock price over time.",
7 | "mark": "line",
8 | "encoding": {
9 | "x": {"field": "date","type": "temporal"},
10 | "y": {"field": "price","type": "quantitative"},
11 | "color": {"field": "symbol", "type": "nominal"}
12 | }
13 | },
14 | {
15 | "description": "Google's stock price over time.",
16 | "mark": "point",
17 | "encoding": {
18 | "x": {"field": "date","type": "temporal"},
19 | "y": {"field": "price","type": "quantitative"},
20 | "color": {"field": "symbol", "type": "nominal"}
21 | },
22 | "config": {"mark": {"filled": true}}
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/overlay_line_short.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Google's stock price over time.",
3 | "data": {"url": "data/stocks.csv", "format": {"type": "csv"}},
4 | "transform": {"filter": "datum.symbol==='GOOG'"},
5 | "mark": "line",
6 | "encoding": {
7 | "x": {"field": "date", "type": "temporal"},
8 | "y": {"field": "price", "type": "quantitative"},
9 | "color": {"field": "symbol", "type": "nominal"}
10 | },
11 | "config": {"overlay": {"line": true}}
12 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/point_1d.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "point",
4 | "encoding": {
5 | "x": {"field": "Horsepower", "type": "quantitative"}
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/point_color.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"values": [{"a": 2}]},
3 | "mark": "point",
4 | "encoding": {},
5 | "config": {"mark": {"color": "purple"}}
6 | }
7 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/point_dot_timeunit_color.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/seattle-temps.csv","format": {"type": "csv"}},
3 | "mark": "point",
4 | "encoding": {
5 | "color": {
6 | "timeUnit": "yearmonth", "field": "date", "type": "temporal"
7 | },
8 | "x": {"aggregate": "mean", "field": "temp", "type": "quantitative"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/point_filled.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "point",
4 | "encoding": {
5 | "x": {"field": "Horsepower","type": "quantitative"},
6 | "y": {"field": "Miles_per_Gallon","type": "quantitative"}
7 | },
8 | "config": {
9 | "mark": {"filled": true}
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/point_ordinal_color.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "values": [
4 | {"a": "B","x": 2,"y": 2},
5 | {"a": "A","x": 1,"y": 1},
6 | {"a": "A","x": 4,"y": 4},
7 | {"a": "B","x": 5,"y": 5},
8 | {"a": "C","x": 3,"y": 3},
9 | {"a": "C","x": 6,"y": 6}
10 | ]
11 | },
12 | "mark": "point",
13 | "encoding": {
14 | "x": {"field": "x","type": "quantitative"},
15 | "y": {"field": "y","type": "quantitative"},
16 | "color": {"field": "a", "type": "ordinal"}
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A scatterplot showing horsepower and miles per gallons for various cars.",
3 | "data": {"url": "data/cars.json"},
4 | "mark": "point",
5 | "encoding": {
6 | "x": {"field": "Horsepower","type": "quantitative"},
7 | "y": {"field": "Miles_per_Gallon","type": "quantitative"}
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_aggregate_detail.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A scatterplot showing average horsepower and displacement for cars from different origins.",
3 | "data": {"url": "data/cars.json"},
4 | "mark": "point",
5 | "encoding": {
6 | "x": {"aggregate": "mean", "field": "Horsepower","type": "quantitative"},
7 | "y": {"aggregate": "mean", "field": "Displacement","type": "quantitative"},
8 | "detail": {"field": "Origin","type": "nominal"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_binned.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/movies.json"},
3 | "mark": "point",
4 | "encoding": {
5 | "x": {
6 | "bin": {"maxbins": 10},
7 | "field": "IMDB_Rating",
8 | "type": "quantitative"
9 | },
10 | "y": {
11 | "bin": {"maxbins": 10},
12 | "field": "Rotten_Tomatoes_Rating",
13 | "type": "quantitative"
14 | },
15 | "size": {
16 | "aggregate": "count",
17 | "field": "*", "type":
18 | "quantitative"
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_binned_color.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A scatterplot showing horsepower and miles per gallons with binned acceleration on color.",
3 | "data": {"url": "data/cars.json"},
4 | "mark": "point",
5 | "encoding": {
6 | "x": {"field": "Horsepower","type": "quantitative"},
7 | "y": {"field": "Miles_per_Gallon","type": "quantitative"},
8 | "color": {"bin": {"maxbins": 5}, "field": "Acceleration","type": "quantitative"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_binned_size.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A scatterplot showing horsepower and miles per gallons with binned acceleration on size.",
3 | "data": {"url": "data/cars.json"},
4 | "mark": "point",
5 | "encoding": {
6 | "x": {"field": "Horsepower","type": "quantitative"},
7 | "y": {"field": "Miles_per_Gallon","type": "quantitative"},
8 | "size": {"bin": true, "field": "Acceleration","type": "quantitative"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_bubble.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A bubbleplot showing horsepower on x, miles per gallons on y, and binned acceleration on size.",
3 | "data": {"url": "data/cars.json"},
4 | "mark": "point",
5 | "encoding": {
6 | "x": {"field": "Horsepower", "type": "quantitative"},
7 | "y": {"field": "Miles_per_Gallon", "type": "quantitative"},
8 | "size": {"field": "Acceleration", "type": "quantitative"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_color.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "point",
4 | "encoding": {
5 | "x": {"field": "Horsepower", "type": "quantitative"},
6 | "y": {"field": "Miles_per_Gallon", "type": "quantitative"},
7 | "color": {"field": "Origin", "type": "nominal"}
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_color_custom.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "point",
4 | "encoding": {
5 | "x": {"field": "Horsepower", "type": "quantitative"},
6 | "y": {"field": "Miles_per_Gallon", "type": "quantitative"},
7 | "color": {
8 | "field": "Origin", "type": "nominal",
9 | "scale": {"range": ["purple", "#ff0000", "teal"]}
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_color_order.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "point",
4 | "encoding": {
5 | "x": {"field": "Horsepower", "type": "quantitative"},
6 | "y": {"field": "Miles_per_Gallon", "type": "quantitative"},
7 | "color": {"field": "Origin", "type": "nominal"},
8 | "order": {"field": "Origin", "type": "ordinal", "sort": "descending"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_color_ordinal.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "point",
4 | "encoding": {
5 | "x": {"field": "Horsepower", "type": "quantitative"},
6 | "y": {"field": "Miles_per_Gallon", "type": "quantitative"},
7 | "color": {"field": "Cylinders", "type": "ordinal"}
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_color_ordinal_custom.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "point",
4 | "encoding": {
5 | "x": {"field": "Horsepower", "type": "quantitative"},
6 | "y": {"field": "Miles_per_Gallon", "type": "quantitative"},
7 | "color": {
8 | "field": "Cylinders", "type": "ordinal",
9 | "scale": {"range": ["pink", "red"]}
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_color_quantitative.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "point",
4 | "encoding": {
5 | "x": {"field": "Horsepower", "type": "quantitative"},
6 | "y": {"field": "Miles_per_Gallon", "type": "quantitative"},
7 | "color": {"field": "Displacement", "type": "quantitative"}
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_color_shape_constant.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "point",
4 | "encoding": {
5 | "x": {"field": "Horsepower","type": "quantitative"},
6 | "y": {"field": "Miles_per_Gallon","type": "quantitative"},
7 | "color": {"value": "#ff9900"},
8 | "shape": {"value": "square"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_colored_with_shape.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A scatterplot showing horsepower and miles per gallons.",
3 | "data": {"url": "data/cars.json"},
4 | "mark": "point",
5 | "encoding": {
6 | "x": {"field": "Horsepower", "type": "quantitative"},
7 | "y": {"field": "Miles_per_Gallon", "type": "quantitative"},
8 | "color": {"field": "Origin", "type": "nominal"},
9 | "shape": {"field": "Origin", "type": "nominal"}
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_connected.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/driving.json"},
3 | "mark": "line",
4 | "encoding": {
5 | "x": {"field": "miles","type": "quantitative", "scale": {"zero": false}},
6 | "y": {"field": "gas","type": "quantitative", "scale": {"zero": false}},
7 | "path": {"field": "year","type": "temporal"}
8 | },
9 | "config": {"overlay": {"line": true}}
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_log.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "mark": "point",
3 | "data": {
4 | "values": [
5 | {"x": 0, "y": 1}, {"x": 1, "y": 10},
6 | {"x": 2, "y": 100}, {"x": 3, "y": 1000},
7 | {"x": 4, "y": 10000}, {"x": 5, "y": 100000},
8 | {"x": 6, "y": 1000000}, {"x": 7, "y": 10000000}
9 | ]
10 | },
11 | "encoding": {
12 | "x": {"field": "x", "type": "quantitative"},
13 | "y": {
14 | "field": "y", "type": "quantitative",
15 | "scale": {"type": "log"}
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_opacity.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "circle",
4 | "encoding": {
5 | "x": {"field": "Horsepower", "type": "quantitative"},
6 | "y": {"field": "Miles_per_Gallon", "type": "quantitative"},
7 | "opacity": {"field": "Miles_per_Gallon", "type": "quantitative"}
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/scatter_shape_custom.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A scatterplot with custom star shapes.",
3 | "data": {"url": "data/cars.json"},
4 | "mark": "point",
5 | "encoding": {
6 | "x": {"field": "Horsepower","type": "quantitative"},
7 | "y": {"field": "Miles_per_Gallon","type": "quantitative"},
8 | "color": {"field": "Cylinders", "type": "nominal"},
9 | "size": {"field": "Weight_in_lbs", "type": "quantitative"}
10 | },
11 | "config": {
12 | "mark": {
13 | "shape": "M0,0.2L0.2351,0.3236 0.1902,0.0618 0.3804,-0.1236 0.1175,-0.1618 0,-0.4 -0.1175,-0.1618 -0.3804,-0.1236 -0.1902,0.0618 -0.2351,0.3236 0,0.2Z"
14 | }
15 | }
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/square.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "square",
4 | "encoding": {
5 | "x": {"field": "Horsepower", "type": "quantitative"},
6 | "y": {"field": "Miles_per_Gallon", "type": "quantitative"}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_area.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Area chart showing weight of cars over time.",
3 | "data": {"url": "data/unemployment-across-industries.json"},
4 | "mark": "area",
5 | "encoding": {
6 | "x": {
7 | "timeUnit": "yearmonth", "field": "date", "type": "temporal",
8 | "scale": {"nice": "month"},
9 | "axis": {"axisWidth": 0, "format": "%Y", "labelAngle": 0}
10 | },
11 | "y": {
12 | "aggregate": "sum", "field": "count","type": "quantitative"
13 | },
14 | "color": {"field":"series", "type":"nominal", "scale":{"range": "category20b"}}
15 | },
16 | "config": {"cell": {"width": 300, "height": 200}}
17 | }
18 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_area_binned.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "url": "data/cars.json"
4 | },
5 | "mark": "area",
6 | "encoding": {
7 | "color": {
8 | "field": "Cylinders",
9 | "type": "nominal"
10 | },
11 | "y": {
12 | "aggregate": "mean",
13 | "bin": false,
14 | "field": "Horsepower",
15 | "type": "quantitative"
16 | },
17 | "x": {
18 | "bin": {},
19 | "field": "Acceleration",
20 | "type": "quantitative"
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_area_normalize.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/unemployment-across-industries.json"},
3 | "mark": "area",
4 | "encoding": {
5 | "x": {
6 | "timeUnit": "yearmonth", "field": "date", "type": "temporal",
7 | "scale": {"nice": "month"},
8 | "axis": {"axisWidth": 0, "format": "%Y", "labelAngle": 0}
9 | },
10 | "y": {
11 | "aggregate": "sum", "field": "count","type": "quantitative",
12 | "axis": false
13 | },
14 | "color": {"field":"series", "type":"nominal", "scale":{"range": "category20b"}}
15 | },
16 | "config": {"cell": {"width": 300, "height": 200}, "mark": {"stacked": "normalize"}}
17 | }
18 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_area_ordinal.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "area",
4 | "encoding": {
5 | "x": {"timeUnit": "year", "field": "Year", "type": "temporal"},
6 | "y": {"aggregate": "sum", "field": "Weight_in_lbs", "type": "quantitative"},
7 | "color": {"field": "Cylinders", "type": "ordinal"}
8 | },
9 | "config": {"mark": {"interpolate": "monotone"}}
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_area_stream.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/unemployment-across-industries.json"},
3 | "mark": "area",
4 | "encoding": {
5 | "x": {
6 | "timeUnit": "yearmonth", "field": "date", "type": "temporal",
7 | "scale": {"nice": "month"},
8 | "axis": {"axisWidth": 0, "format": "%Y", "labelAngle": 0, "tickSize": 0}
9 | },
10 | "y": {
11 | "aggregate": "sum", "field": "count","type": "quantitative",
12 | "axis": false
13 | },
14 | "color": {"field":"series", "type":"nominal", "scale":{"range": "category20b"}}
15 | },
16 | "config": {"cell": {"width": 300, "height": 200}, "mark": {"stacked": "center"}}
17 | }
18 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_bar_1d.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "bar",
4 | "encoding": {
5 | "x": {"aggregate": "sum", "field": "Acceleration","type": "quantitative"},
6 | "color": {"field": "Origin","type": "nominal"}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_bar_h.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/barley.json"},
3 | "mark": "bar",
4 | "encoding": {
5 | "x": {"aggregate": "sum", "field": "yield", "type": "quantitative"},
6 | "y": {"field": "variety", "type": "nominal"},
7 | "color": {"field": "site", "type": "nominal"}
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_bar_h_order.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/barley.json"},
3 | "mark": "bar",
4 | "encoding": {
5 | "x": {"aggregate": "sum", "field": "yield", "type": "quantitative"},
6 | "y": {"field": "variety", "type": "nominal"},
7 | "color": {"field": "site", "type": "nominal"},
8 | "order": {"aggregate": "sum", "field": "yield", "type": "quantitative"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_bar_normalize.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": { "url": "data/population.json"},
3 | "transform": {
4 | "filter": "datum.year == 2000",
5 | "calculate": [{"field": "gender", "expr": "datum.sex == 2 ? \"Female\" : \"Male\""}]
6 | },
7 | "mark": "bar",
8 | "encoding": {
9 | "y": {
10 | "aggregate": "sum", "field": "people", "type": "quantitative",
11 | "axis": {"title": "population"}
12 | },
13 | "x": {
14 | "field": "age", "type": "ordinal",
15 | "scale": {"bandSize": 17}
16 | },
17 | "color": {
18 | "field": "gender", "type": "nominal",
19 | "scale": {"range": ["#EA98D2", "#659CCA"]}
20 | }
21 | },
22 | "config": {"mark": {"stacked": "normalize"}}
23 | }
24 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_bar_population.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A bar chart showing the US population distribution of age groups and gender in 2000.",
3 | "data": { "url": "data/population.json"},
4 | "transform": {
5 | "filter": "datum.year == 2000",
6 | "calculate": [{"field": "gender", "expr": "datum.sex == 2 ? \"Female\" : \"Male\""}]
7 | },
8 | "mark": "bar",
9 | "encoding": {
10 | "y": {
11 | "aggregate": "sum", "field": "people", "type": "quantitative",
12 | "axis": {"title": "population"}
13 | },
14 | "x": {
15 | "field": "age", "type": "ordinal",
16 | "scale": {"bandSize": 17}
17 | },
18 | "color": {
19 | "field": "gender", "type": "nominal",
20 | "scale": {"range": ["#EA98D2", "#659CCA"]}
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_bar_size.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/seattle-weather.csv", "format": {"type": "csv"}},
3 | "mark": "bar",
4 | "config": {
5 | "mark": { "opacity": 0.5 }
6 | },
7 | "encoding": {
8 | "x": {
9 | "field": "date",
10 | "type": "temporal",
11 | "timeUnit": "month"
12 | },
13 | "y": {
14 | "field": "*",
15 | "type": "quantitative",
16 | "aggregate": "count"
17 | },
18 | "size": {
19 | "field": "weather",
20 | "type": "nominal"
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_bar_sum_opacity.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A bar chart showing the US population distribution of age groups and gender in 2000.",
3 | "data": {"url": "data/population.json"},
4 | "transform": {
5 | "filter": "datum.year == 2000",
6 | "calculate": [
7 | {
8 | "field": "gender",
9 | "expr": "datum.sex == 2 ? \"Female\" : \"Male\""
10 | }
11 | ]
12 | },
13 | "mark": "bar",
14 | "encoding": {
15 | "x": {
16 | "field": "age",
17 | "type": "ordinal",
18 | "scale": {"bandSize": 17}
19 | },
20 | "y": {
21 | "aggregate": "sum",
22 | "field": "people",
23 | "type": "quantitative",
24 | "axis": {"title": "population"}
25 | },
26 | "opacity": {"field": "people","type": "quantitative"}
27 | }
28 | }
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_bar_v.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/barley.json"},
3 | "mark": "bar",
4 | "encoding": {
5 | "x": {"field": "variety", "type": "nominal"},
6 | "y": {"aggregate": "sum", "field": "yield", "type": "quantitative"},
7 | "color": {"field": "site", "type": "nominal"}
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/stacked_bar_weather.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/seattle-weather.csv","format": {"type": "csv"}},
3 | "mark": "bar",
4 | "encoding": {
5 | "x": {
6 | "field": "date",
7 | "type": "temporal",
8 | "timeUnit": "month",
9 | "axis": {"title": "Month of the year"}
10 | },
11 | "y": {
12 | "field": "*",
13 | "type": "quantitative",
14 | "aggregate": "count"
15 | },
16 | "color": {
17 | "field": "weather",
18 | "type": "nominal",
19 | "scale": {
20 | "domain": ["sun","fog","drizzle","rain","snow"],
21 | "range": ["#e7ba52","#c7c7c7","#aec7e8","#1f77b4","#9467bd"]
22 | },
23 | "legend": {
24 | "title": "Weather type"
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/text_scatter_colored.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "transform": {
4 | "calculate": [{
5 | "field": "OriginInitial",
6 | "expr": "datum.Origin[0]"
7 | }]
8 | },
9 | "mark": "text",
10 | "encoding": {
11 | "x": {"field": "Horsepower", "type": "quantitative"},
12 | "y": {"field": "Miles_per_Gallon", "type": "quantitative"},
13 | "color": {"field": "Origin", "type": "nominal"},
14 | "text": {"field": "OriginInitial", "type": "nominal"}
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/text_table_heatmap.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "text",
4 | "encoding": {
5 | "row": {"field": "Origin", "type": "ordinal"},
6 | "column": {"field": "Cylinders", "type": "ordinal"},
7 | "color": {"aggregate": "mean", "field": "Horsepower", "type": "quantitative"},
8 | "text": {"aggregate": "count", "field": "*", "type": "quantitative"}
9 | },
10 | "config": {"mark": {"applyColorToBackground": true}}
11 | }
12 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/tick_dot.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "tick",
4 | "encoding": {
5 | "x": {"field": "Horsepower", "type": "quantitative"}
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/tick_dot_thickness.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "tick",
4 | "encoding": {
5 | "x": {"field": "Horsepower","type": "quantitative"}
6 | },
7 | "config": {
8 | "mark": {
9 | "tickThickness": 2,
10 | "tickSize": 10
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/tick_strip.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Shows the relationship between horsepower and the numbver of cylinders using tick marks.",
3 | "data": {"url": "data/cars.json"},
4 | "mark": "tick",
5 | "encoding": {
6 | "x": {"field": "Horsepower", "type": "quantitative"},
7 | "y": {"field": "Cylinders", "type": "ordinal"}
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/trellis_anscombe.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Anscombe's Quartet",
3 | "data": {"url": "data/anscombe.json"},
4 | "mark": "circle",
5 | "encoding": {
6 | "column": {"field": "Series", "type": "nominal"},
7 | "x": {
8 | "field": "X",
9 | "type": "quantitative",
10 | "scale": {"zero": false}
11 | },
12 | "y": {
13 | "field": "Y",
14 | "type": "quantitative",
15 | "scale": {"zero": false}
16 | }
17 | },
18 | "config": {"mark": {"opacity": 1}}
19 | }
20 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/trellis_bar.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A trellis bar chart showing the US population distribution of age groups and gender in 2000.",
3 | "data": { "url": "data/population.json"},
4 | "transform": {
5 | "filter": "datum.year == 2000",
6 | "calculate": [{"field": "gender", "expr": "datum.sex == 2 ? \"Female\" : \"Male\""}]
7 | },
8 | "mark": "bar",
9 | "encoding": {
10 | "row": {"field": "gender", "type": "nominal"},
11 | "y": {
12 | "aggregate": "sum", "field": "people", "type": "quantitative",
13 | "axis": {"title": "population"}
14 | },
15 | "x": {
16 | "field": "age", "type": "ordinal",
17 | "scale": {"bandSize": 17}
18 | },
19 | "color": {
20 | "field": "gender", "type": "nominal",
21 | "scale": {"range": ["#EA98D2","#659CCA"]}
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/trellis_bar_histogram.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "bar",
4 | "encoding": {
5 | "x": {
6 | "bin": {"maxbins": 15},
7 | "field": "Horsepower",
8 | "type": "quantitative"
9 | },
10 | "y": {
11 | "aggregate": "count",
12 | "field": "*",
13 | "type": "quantitative"
14 | },
15 | "row": {"field": "Origin","type": "nominal"}
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/trellis_barley.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "The Trellis display by Becker et al. helped establish small multiples as a “powerful mechanism for understanding interactions in studies of how a response depends on explanatory variables”. Here we reproduce a trellis of Barley yields from the 1930s, complete with main-effects ordering to facilitate comparison.",
3 | "data": {"url": "data/barley.json"},
4 | "mark": "point",
5 | "encoding": {
6 | "row": {"field": "site", "type": "ordinal"},
7 | "x": {"aggregate": "mean", "field": "yield", "type": "quantitative"},
8 | "y": {
9 | "field": "variety", "type": "ordinal",
10 | "sort": {"field": "yield","op": "mean"},
11 | "scale": {"bandSize": 12}
12 | },
13 | "color": {"field": "year", "type": "nominal"}
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/trellis_row_column.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/cars.json"},
3 | "mark": "point",
4 | "encoding": {
5 | "row": {"field": "Cylinders","type": "ordinal"},
6 | "column": {"field": "Origin","type": "ordinal"},
7 | "x": {"field": "Horsepower","type": "quantitative"},
8 | "y": {"field": "Miles_per_Gallon","type": "quantitative"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/trellis_scatter.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/movies.json"},
3 | "mark": "point",
4 | "encoding": {
5 | "column": {"field": "MPAA_Rating","type": "ordinal"},
6 | "x": {"field": "Worldwide_Gross","type": "quantitative"},
7 | "y": {"field": "US_DVD_Sales","type": "quantitative"}
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/trellis_scatter_binned_row.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "A trellis scatterplot showing Horsepower and Miles per gallons, faceted by binned values of Acceleration.",
3 | "data": {"url": "data/cars.json"},
4 | "mark": "point",
5 | "encoding": {
6 | "row": {"field": "Acceleration","type": "quantitative", "bin": true},
7 | "x": {"field": "Horsepower","type": "quantitative"},
8 | "y": {"field": "Miles_per_Gallon","type": "quantitative"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/example-specs/trellis_stacked_bar.vl.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {"url": "data/barley.json"},
3 | "mark": "bar",
4 | "encoding": {
5 | "column": {"field": "year","type": "ordinal"},
6 | "x": {"field": "yield","type": "quantitative","aggregate": "sum"},
7 | "y": {"field": "variety","type": "nominal"},
8 | "color": {"field": "site","type": "nominal"}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.logger.org.apache.spark=WARN
2 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/BaseSpec.scala:
--------------------------------------------------------------------------------
1 | package vegas
2 |
3 | import org.scalatest._
4 |
5 | /**
6 | * @author Aish Fenton.
7 | */
8 | abstract class BaseSpec extends FlatSpec with Matchers
9 |
10 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/DSL/AllDSLSpec.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import java.io.File
4 |
5 | import org.scalatest.{FlatSpec, Matchers}
6 | import vegas.JsonMatchers
7 | import vegas.fixtures.BasicPlots.plotsWithNames
8 |
9 | import scala.io.Source
10 |
11 | class AllDSLSpec extends FlatSpec with Matchers with JsonMatchers {
12 | import AllDSLSpec._
13 |
14 | behavior of "BasicPlots"
15 | for((name, plot) <- plotsWithNames) {
16 | it should s"produce the correct json as ${name}" in {
17 | plot.asCirceJson should beSameJsonAs(examples(name))
18 | }
19 | }
20 | }
21 |
22 | object AllDSLSpec {
23 | val examples = new File("core/src/test/resources/example-specs")
24 | .listFiles.toList
25 | .filter(_.getName.endsWith("json"))
26 | .map { file =>
27 | val json = Source.fromFile(file)
28 | .getLines.mkString
29 | // Make URLs absolute
30 | .replaceAll("data/.*.(json|csv|tsv)", "https://vega.github.io/vega-editor/app/$0")
31 | (file.getName.stripSuffix(".vl.json"), json)
32 | }
33 | .toMap
34 | }
35 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/DSL/DSLSpec.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import monocle.macros.GenLens
4 | import org.scalatest.{FlatSpec, Matchers}
5 |
6 | /**
7 | * @author Aish Fenton.
8 | */
9 | class DSLSpec extends FlatSpec with Matchers {
10 |
11 | "_orElse, when composing a Lens and a Prism" should "reach modify case classes with optional values immutably" in {
12 | case class Person(address: Option[Address])
13 | case class Address(street: Option[String])
14 |
15 | val _address = GenLens[Person](_.address)
16 | val _street = GenLens[Address](_.street)
17 |
18 | val bob = Person(None)
19 |
20 | val setStreet = (_address composePrism _orElse(Address(None)) composeLens _street composePrism _orElse("NoWhere"))
21 |
22 | setStreet.set("myStreet")(bob) should equal (Person(Some(Address(Some("myStreet")))))
23 |
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/DSL/DataDSLSpec.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import org.scalatest.{FlatSpec, Matchers}
4 | import vegas._
5 | import vegas.data.ValueTransformer
6 | import vegas.spec.Spec._
7 | import vegas.util.Time
8 |
9 | /**
10 | * @author Aish Fenton.
11 | */
12 | class DataDSLSpec extends FlatSpec with Matchers {
13 |
14 | case class Ex(a: Int, b: String)
15 | val testValueTransformer = new ValueTransformer { def transformValue(v: Any) = "ok" }
16 |
17 |
18 | "DataDSL Trait" should "wire in data from Seq of Maps" in {
19 | val data = List(Map("population" -> "318", "country" -> "UK"), Map("population" -> "64", "country" -> "UK"))
20 |
21 | val specBuilder = Vegas()
22 | .withData(data)
23 |
24 | specBuilder.spec.data should === (Some(Data(
25 | values = Some(data.map(Data.Values(_)))
26 | )))
27 | }
28 |
29 | it should "wire in a Seq of data, treating each index as a column name" in {
30 | val data = Seq(Seq("a", 1), Seq("b" ,2))
31 |
32 | val specBuilder = Vegas()
33 | .withSeqValues(data)
34 |
35 | val expectedData = List(Map("0" -> "a", "1" -> 1), Map("0" -> "b", "1" -> 2))
36 | specBuilder.spec.data should === (Some(Data(
37 | values=Some( expectedData.map(Data.Values(_)) )
38 | )))
39 | }
40 |
41 | it should "wire in a Seq of case classes, treating each field name as a column name" in {
42 | val data = Seq( Ex(1, "UK"), Ex(2, "USA") )
43 |
44 | val specBuilder = Vegas()
45 | .withCaseClasses(data)
46 |
47 | val expectedData = List(Map("a" -> 1, "b" -> "UK"), Map("a" -> 2, "b" -> "USA"))
48 | specBuilder.spec.data should === (Some(Data(
49 | values=Some( expectedData.map(Data.Values(_)))
50 | )))
51 | }
52 |
53 | it should "wire in a simple Seq of values, treating indices as an 'x' and values as a 'y' column" in {
54 | val data = Seq(1,2)
55 |
56 | val specBuilder = Vegas()
57 | .withValues(data)
58 |
59 | val expectedData = List(Map("x" -> 0, "y" -> 1), Map("x" -> 1, "y" -> 2))
60 | specBuilder.spec.data should === (Some(Data(
61 | values=Some( expectedData.map(Data.Values(_)) )
62 | )))
63 | }
64 |
65 | it should "wire in a seq of (x,y) tuples, using 'x' and 'y' as column names" in {
66 | val data = Seq(("uk", 10), ("usa", 20))
67 |
68 | val specBuilder = Vegas()
69 | .withXY(data)
70 |
71 | val expectedData = List(Map("x" -> "uk", "y" -> 10), Map("x" -> "usa", "y" -> 20))
72 | specBuilder.spec.data should === (Some(Data(
73 | values=Some( expectedData.map(Data.Values(_)) )
74 | )))
75 | }
76 |
77 | it should "transform values to 'SimpleTypes' that can be handled by vega-lite" in {
78 | val data = Seq(Time.mkDate(2015, 12, 25), Ex(4, "a"), 3.14)
79 |
80 | val specBuilder = Vegas()
81 | .withValues(data)
82 |
83 | val expectedData = List(
84 | Map("x" -> 0, "y" -> "2015-12-25T00:00:00"),
85 | Map("x" -> 1, "y" -> "Ex(4,a)"),
86 | Map("x" -> 2, "y" -> 3.14)
87 | )
88 |
89 | specBuilder.spec.data should === (Some(Data(
90 | values=Some( expectedData.map(Data.Values(_)) )
91 | )))
92 | }
93 |
94 | it should "let you override how values are transformed" in {
95 | val data = Seq(Ex(4, "a"), Ex(5, "b"))
96 |
97 | val specBuilder = Vegas()
98 | .withValues(data)(testValueTransformer)
99 |
100 | val expectedData = List(
101 | Map("x" -> "ok", "y" -> "ok"),
102 | Map("x" -> "ok", "y" -> "ok")
103 | )
104 |
105 | specBuilder.spec.data should === (Some(Data(
106 | values=Some( expectedData.map(Data.Values(_)) )
107 | )))
108 | }
109 |
110 | }
111 |
112 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/DSL/OptSpec.scala:
--------------------------------------------------------------------------------
1 | package vegas.DSL
2 |
3 | import org.scalatest.{FlatSpec, Matchers}
4 |
5 | /**
6 | * @author Aish Fenton.
7 | */
8 | class OptSpec extends FlatSpec with Matchers {
9 |
10 | def test(a: OptArg[Int] = NoArg, b: OptArg[String] = NoArg, c: OptArg[Any] = NoArg) = (a,b,c)
11 |
12 | "OptArg" should "throw an exception if NoArg.get is called, and return value if SomeArg.get is called" in {
13 | val (x1,x2,x3) = test(b="test")
14 | an [NoSuchElementException] should be thrownBy x1.get
15 | x2.get should equal ("test")
16 | }
17 |
18 | it should "return NoArg if created with a null, and SomeArg if created with anything else" in {
19 | OptArg(1) should equal (SomeArg[Int](1))
20 | OptArg(null) should equal (NoArg)
21 | }
22 |
23 | it should "implicitly convert arguments to SomeArg[T] with the right type" in {
24 | val obj = new Object
25 | val (x1: OptArg[Int],x2: OptArg[String], x3: OptArg[Any]) = test(1, "test", obj)
26 |
27 | x1 should equal (SomeArg(1))
28 | x2 should equal (SomeArg("test"))
29 | x3 should equal (SomeArg(obj))
30 | }
31 |
32 | it should "implicitly convert OptArgs to Option" in {
33 | val (x1,x2,x3) = test(a=1)
34 |
35 | x1.map(x => x) should equal (Some(1))
36 | x2.getOrElse("was none") should equal ("was none")
37 |
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/DSL/TransformDSLSpec.scala:
--------------------------------------------------------------------------------
1 |
2 | import org.scalatest.{FlatSpec, Matchers}
3 | import vegas._
4 | import vegas.spec.Spec._
5 |
6 | /**
7 | * @author Aish Fenton.
8 | */
9 | class TransformDSLSpec extends FlatSpec with Matchers {
10 |
11 | // "TransformDSL Trait" should "add a transform filter" in {
12 | // val specBuilder = Vegas()
13 | // .transformFilter("datum.b2 > 60")
14 | //
15 | // specBuilder.spec.transform.get should equal (Transform(
16 | // filter = Some("datum.b2 > 60")
17 | // ))
18 | // }
19 |
20 | it should "add to an array of transform formulas" in {
21 | val specBuilder = Vegas()
22 | .addTransformCalculation("a", "datum.a + 1")
23 | .addTransformCalculation("b", "datum.b + 2")
24 |
25 | specBuilder.spec.transform.get should equal (Transform(
26 | calculate = Some(List(
27 | Formula("a", "datum.a + 1"),
28 | Formula("b", "datum.b + 2")
29 | ))
30 | ))
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/JsonMatchers.scala:
--------------------------------------------------------------------------------
1 | package vegas
2 |
3 | import argus.json.JsonDiff
4 | import org.scalactic.Equality
5 | import io.circe._
6 | import org.scalatest.matchers.{ MatchResult, Matcher }
7 |
8 | // TODO This is copy-and-pasted from argus. Really needs to be put somewhere shared.
9 | trait JsonMatchers {
10 |
11 | def beSameJsonAs(jrStr: String): Matcher[Json] = new Matcher[Json] {
12 | def apply(jl: Json) = {
13 | val jr = parser.parse(jrStr).right.get
14 | beSameJsonAs(jr)(jl)
15 | }
16 | }
17 |
18 | def beSameJsonAs(jr: Json): Matcher[Json] = new Matcher[Json] {
19 | def apply(jl: Json) = {
20 | val diff = JsonDiff.diff(jl, jr)
21 | MatchResult(diff.isEmpty, "Differences found in json: " + diff.mkString(","), "No differences found!")
22 | }
23 | }
24 |
25 | implicit val jsonEq = new Equality[Json] {
26 | def areEqual(a: Json, b: Any): Boolean =
27 | b match {
28 | case c: Json => JsonDiff.diff(a, c) == Nil
29 | case c: String => JsonDiff.diff(a, parser.parse(c).right.get) == Nil
30 | case _ => false
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/WebMatchers.scala:
--------------------------------------------------------------------------------
1 | package vegas
2 |
3 | import org.openqa.selenium.chrome.ChromeDriver
4 | import org.openqa.selenium.logging.LogType
5 | import java.util.logging.Level
6 |
7 | import org.scalatest.Matchers
8 | import vegas.util.WebGenerators
9 |
10 | import scala.collection.JavaConverters._
11 |
12 | trait WebMatchers extends WebGenerators {
13 | self: Matchers =>
14 |
15 | def hasNoJsErrors()(implicit webDriver: ChromeDriver) = {
16 | val logs = webDriver.manage.logs.get(LogType.BROWSER).filter(Level.WARNING).asScala.toList
17 | logs should be ('empty)
18 | }
19 |
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/data/FieldExtractorSpec.scala:
--------------------------------------------------------------------------------
1 | package vegas.data
2 |
3 | import org.scalatest.{FlatSpec, Matchers}
4 |
5 | class FieldExtractorSpec extends FlatSpec with Matchers {
6 |
7 | case class Ex(a: Int, b: String, c: Double)
8 |
9 | "FieldExtractor" should "extract fields from a case class into a Map[String, Any]" in {
10 | val ex = Ex(2, "UK", 3.14)
11 | val fields = FieldExtractor.extractFields( ex )
12 | fields should equal (Map("a" -> 2, "b" -> "UK", "c" -> 3.14))
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/data/SimpleTypeUtilsSpec.scala:
--------------------------------------------------------------------------------
1 | package vegas.data
2 |
3 | import org.scalatest.{FlatSpec, Matchers}
4 |
5 | /**
6 | * @author Aish Fenton.
7 | */
8 | class SimpleTypeUtilsSpec extends FlatSpec with Matchers {
9 |
10 | "SimpleTypeUtils.toDouble" should "try to transform Any into Doubles, or return None" in {
11 | SimpleTypeUtils.toDouble(3.14) should === (Some(3.14))
12 | SimpleTypeUtils.toDouble(3) should === (Some(3.0))
13 | SimpleTypeUtils.toDouble(3L) should === (Some(3.0))
14 | SimpleTypeUtils.toDouble(1.toByte) should === (Some(1.0))
15 | SimpleTypeUtils.toDouble("hi") should === (None)
16 | }
17 |
18 | "SimpleTypeUtils.isNumber" should "true if a number, false otherwise" in {
19 | SimpleTypeUtils.isNumber(3.14) should be(true)
20 | SimpleTypeUtils.isNumber(1.toByte) should be(true)
21 | SimpleTypeUtils.isNumber(Long.MaxValue) should be(true)
22 | SimpleTypeUtils.isNumber(true) should be(false)
23 | SimpleTypeUtils.isNumber("a") should be(false)
24 | }
25 |
26 | "SimpleTypeUtils.isSimpleType" should "true if a number or String, otherwise false" in {
27 | SimpleTypeUtils.isSimpleType(3.14) should be(true)
28 | SimpleTypeUtils.isSimpleType("a") should be(true)
29 | SimpleTypeUtils.isSimpleType(Some("ok")) should be(false)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/data/ValueTransformerSpec.scala:
--------------------------------------------------------------------------------
1 | package vegas.data
2 |
3 | import org.scalatest.{FlatSpec, Matchers}
4 | import vegas.util.Time
5 |
6 | class ValueTransformerSpec extends FlatSpec with Matchers {
7 |
8 | case class Test(i: Int)
9 |
10 | "DefaultValueTransformer" should "transform any values to appropriate primitive types" in {
11 | DefaultValueTransformer.transform(Map(
12 | "a" -> 1,
13 | "b" -> 3.14,
14 | "c" -> null,
15 | "d" -> Test(3),
16 | "e" -> Some(3),
17 | "f" -> None,
18 | "g" -> Time.mkSqlTimestamp(1984, 12, 25, 23, 59, 59)
19 | )) should === (Map(
20 | "a" -> 1,
21 | "b" -> 3.14,
22 | "c" -> null,
23 | "d" -> "Test(3)",
24 | "e" -> 3,
25 | "f" -> null,
26 | "g" -> "1984-12-25T23:59:59"
27 | ))
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/fixtures/BasicPlots.scala:
--------------------------------------------------------------------------------
1 | package vegas.fixtures
2 |
3 | import vegas._
4 | import vegas.data.External._
5 | import vegas.DSL.SpecBuilder
6 |
7 | object BasicPlots {
8 |
9 | val SimpleBarChart =
10 | Vegas("A simple bar chart with embedded data.").
11 | withData(Seq(
12 | Map("a" -> "A", "b" -> 28), Map("a" -> "B", "b" -> 55), Map("a" -> "C", "b" -> 43),
13 | Map("a" -> "D", "b" -> 91), Map("a" -> "E", "b" -> 81), Map("a" -> "F", "b" -> 53),
14 | Map("a" -> "G", "b" -> 19), Map("a" -> "H", "b" -> 87), Map("a" -> "I", "b" -> 52)
15 | )).
16 | encodeX("a", Ordinal).
17 | encodeY("b", Quantitative).
18 | mark(Bar)
19 |
20 | val AggregateBarChart =
21 | Vegas("A bar chart showing the US population distribution of age groups in 2000.").
22 | withURL(Population).
23 | mark(Bar).
24 | filter("datum.year == 2000").
25 | encodeY("age", Ordinal, scale=Scale(bandSize=17)).
26 | encodeX("people", Quantitative, aggregate=AggOps.Sum, axis=Axis(title="population"))
27 |
28 | val GroupedBarChart =
29 | Vegas().
30 | withURL(Population).
31 | mark(Bar).
32 | addTransformCalculation("gender", """datum.sex == 2 ? "Female" : "Male"""").
33 | filter("datum.year == 2000").
34 | encodeColumn("age", Ord, scale=Scale(padding=4.0), axis=Axis(orient=Orient.Bottom, axisWidth=1.0, offset= -8.0)).
35 | encodeY("people", Quantitative, aggregate=AggOps.Sum, axis=Axis(title="population", grid=false)).
36 | encodeX("gender", Nominal, scale=Scale(bandSize = 6.0), hideAxis=true).
37 | encodeColor("gender", Nominal, scale=Scale(rangeNominals=List("#EA98D2", "#659CCA"))).
38 | configFacet(cell=CellConfig(strokeWidth = 0))
39 |
40 | val AreaChart =
41 | Vegas().
42 | withURL(Unemployment).
43 | mark(Area).
44 | encodeX("date", Temp, timeUnit=TimeUnit.Yearmonth, scale=Scale(nice=Nice.Month),
45 | axis=Axis(axisWidth=0, format="%Y", labelAngle=0)).
46 | encodeY("count", Quantitative, aggregate=AggOps.Sum).
47 | configCell(width=300, height=200)
48 |
49 | val NormalizedStackedBarChart =
50 | Vegas().
51 | withURL(Population).
52 | filter("datum.year == 2000").
53 | addTransform("gender", "datum.sex == 2 ? \"Female\" : \"Male\"").
54 | mark(Bar).
55 | encodeY("people", Quant, AggOps.Sum, axis=Axis(title="population")).
56 | encodeX("age", Ord, scale=Scale(bandSize= 17)).
57 | encodeColor("gender", Nominal, scale=Scale(rangeNominals=List("#EA98D2", "#659CCA"))).
58 | configMark(stacked=StackOffset.Normalize)
59 |
60 | val BinnedChart =
61 | Vegas("A trellis scatterplot showing Horsepower and Miles per gallons, faceted by binned values of Acceleration.").
62 | withURL(Cars).
63 | mark(Point).
64 | encodeX("Horsepower", Quantitative).
65 | encodeY("Miles_per_Gallon", Quantitative).
66 | encodeRow("Acceleration", Quantitative, enableBin=true)
67 |
68 | val ScatterBinnedPlot =
69 | Vegas().
70 | withURL(Movies).
71 | mark(Point).
72 | encodeX("IMDB_Rating", Quantitative, bin=Bin(maxbins=10.0)).
73 | encodeY("Rotten_Tomatoes_Rating", Quantitative, bin=Bin(maxbins=10.0)).
74 | encodeSize(aggregate=AggOps.Count, field="*", dataType=Quantitative)
75 |
76 | val ScatterColorPlot =
77 | Vegas().
78 | withURL(Cars).
79 | mark(Point).
80 | encodeX("Horsepower", Quantitative).
81 | encodeY("Miles_per_Gallon", Quantitative).
82 | encodeColor(field="Origin", dataType=Nominal)
83 |
84 | val ScatterBinnedColorPlot =
85 | Vegas("A scatterplot showing horsepower and miles per gallons with binned acceleration on color.").
86 | withURL(Cars).
87 | mark(Point).
88 | encodeX("Horsepower", Quantitative).
89 | encodeY("Miles_per_Gallon", Quantitative).
90 | encodeColor(field="Acceleration", dataType=Quantitative, bin=Bin(maxbins=5.0))
91 |
92 | val StackedAreaBinnedPlot =
93 | Vegas().
94 | withURL(Cars).
95 | mark(Area).
96 | encodeX("Acceleration", Quantitative, bin=Bin()).
97 | encodeY("Horsepower", Quantitative, AggOps.Mean, enableBin=false).
98 | encodeColor(field="Cylinders", dataType=Nominal)
99 |
100 | val SortColorPlot =
101 | Vegas("The Trellis display by Becker et al. helped establish small multiples as a “powerful mechanism for understanding interactions in studies of how a response depends on explanatory variables”. Here we reproduce a trellis of Barley yields from the 1930s, complete with main-effects ordering to facilitate comparison.").
102 | withURL(Barley).
103 | mark(Point).
104 | encodeRow("site", Ordinal).
105 | encodeX("yield", Quantitative, aggregate=AggOps.Mean).
106 | encodeY("variety", Ordinal, sortField=Sort("yield", AggOps.Mean), scale=Scale(bandSize = 12.0)).
107 | encodeColor(field="year", dataType=Nominal)
108 |
109 | val CustomShapePlot =
110 | Vegas("A scatterplot with custom star shapes.").
111 | withURL(Cars).
112 | mark(Point).
113 | encodeX("Horsepower", Quant).
114 | encodeY("Miles_per_Gallon", Quant).
115 | encodeColor("Cylinders", Nom).
116 | encodeSize("Weight_in_lbs", Quant).
117 | configMark(customShape="M0,0.2L0.2351,0.3236 0.1902,0.0618 0.3804,-0.1236 0.1175,-0.1618 0,-0.4 -0.1175,-0.1618 -0.3804,-0.1236 -0.1902,0.0618 -0.2351,0.3236 0,0.2Z")
118 |
119 | val ScatterAggregateDetail =
120 | Vegas("A scatterplot showing average horsepower and displacement for cars from different origins.").
121 | withURL(Cars).
122 | mark(Point).
123 | encodeX("Horsepower", Quant, AggOps.Mean).
124 | encodeY("Displacement", Quant, AggOps.Mean).
125 | encodeDetail("Origin")
126 |
127 | val LineDetail =
128 | Vegas("Stock prices of 5 Tech Companies Over Time.").
129 | withURL(Stocks, formatType = DataFormat.Csv).
130 | mark(Line).
131 | encodeX("date", Temp).
132 | encodeY("price", Quant).
133 | encodeDetailFields(Field(field="symbol", dataType=Nominal))
134 |
135 | val GithubPunchCard =
136 | Vegas().
137 | withURL(Github, formatType = DataFormat.Csv).
138 | mark(Circle).
139 | encodeX("time", Temporal, timeUnit = TimeUnit.Hours).
140 | encodeY("time", Temporal, timeUnit = TimeUnit.Day).
141 | encodeSize("count", Quantitative, aggregate = AggOps.Sum)
142 |
143 | val AnscombesQuartet =
144 | Vegas("Anscombe's Quartet").
145 | withURL(Anscombe).
146 | mark(Circle).
147 | encodeX("X", Quantitative, scale = Scale(zero = false)).
148 | encodeY("Y", Quantitative, scale = Scale(zero = false)).
149 | encodeColumn("Series", Nominal).
150 | configMark(opacity = 1)
151 |
152 | val StackedAreaChart =
153 | Vegas("Area chart showing weight of cars over time.").
154 | withURL(Unemployment).
155 | mark(Area).
156 | encodeX(
157 | "date", Temporal, timeUnit = TimeUnit.Yearmonth,
158 | axis = Axis(axisWidth = 0, format = "%Y", labelAngle = 0),
159 | scale = Scale(nice = spec.Spec.NiceTimeEnums.Month)
160 | ).
161 | encodeY("count", Quantitative, aggregate = AggOps.Sum).
162 | encodeColor("series", Nominal, scale = Scale(rangePreset = Category20b)).
163 | configCell(width = 300, height = 200)
164 |
165 | val NormalizedStackedAreaChart =
166 | Vegas().
167 | withURL(Unemployment).
168 | mark(Area).
169 | encodeX(
170 | "date", Temporal, timeUnit = TimeUnit.Yearmonth,
171 | axis = Axis(axisWidth=0, format="%Y", labelAngle=0),
172 | scale = Scale(nice = spec.Spec.NiceTimeEnums.Month)
173 | ).
174 | encodeY("count", Quantitative, aggregate = AggOps.Sum, hideAxis = Some(true)).
175 | encodeColor("series", Nominal, scale = Scale(rangePreset = Category20b)).
176 | configCell(width = 300, height = 200).
177 | configMark(stacked = StackOffset.Normalize)
178 |
179 | val Streamgraph =
180 | Vegas().
181 | withURL(Unemployment).
182 | mark(Area).
183 | encodeX(
184 | "date", Temporal, timeUnit = TimeUnit.Yearmonth,
185 | axis = Axis(axisWidth = 0, format = "%Y", labelAngle = 0, tickSize = Some(0.0)),
186 | scale = Scale(nice = spec.Spec.NiceTimeEnums.Month)
187 | ).
188 | encodeY("count", Quantitative, aggregate = AggOps.Sum, hideAxis = Some(true)).
189 | encodeColor("series", Nominal, scale = Scale(rangePreset = Category20b)).
190 | configCell(width = 300, height = 200).
191 | configMark(stacked = StackOffset.Center)
192 |
193 | val StackedBarChart =
194 | Vegas().
195 | withURL(SeattleWeather, formatType = DataFormat.Csv).
196 | mark(Bar).
197 | encodeX("date", Temporal, timeUnit = TimeUnit.Month, axis = Axis(title = "Month of the year")).
198 | encodeY("*", Quantitative, aggregate = AggOps.Count).
199 | encodeColor("weather", Nominal, scale = Scale(
200 | domainNominals = List("sun", "fog", "drizzle", "rain", "snow"),
201 | rangeNominals = List("#e7ba52", "#c7c7c7", "#aec7e8", "#1f77b4", "#9467bd")),
202 | legend = Legend(title = "Weather type"))
203 |
204 | val StripPlot =
205 | Vegas("Shows the relationship between horsepower and the numbver of cylinders using tick marks.").
206 | withURL(Cars).
207 | mark(Tick).
208 | encodeX("Horsepower", Quantitative).
209 | encodeY("Cylinders", Ordinal)
210 |
211 | // Names (ex. bar, bar_aggregate, etc.) are corresponding to filenames
212 | // of `/core/src/test/resources/example-specs/*.vl.json`
213 | val plotsWithNames: List[(String, SpecBuilder)] = List(
214 | "bar" -> SimpleBarChart,
215 | "bar_aggregate" -> AggregateBarChart,
216 | "bar_grouped" -> GroupedBarChart,
217 | "area" -> AreaChart,
218 | "stacked_bar_normalize" -> NormalizedStackedBarChart,
219 | "scatter_binned" -> ScatterBinnedPlot,
220 | "scatter_color" -> ScatterColorPlot,
221 | "scatter_binned_color" -> ScatterBinnedColorPlot,
222 | "stacked_area_binned" -> StackedAreaBinnedPlot,
223 | "trellis_barley" -> SortColorPlot,
224 | "trellis_scatter_binned_row" -> BinnedChart,
225 | "scatter_shape_custom" -> CustomShapePlot,
226 | "line_detail" -> LineDetail,
227 | "github_punchcard" -> GithubPunchCard,
228 | "trellis_anscombe" -> AnscombesQuartet,
229 | "stacked_area" -> StackedAreaChart,
230 | "stacked_area_normalize" -> NormalizedStackedAreaChart,
231 | "stacked_area_stream" -> Streamgraph,
232 | "stacked_bar_weather" -> StackedBarChart,
233 | "tick_strip" -> StripPlot
234 | ).sortBy(_._1)
235 |
236 | val plots: List[SpecBuilder] = plotsWithNames.map(_._2)
237 | }
238 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/fixtures/VegasPlots.scala:
--------------------------------------------------------------------------------
1 | package vegas.fixtures
2 |
3 | import vegas.DSL.SpecBuilder
4 | import vegas._
5 | import vegas.data.External._
6 |
7 | object VegasPlots {
8 |
9 | val ValuePlot =
10 | Vegas("Plot with hard-coded size value").
11 | withURL(Cars).
12 | mark(Circle).
13 | encodeY("Horsepower", Quantitative).
14 | encodeX("Miles_per_Gallon", Quantitative).
15 | encodeSize(value=201L)
16 |
17 | val IQRPlot =
18 | Vegas.layered("Plots both mean and IQR as a background layer").
19 | withURL(Population).
20 | withLayers(
21 | Layer().
22 | mark(Line).
23 | encodeX("age", Ordinal).
24 | encodeY("people", aggregate=AggOps.Mean),
25 | Layer().
26 | mark(Area).
27 | encodeX("age", Ordinal).
28 | encodeY("people", aggregate=AggOps.Q1).
29 | encodeY2("people", aggregate=AggOps.Q3)
30 | )
31 |
32 | val LegendPlot =
33 | Vegas("Plot with legend on the left and a different title ").
34 | withURL(Cars).
35 | mark(Point).
36 | encodeY("Horsepower", Quantitative).
37 | encodeX("Miles_per_Gallon", Quantitative).
38 | encodeColor(field="Origin", dataType=Nominal, legend=Legend(orient = "left", title="Place of Origin" )).
39 | encodeShape(field="Origin", dataType=Nominal, legend=Legend(orient = "left", title="Place of Origin",
40 | titleColor="red"))
41 |
42 | val BinnedPlot =
43 | Vegas("Plot to show Binning options").
44 | withURL(Movies).
45 | mark(Bar).
46 | encodeX("IMDB_Rating", Quantitative, bin=Bin(step=2.0, maxbins=3.0)).
47 | encodeY(field="*", Quantitative, aggregate=AggOps.Count)
48 |
49 | val BinnedPlotWithSort =
50 | Vegas("Plot to show Binning options").
51 | withURL(Movies).
52 | mark(Bar).
53 | encodeX("Worldwide_Gross", Quant, bin=Bin(maxbins=20.0), sortOrder=SortOrder.Desc).
54 | encodeY(field="*", Quant, aggregate=AggOps.Count)
55 |
56 | val ColoredTextScatterPlot =
57 | Vegas("Plot to show usage of encodeText").
58 | withURL(Cars).
59 | addTransform("OriginInitial", "datum.Origin[0]").
60 | mark(Text).
61 | encodeX("Horsepower", Quantitative).
62 | encodeY("Miles_per_Gallon", Quantitative).
63 | encodeColor(field="Origin", dataType= Nominal).
64 | encodeText(field="OriginInitial", dataType= Nominal)
65 |
66 | val plotsWithNames: List[(String, SpecBuilder)] = List(
67 | "value_plot" -> ValuePlot,
68 | "iqr_plot" -> IQRPlot,
69 | "legend_plot" -> LegendPlot,
70 | "binned plot" -> BinnedPlot,
71 | "binned_plot_with_sort" -> BinnedPlotWithSort,
72 | "colored_txt_scatter_plot" -> ColoredTextScatterPlot
73 | ).sortBy(_._1)
74 |
75 | val plots: List[SpecBuilder] = plotsWithNames.map(_._2)
76 | }
77 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/integration/PlotHtml.scala:
--------------------------------------------------------------------------------
1 | package vegas.integration
2 |
3 | import vegas.WebMatchers
4 | import vegas.fixtures.{BasicPlots, VegasPlots}
5 | import org.scalatest.{BeforeAndAfterAll, FlatSpec, Matchers}
6 | import org.scalatest.selenium.Chrome
7 |
8 | class PlotHtml extends FlatSpec with Matchers with WebMatchers with Chrome with BeforeAndAfterAll {
9 |
10 | val scheme = "file://"
11 |
12 | behavior of "Basic plots"
13 | BasicPlots.plotsWithNames.foreach { case (name, plot) =>
14 | it should s"render HTML without error ${name}" in {
15 | go to (scheme + mkPage(plot))
16 | find(tagName("canvas"))
17 | hasNoJsErrors()
18 | }
19 | }
20 |
21 | behavior of "Vegas plots"
22 | VegasPlots.plotsWithNames.foreach { case (name, plot) =>
23 | it should s"render HTML without error ${name}" in {
24 | go to (scheme + mkPage(plot))
25 | find(tagName("canvas"))
26 | hasNoJsErrors()
27 | }
28 |
29 | }
30 |
31 | override protected def afterAll() {
32 | quit()
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/macros/AliasWithLensSpec.scala:
--------------------------------------------------------------------------------
1 | package vegas.macros
2 |
3 | import monocle.Lens
4 | import monocle.macros.GenLens
5 | import org.scalatest.{FlatSpec, Matchers}
6 |
7 | import scala.annotation.StaticAnnotation
8 |
9 | /**
10 | * @author Aish Fenton.
11 | */
12 | class AliasWithLensSpec extends FlatSpec with Matchers {
13 |
14 | class ignore_me extends StaticAnnotation
15 | case class Ex(a: Int)
16 | val _a = GenLens[Ex](_.a)
17 |
18 | @aliased
19 | trait Test {
20 |
21 | @alias_with_lens("fnA1", _a)
22 | private def fnA(a: Lens[Ex, Int])(b: Int, c: String) = (a, b, c)
23 |
24 | @ignore_me
25 | private def fnB(a: Lens[Ex, Int])(b: Int, c: String) = (a, b, c)
26 |
27 | @alias_with_lens("fnC1", _a)
28 | @ignore_me
29 | private def fnC(a: Lens[Ex, Int])(b: Int, c: String) = (a, b, c)
30 | }
31 |
32 | "alias_with_lens" should "alias the annotated method and partially apply it using the given lens" in {
33 | val t = new Test { }
34 | t.fnA1(1, "2") should equal((_a, 1, "2"))
35 | }
36 |
37 | it should "ignore other annotations" in {
38 | val t = new Test { }
39 | t.fnC1(1, "2") should equal((_a, 1, "2"))
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/render/ShowSpec.scala:
--------------------------------------------------------------------------------
1 | package vegas.render
2 |
3 | import org.scalatest.{FlatSpec, Matchers}
4 | import vegas.DSL.SpecBuilder
5 | import vegas.{Bar, Nominal, Quantitative, Vegas}
6 |
7 | class ShowSpec extends FlatSpec with Matchers {
8 | val data = Seq( Map("population" -> 318000000, "country" -> "USA"), Map("population" -> 64000000, "country" -> "UK") )
9 |
10 | val specBuilder: SpecBuilder = Vegas("Country Pop")
11 | .withData(data)
12 | .addTransformCalculation("pop_millions", "datum.population / 1000000")
13 | .encodeX("pop_millions", Quantitative)
14 | .encodeY("country", Nominal)
15 | .mark(Bar)
16 |
17 | "show" should "use zeppelin when it's in scope" in {
18 | var called: String = null
19 | object org {
20 | object apache {
21 | object zeppelin {
22 | object spark {
23 | object utils {
24 | object DisplayUtils {
25 | def html(str: String): String = {
26 | called = str
27 | s"%html $str"
28 | }
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
35 | specBuilder.show
36 | assert(called != null) // can't easily compare to an expected output due to random UUID
37 | }
38 |
39 | it should "use jupyter publish.html when it's in scope" in {
40 | var called: String = null
41 | object publish {
42 | def html(str: String) = {
43 | called = str
44 | }
45 | }
46 | specBuilder.show
47 | assert(called != null)
48 | }
49 |
50 | it should "use jupyter display.html when it's in scope" in {
51 | var called: String = null
52 | object display {
53 | def html(str: String) = {
54 | called = str
55 | }
56 | }
57 | specBuilder.show
58 | assert(called != null)
59 | }
60 |
61 | it should "use toree kernel.display.content when it's in scope" in {
62 | var calledStr: String = null
63 | var calledType: String = null
64 | object kernel {
65 | object display {
66 | def content(typ: String, str: String) = {
67 | calledStr = str
68 | calledType = typ
69 | }
70 | }
71 | }
72 | specBuilder.show
73 | assert(calledStr != null)
74 | assert(calledType == "text/html")
75 | }
76 |
77 | it should "use window when nothing else is in scope" in {
78 | var called: SpecBuilder = null
79 | object vegas {
80 | object render {
81 | object ShowRender {
82 | def using(fn: SpecBuilder => Unit) = {
83 | new ShowRender {
84 | def apply(sb: SpecBuilder) = {
85 | called = sb
86 | }
87 | }
88 | }
89 | }
90 | }
91 | }
92 | specBuilder.show
93 | assert(called == specBuilder)
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/render/StaticHTMLRendererSpec.scala:
--------------------------------------------------------------------------------
1 | package vegas.render
2 |
3 | import org.scalatest.{FlatSpec, Matchers}
4 | import vegas.DSL.SpecBuilder
5 | import vegas._
6 |
7 | class StaticHTMLRendererSpec extends FlatSpec with Matchers {
8 | val data = Seq( Map("population" -> 318000000, "country" -> "USA"), Map("population" -> 64000000, "country" -> "UK") )
9 |
10 | val specBuilder: SpecBuilder = Vegas("Country Pop")
11 | .withData(data)
12 | .addTransformCalculation("pop_millions", "datum.population / 1000000")
13 | .encodeX("pop_millions", Quantitative)
14 | .encodeY("country", Nominal)
15 | .mark(Bar)
16 |
17 | "StaticHTMLRenderer.HTMLPage" should "produce an HTML page" in {
18 | val html = specBuilder.html.pageHTML()
19 | html shouldBe a [String]
20 | html should startWith("")
21 | html should include(specBuilder.toJson)
22 | html should endWith("")
23 | }
24 |
25 | "StaticHTMLRenderer.HTMLChart" should "produce a HTML script element containing the Spec json" in {
26 | val html = specBuilder.html.plotHTML("test")
27 |
28 | html shouldBe a [String]
29 | html.trim should startWith ("")
32 | }
33 |
34 | it should "use the given chart name" in {
35 | val name = "myChart"
36 | val html = specBuilder.html.plotHTML(name)
37 |
38 | html should include ("embed(\"#" + name)
39 | html should include ("id='" + name)
40 | }
41 |
42 | it should "have a default chart name that starts with a letter, and contains no spaces" in {
43 | val html = specBuilder.html.plotHTML()
44 | val name = "
33 | val json = Source.fromFile(example).getLines.mkString
34 | val spec = parser.decode[VegaUnion](json)
35 |
36 | spec should be ('isRight)
37 | spec.right.get.asJson should beSameJsonAs(json)
38 | }
39 | }
40 |
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/util/NotebookGenerator.scala:
--------------------------------------------------------------------------------
1 | package vegas.util
2 |
3 | import java.io.File
4 |
5 | import scala.io.Source
6 |
7 | trait NotebookGenerator {
8 |
9 | val PlotBlock = """Vegas(.layered)?\((.+\n)+\n""".r
10 | val Indent = """\n(\s*)""".r
11 | val Title = """Vegas(.layered)?\("(.*)"\)""".r
12 |
13 | def preamble(version: String): List[(String,String)]
14 |
15 | private def extractPlotSources(files: List[File]): List[(String, String)] = files.flatMap(extractPlotSources)
16 |
17 | // Extract blocks of plot code. A plot starts with "Vegas" and ends with a blank line
18 | private def extractPlotSources(file: File): List[(String, String)] = {
19 | val code = Source.fromFile(file).getLines().mkString("\n")
20 | PlotBlock.findAllIn(code).toList.map { block =>
21 | val title = Title.findFirstMatchIn(block).map(_.group(2)).getOrElse("")
22 | val code = format(block) + ".\n show"
23 | (title, code)
24 | }
25 | }
26 |
27 | protected def mkNotebook(plots: List[(String, String)]): String
28 |
29 | protected def format(s: String) = {
30 | // Base on indent level after first line break, since first line doesn't include
31 | // it's indent.
32 | val indent = Indent.findFirstMatchIn(s).map(_.group(1).length - 2).getOrElse(0)
33 |
34 | // Break into lines, and remove indent
35 | val parts = s.split("\n").toList
36 | (parts.head.trim :: parts.tail.map(_.drop(indent))).mkString("\n")
37 | }
38 |
39 | protected def escapeJson(s: String) = s.
40 | replace("\\", "\\\\").
41 | replace("\"", "\\\"").
42 | replace("\n", "\\n")
43 |
44 |
45 | def generate(version: String, files: List[File]) = {
46 | mkNotebook(preamble(version) ++ extractPlotSources(files))
47 | }
48 | }
49 |
50 | class JupyterGenerator extends NotebookGenerator {
51 |
52 | def toJsonStrings(s: String) = s.split("\n").
53 | map { line =>
54 | if (line.isEmpty) "" else "\"" + escapeJson(line) + "\\n\""
55 | }.
56 | filterNot(_.isEmpty)
57 |
58 | def preamble(version: String) =
59 | ("", "import $ivy.`org.vegas-viz::vegas:" + s"$version`") ::
60 | ("", """
61 | |import vegas._
62 | |import vegas.data.External._""".stripMargin
63 | ) ::
64 | Nil
65 |
66 | def mkNotebook(blocks: List[(String, String)]) = {
67 | s"""
68 | | {
69 | | "cells": [ ${ blocks.map(mkCell).mkString(",") } ],
70 | | "metadata": {
71 | | "kernelspec": {
72 | | "display_name": "Scala 2.11",
73 | | "language": "scala211",
74 | | "name": "scala211"
75 | | },
76 | | "language_info": {
77 | | "codemirror_mode": "text/x-scala",
78 | | "file_extension": ".scala",
79 | | "mimetype": "text/x-scala",
80 | | "name": "scala211",
81 | | "pygments_lexer": "scala",
82 | | "version": "2.11.6"
83 | | }
84 | | },
85 | | "nbformat": 4,
86 | | "nbformat_minor": 0
87 | | }
88 | """.stripMargin
89 | }
90 |
91 | def mkCell(block: (String, String)) = {
92 | val title =
93 | s"""
94 | | {
95 | | "cell_type" : "markdown",
96 | | "metadata": {},
97 | | "source": [ "# ${ block._1 }" ]
98 | | },
99 | """.stripMargin
100 |
101 | val code = s"""
102 | | {
103 | | "cell_type" : "code",
104 | | "execution_count": null,
105 | | "outputs": [],
106 | | "metadata": {},
107 | | "source": [ ${toJsonStrings(block._2).mkString(",") } ]
108 | | }
109 | """.stripMargin
110 |
111 | if (block._1.isEmpty) code else title + code
112 | }
113 |
114 | }
115 |
116 |
117 | class ZeppelinGenerator extends NotebookGenerator {
118 |
119 | def preamble(version: String) =
120 | ("", s"""
121 | |%dep
122 | |z.load("org.vegas-viz:vegas-spark_2.11:${version}")""".stripMargin) ::
123 | ("", """
124 | |import vegas._
125 | |import vegas.data.External._""".stripMargin) ::
126 | Nil
127 |
128 | def mkNotebook(plots: List[(String, String)]) = {
129 | s"""
130 | | {
131 | | "name": "Vegas Examples",
132 | | "angularObjects": {},
133 | | "paragraphs": [ ${ plots.map(mkCell).mkString(",") } ]
134 | | }
135 | """.stripMargin
136 | }
137 |
138 | def mkCell(block: (String, String)) =
139 | s"""
140 | | {
141 | | "config": { "title": ${ if (block._1.isEmpty) "false" else "true" } },
142 | | "title": "${block._1}",
143 | | "text": "${ escapeJson(block._2) }"
144 | | }
145 | """.stripMargin
146 |
147 | }
148 |
149 | object GenerateNotebooks extends App {
150 | val jupyterGenerator = new JupyterGenerator
151 | val zepGenerator = new ZeppelinGenerator
152 |
153 | def gen(name: String, generator: NotebookGenerator, version: String, sources: List[File], destDir: File) = {
154 | require(destDir.isDirectory)
155 | val json = generator.generate(version, sources)
156 |
157 | val dest = new File(destDir, name)
158 | val out = new java.io.PrintWriter(dest)
159 | out.write(json)
160 | out.close()
161 | }
162 |
163 | val version = args(0)
164 | val sources = args.toList.drop(1).take(args.length - 2).map(new File(_))
165 | val destDir = new File(args.last)
166 |
167 | gen("jupyter_example.ipynb", jupyterGenerator, version, sources, destDir)
168 | gen("zeppelin_example.json", zepGenerator, version, sources, destDir)
169 | }
170 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/util/Time.scala:
--------------------------------------------------------------------------------
1 | package vegas.util
2 |
3 | import java.util.{Calendar, GregorianCalendar, TimeZone}
4 |
5 | /**
6 | * Annoying but necessary (open to other suggestions though) utils for dealing with
7 | * dates / times in Scala/Java
8 | */
9 | object Time {
10 |
11 | /**
12 | * Makes a java.util.Date with the given year, month, day, and adjusts for local timezone.
13 | * @param year The year (i.e. 2015)
14 | * @param month The month number: 1-12 (i.e. 12 == December)
15 | * @param day The day of the month: 1-31.
16 | * @param hour The hour of the day (e.g. 23)
17 | * @param minutes The minutes of the hour (e.g. 59)
18 | * @param seconds The seconds of minute (e.g. 59)
19 | * @param timeZone The timezone of given date (i.e. "PST"). Defaults to PST.
20 | * @return A java.util Date object
21 | */
22 | def mkDate(year: Int, month: Int, day: Int, hour: Int = 0, minutes: Int = 0, seconds: Int = 0, timeZone: String = "PST"): java.util.Date = {
23 | val refTimeZone = TimeZone.getTimeZone(timeZone)
24 | val time = new GregorianCalendar(refTimeZone)
25 | time.set(year, month - 1, day, hour, minutes, seconds)
26 |
27 | // Annoying but necessary to make sure timezones on different machines don't break
28 | // the test.
29 | val localOffset: Int = TimeZone.getDefault.getRawOffset
30 | val refOffset: Int = refTimeZone.getRawOffset
31 | time.add(Calendar.MILLISECOND, refOffset - localOffset)
32 |
33 | time.getTime
34 | }
35 |
36 | def mkSqlDate(year: Int, month: Int, day: Int, timeZone: String = "PST") = new java.sql.Date(mkDate(year, month, day, timeZone=timeZone).getTime)
37 | def mkSqlTimestamp(year: Int, month: Int, day: Int, hour: Int, minutes: Int, seconds: Int,
38 | timeZone: String = "PST") = new java.sql.Timestamp(mkDate(year, month, day, hour, minutes, seconds, timeZone).getTime)
39 |
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/core/src/test/scala/vegas/util/WebGenerators.scala:
--------------------------------------------------------------------------------
1 | package vegas.util
2 |
3 | import java.io.{File, FileWriter}
4 |
5 | import vegas.DSL.SpecBuilder
6 | import vegas.fixtures._
7 |
8 | /**
9 | * @author Aish Fenton.
10 | */
11 | trait WebGenerators {
12 |
13 | def mkPage(plots: Seq[SpecBuilder]) = {
14 | val file = File.createTempFile("vegas", ".html")
15 | val writer = new FileWriter(file)
16 |
17 | val body = plots.map { plot =>
18 | plot.html.frameHTML()
19 | }.mkString
20 |
21 | writer.write(s"""
22 |
23 |
24 | $body