├── .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 | Vegas 4 | 5 | [![TravisCI](https://travis-ci.org/vegas-viz/Vegas.svg?branch=master)](https://travis-ci.org/vegas-viz/Vegas) 6 | [![codecov](https://codecov.io/gh/vegas-viz/Vegas/branch/master/graph/badge.svg)](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 | !["Readme Chart 1"](https://www.vegas-viz.org/images/readme-chart-1.png) 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 25 | 26 | """) 27 | writer.close 28 | file.getAbsolutePath 29 | } 30 | 31 | def mkPage(plot: SpecBuilder) = { 32 | val file = File.createTempFile("vegas", ".html") 33 | val writer = new FileWriter(file) 34 | 35 | val html = plot.html.pageHTML() 36 | writer.write(html) 37 | writer.close 38 | 39 | file.getAbsolutePath 40 | } 41 | } 42 | 43 | /** 44 | * Sometimes automated testing isn't enough. Sometimes you need to see stuff. This little "app" generates all the 45 | * fixture plots and opens them in the mac browser. 46 | */ 47 | object Look extends App with WebGenerators { 48 | import scala.sys.process._ 49 | 50 | val plots = BasicPlots.plots ++ VegasPlots.plots 51 | val page = mkPage(plots) 52 | 53 | s"open $page".!! 54 | 55 | } 56 | -------------------------------------------------------------------------------- /flink/src/main/scala/vegas/flink/Flink.scala: -------------------------------------------------------------------------------- 1 | package vegas.flink 2 | 3 | import org.apache.flink.api.scala._ 4 | import vegas.DSL.DataDSL 5 | 6 | /** 7 | * Created by ASRagab on 8/18/16. 8 | */ 9 | object Flink { 10 | 11 | implicit class FlinkExt(val specBuilder: DataDSL[_]) { 12 | 13 | def withData[T <: Product](ds: DataSet[T], limit: Int = 1000) = { 14 | val count = ds.count() 15 | val localData: Seq[T] = { 16 | if (count >= limit) ds.first(limit).collect() else ds.collect() 17 | } 18 | 19 | specBuilder.withCaseClasses(localData) 20 | } 21 | 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /macros/src/main/scala/vegas/macros/AliasWithLens.scala: -------------------------------------------------------------------------------- 1 | package vegas.macros 2 | 3 | import language.experimental.macros 4 | import scala.annotation.{ StaticAnnotation, compileTimeOnly } 5 | import reflect.macros.blackbox.Context 6 | import macrocompat.bundle 7 | import monocle.Lens 8 | 9 | class alias_with_lens(name: String, lens: Lens[_,_]) extends StaticAnnotation 10 | 11 | @compileTimeOnly("You must enable the macro paradise plugin.") 12 | class aliased extends StaticAnnotation { 13 | def macroTransform(annottees: Any*): Any = macro AliasMacros.lensAliasMacroImpl 14 | } 15 | 16 | /** 17 | * Aliases an annotated method using the given name and partially applies using the given lens 18 | * Hat tip to following for inspiration 19 | * http://stackoverflow.com/questions/33279472/use-scala-macros-to-generate-methods 20 | */ 21 | @bundle 22 | class AliasMacros(val c: Context) { 23 | import c.universe._ 24 | 25 | def paramsToArgs(params: List[ValDef]): List[Tree] = { 26 | params.map { case q"$a val $param: $b = $c" => q"$param" } 27 | } 28 | 29 | def lensAliasMacroImpl(annottees: c.Expr[Any]*): c.Expr[Any] = { 30 | import c.universe._ 31 | 32 | val result = annottees map (_.tree) match { 33 | 34 | // Match a trait 35 | case (traitDef @ q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self => ..$stats }") :: _ => { 36 | 37 | // Loop through all functions with aliases, and great new defs for each using given name and lens 38 | val aliasedDefs = for { 39 | q"@..$annots private def $tname[..$tparams](...$paramss): $tpt = $expr" <- stats 40 | annot <- annots 41 | Apply(Select(New(Ident(TypeName(aName))), _), annotArgs) = annot if (aName == "alias_with_lens") 42 | } yield { 43 | val List(Literal(Constant(name: String)), lens) = annotArgs 44 | val aliasIdent = TermName(name) 45 | val args = paramss.tail.map(paramsToArgs) 46 | q"def $aliasIdent[..$tparams](...${ paramss.tail }): $tpt = $tname(..$lens)(...$args)" 47 | } 48 | 49 | // Now rewrite trait with additional methods 50 | if(aliasedDefs.nonEmpty) { 51 | q""" 52 | $mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self => 53 | ..$stats 54 | ..$aliasedDefs 55 | } 56 | """ 57 | } else traitDef 58 | 59 | } 60 | 61 | case _ => c.abort(c.enclosingPosition, "Invalid annotation target: not a trait") 62 | 63 | } 64 | 65 | c.Expr[Any](result) 66 | } 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /macros/src/main/scala/vegas/macros/ShowRenderMacros.scala: -------------------------------------------------------------------------------- 1 | package vegas.macros 2 | 3 | import scala.reflect.macros.whitebox 4 | import scala.util.Try 5 | 6 | class ShowRenderMacros(val c: whitebox.Context) { 7 | import c.universe.{Try => _, _} 8 | 9 | private def html(tree: Tree) = Try(c.typecheck(tree)).flatMap { 10 | checked => Try(c.typecheck(q"vegas.render.ShowHTML($checked)")) 11 | } 12 | 13 | def materializeDefault: Tree = { 14 | val possibilities: Try[Tree] = 15 | html(q"""(str: String) => { println(org.apache.zeppelin.spark.utils.DisplayUtils.html(str)) }""") orElse 16 | html(q"""(str: String) => { publish.html(str) }""") orElse 17 | html(q"""(str: String) => { display.html(str) }""") orElse 18 | html(q"""(str: String) => { kernel.display.content("text/html", str) }""") orElse 19 | Try(c.typecheck(q"""vegas.render.ShowRender.using(_.window.show)""")) 20 | 21 | possibilities.getOrElse(c.abort(c.enclosingPosition, "No default Vegas renderer could be materialized")) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /notebooks/jupyter_example.ipynb: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "cells": [ 4 | { 5 | "cell_type" : "code", 6 | "execution_count": null, 7 | "outputs": [], 8 | "metadata": {}, 9 | "source": [ "import $ivy.`org.vegas-viz::vegas:0.3.9`\n" ] 10 | } 11 | , 12 | { 13 | "cell_type" : "code", 14 | "execution_count": null, 15 | "outputs": [], 16 | "metadata": {}, 17 | "source": [ "import vegas._\n","import vegas.data.External._\n" ] 18 | } 19 | , 20 | { 21 | "cell_type" : "markdown", 22 | "metadata": {}, 23 | "source": [ "# A simple bar chart with embedded data." ] 24 | }, 25 | 26 | { 27 | "cell_type" : "code", 28 | "execution_count": null, 29 | "outputs": [], 30 | "metadata": {}, 31 | "source": [ "Vegas(\"A simple bar chart with embedded data.\").\n"," withData(Seq(\n"," Map(\"a\" -> \"A\", \"b\" -> 28), Map(\"a\" -> \"B\", \"b\" -> 55), Map(\"a\" -> \"C\", \"b\" -> 43),\n"," Map(\"a\" -> \"D\", \"b\" -> 91), Map(\"a\" -> \"E\", \"b\" -> 81), Map(\"a\" -> \"F\", \"b\" -> 53),\n"," Map(\"a\" -> \"G\", \"b\" -> 19), Map(\"a\" -> \"H\", \"b\" -> 87), Map(\"a\" -> \"I\", \"b\" -> 52)\n"," )).\n"," encodeX(\"a\", Ordinal).\n"," encodeY(\"b\", Quantitative).\n"," mark(Bar).\n"," show\n" ] 32 | } 33 | , 34 | { 35 | "cell_type" : "markdown", 36 | "metadata": {}, 37 | "source": [ "# A bar chart showing the US population distribution of age groups in 2000." ] 38 | }, 39 | 40 | { 41 | "cell_type" : "code", 42 | "execution_count": null, 43 | "outputs": [], 44 | "metadata": {}, 45 | "source": [ "Vegas(\"A bar chart showing the US population distribution of age groups in 2000.\").\n"," withURL(Population).\n"," mark(Bar).\n"," filter(\"datum.year == 2000\").\n"," encodeY(\"age\", Ordinal, scale=Scale(bandSize=17)).\n"," encodeX(\"people\", Quantitative, aggregate=AggOps.Sum, axis=Axis(title=\"population\")).\n"," show\n" ] 46 | } 47 | , 48 | { 49 | "cell_type" : "code", 50 | "execution_count": null, 51 | "outputs": [], 52 | "metadata": {}, 53 | "source": [ "Vegas().\n"," withURL(Population).\n"," mark(Bar).\n"," addTransformCalculation(\"gender\", \"\"\"datum.sex == 2 ? \"Female\" : \"Male\"\"\"\").\n"," filter(\"datum.year == 2000\").\n"," encodeColumn(\"age\", Ord, scale=Scale(padding=4.0), axis=Axis(orient=Orient.Bottom, axisWidth=1.0, offset= -8.0)).\n"," encodeY(\"people\", Quantitative, aggregate=AggOps.Sum, axis=Axis(title=\"population\", grid=false)).\n"," encodeX(\"gender\", Nominal, scale=Scale(bandSize = 6.0), hideAxis=true).\n"," encodeColor(\"gender\", Nominal, scale=Scale(rangeNominals=List(\"#EA98D2\", \"#659CCA\"))).\n"," configFacet(cell=CellConfig(strokeWidth = 0)).\n"," show\n" ] 54 | } 55 | , 56 | { 57 | "cell_type" : "code", 58 | "execution_count": null, 59 | "outputs": [], 60 | "metadata": {}, 61 | "source": [ "Vegas().\n"," withURL(Unemployment).\n"," mark(Area).\n"," encodeX(\"date\", Temp, timeUnit=TimeUnit.Yearmonth, scale=Scale(nice=Nice.Month),\n"," axis=Axis(axisWidth=0, format=\"%Y\", labelAngle=0)).\n"," encodeY(\"count\", Quantitative, aggregate=AggOps.Sum).\n"," configCell(width=300, height=200).\n"," show\n" ] 62 | } 63 | , 64 | { 65 | "cell_type" : "code", 66 | "execution_count": null, 67 | "outputs": [], 68 | "metadata": {}, 69 | "source": [ "Vegas().\n"," withURL(Population).\n"," filter(\"datum.year == 2000\").\n"," addTransform(\"gender\", \"datum.sex == 2 ? \\\"Female\\\" : \\\"Male\\\"\").\n"," mark(Bar).\n"," encodeY(\"people\", Quant, AggOps.Sum, axis=Axis(title=\"population\")).\n"," encodeX(\"age\", Ord, scale=Scale(bandSize= 17)).\n"," encodeColor(\"gender\", Nominal, scale=Scale(rangeNominals=List(\"#EA98D2\", \"#659CCA\"))).\n"," configMark(stacked=StackOffset.Normalize).\n"," show\n" ] 70 | } 71 | , 72 | { 73 | "cell_type" : "markdown", 74 | "metadata": {}, 75 | "source": [ "# A trellis scatterplot showing Horsepower and Miles per gallons, faceted by binned values of Acceleration." ] 76 | }, 77 | 78 | { 79 | "cell_type" : "code", 80 | "execution_count": null, 81 | "outputs": [], 82 | "metadata": {}, 83 | "source": [ "Vegas(\"A trellis scatterplot showing Horsepower and Miles per gallons, faceted by binned values of Acceleration.\").\n"," withURL(Cars).\n"," mark(Point).\n"," encodeX(\"Horsepower\", Quantitative).\n"," encodeY(\"Miles_per_Gallon\", Quantitative).\n"," encodeRow(\"Acceleration\", Quantitative, enableBin=true).\n"," show\n" ] 84 | } 85 | , 86 | { 87 | "cell_type" : "code", 88 | "execution_count": null, 89 | "outputs": [], 90 | "metadata": {}, 91 | "source": [ "Vegas().\n"," withURL(Movies).\n"," mark(Point).\n"," encodeX(\"IMDB_Rating\", Quantitative, bin=Bin(maxbins=10.0)).\n"," encodeY(\"Rotten_Tomatoes_Rating\", Quantitative, bin=Bin(maxbins=10.0)).\n"," encodeSize(aggregate=AggOps.Count, field=\"*\", dataType=Quantitative).\n"," show\n" ] 92 | } 93 | , 94 | { 95 | "cell_type" : "code", 96 | "execution_count": null, 97 | "outputs": [], 98 | "metadata": {}, 99 | "source": [ "Vegas().\n"," withURL(Cars).\n"," mark(Point).\n"," encodeX(\"Horsepower\", Quantitative).\n"," encodeY(\"Miles_per_Gallon\", Quantitative).\n"," encodeColor(field=\"Origin\", dataType=Nominal).\n"," show\n" ] 100 | } 101 | , 102 | { 103 | "cell_type" : "markdown", 104 | "metadata": {}, 105 | "source": [ "# A scatterplot showing horsepower and miles per gallons with binned acceleration on color." ] 106 | }, 107 | 108 | { 109 | "cell_type" : "code", 110 | "execution_count": null, 111 | "outputs": [], 112 | "metadata": {}, 113 | "source": [ "Vegas(\"A scatterplot showing horsepower and miles per gallons with binned acceleration on color.\").\n"," withURL(Cars).\n"," mark(Point).\n"," encodeX(\"Horsepower\", Quantitative).\n"," encodeY(\"Miles_per_Gallon\", Quantitative).\n"," encodeColor(field=\"Acceleration\", dataType=Quantitative, bin=Bin(maxbins=5.0)).\n"," show\n" ] 114 | } 115 | , 116 | { 117 | "cell_type" : "code", 118 | "execution_count": null, 119 | "outputs": [], 120 | "metadata": {}, 121 | "source": [ "Vegas().\n"," withURL(Cars).\n"," mark(Area).\n"," encodeX(\"Acceleration\", Quantitative, bin=Bin()).\n"," encodeY(\"Horsepower\", Quantitative, AggOps.Mean, enableBin=false).\n"," encodeColor(field=\"Cylinders\", dataType=Nominal).\n"," show\n" ] 122 | } 123 | , 124 | { 125 | "cell_type" : "markdown", 126 | "metadata": {}, 127 | "source": [ "# 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." ] 128 | }, 129 | 130 | { 131 | "cell_type" : "code", 132 | "execution_count": null, 133 | "outputs": [], 134 | "metadata": {}, 135 | "source": [ "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.\").\n"," withURL(Barley).\n"," mark(Point).\n"," encodeRow(\"site\", Ordinal).\n"," encodeX(\"yield\", Quantitative, aggregate=AggOps.Mean).\n"," encodeY(\"variety\", Ordinal, sortField=Sort(\"yield\", AggOps.Mean), scale=Scale(bandSize = 12.0)).\n"," encodeColor(field=\"year\", dataType=Nominal).\n"," show\n" ] 136 | } 137 | , 138 | { 139 | "cell_type" : "markdown", 140 | "metadata": {}, 141 | "source": [ "# A scatterplot with custom star shapes." ] 142 | }, 143 | 144 | { 145 | "cell_type" : "code", 146 | "execution_count": null, 147 | "outputs": [], 148 | "metadata": {}, 149 | "source": [ "Vegas(\"A scatterplot with custom star shapes.\").\n"," withURL(Cars).\n"," mark(Point).\n"," encodeX(\"Horsepower\", Quant).\n"," encodeY(\"Miles_per_Gallon\", Quant).\n"," encodeColor(\"Cylinders\", Nom).\n"," encodeSize(\"Weight_in_lbs\", Quant).\n"," 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\").\n"," show\n" ] 150 | } 151 | , 152 | { 153 | "cell_type" : "markdown", 154 | "metadata": {}, 155 | "source": [ "# A scatterplot showing average horsepower and displacement for cars from different origins." ] 156 | }, 157 | 158 | { 159 | "cell_type" : "code", 160 | "execution_count": null, 161 | "outputs": [], 162 | "metadata": {}, 163 | "source": [ "Vegas(\"A scatterplot showing average horsepower and displacement for cars from different origins.\").\n"," withURL(Cars).\n"," mark(Point).\n"," encodeX(\"Horsepower\", Quant, AggOps.Mean).\n"," encodeY(\"Displacement\", Quant, AggOps.Mean).\n"," encodeDetail(\"Origin\").\n"," show\n" ] 164 | } 165 | , 166 | { 167 | "cell_type" : "markdown", 168 | "metadata": {}, 169 | "source": [ "# Stock prices of 5 Tech Companies Over Time." ] 170 | }, 171 | 172 | { 173 | "cell_type" : "code", 174 | "execution_count": null, 175 | "outputs": [], 176 | "metadata": {}, 177 | "source": [ "Vegas(\"Stock prices of 5 Tech Companies Over Time.\").\n"," withURL(Stocks, formatType = DataFormat.Csv).\n"," mark(Line).\n"," encodeX(\"date\", Temp).\n"," encodeY(\"price\", Quant).\n"," encodeDetailFields(Field(field=\"symbol\", dataType=Nominal)).\n"," show\n" ] 178 | } 179 | , 180 | { 181 | "cell_type" : "markdown", 182 | "metadata": {}, 183 | "source": [ "# Plot with hard-coded size value" ] 184 | }, 185 | 186 | { 187 | "cell_type" : "code", 188 | "execution_count": null, 189 | "outputs": [], 190 | "metadata": {}, 191 | "source": [ "Vegas(\"Plot with hard-coded size value\").\n"," withURL(Cars).\n"," mark(Circle).\n"," encodeY(\"Horsepower\", Quantitative).\n"," encodeX(\"Miles_per_Gallon\", Quantitative).\n"," encodeSize(value=201L).\n"," show\n" ] 192 | } 193 | , 194 | { 195 | "cell_type" : "markdown", 196 | "metadata": {}, 197 | "source": [ "# Plots both mean and IQR as a background layer" ] 198 | }, 199 | 200 | { 201 | "cell_type" : "code", 202 | "execution_count": null, 203 | "outputs": [], 204 | "metadata": {}, 205 | "source": [ "Vegas.layered(\"Plots both mean and IQR as a background layer\").\n"," withURL(Population).\n"," withLayers(\n"," Layer().\n"," mark(Line).\n"," encodeX(\"age\", Ordinal).\n"," encodeY(\"people\", aggregate=AggOps.Mean),\n"," Layer().\n"," mark(Area).\n"," encodeX(\"age\", Ordinal).\n"," encodeY(\"people\", aggregate=AggOps.Q1).\n"," encodeY2(\"people\", aggregate=AggOps.Q3)\n"," ).\n"," show\n" ] 206 | } 207 | , 208 | { 209 | "cell_type" : "markdown", 210 | "metadata": {}, 211 | "source": [ "# Plot with legend on the left and a different title " ] 212 | }, 213 | 214 | { 215 | "cell_type" : "code", 216 | "execution_count": null, 217 | "outputs": [], 218 | "metadata": {}, 219 | "source": [ "Vegas(\"Plot with legend on the left and a different title \").\n"," withURL(Cars).\n"," mark(Point).\n"," encodeY(\"Horsepower\", Quantitative).\n"," encodeX(\"Miles_per_Gallon\", Quantitative).\n"," encodeColor(field=\"Origin\", dataType=Nominal, legend=Legend(orient = \"left\", title=\"Place of Origin\" )).\n"," encodeShape(field=\"Origin\", dataType=Nominal, legend=Legend(orient = \"left\", title=\"Place of Origin\",\n"," titleColor=\"red\")).\n"," show\n" ] 220 | } 221 | , 222 | { 223 | "cell_type" : "markdown", 224 | "metadata": {}, 225 | "source": [ "# Plot to show Binning options" ] 226 | }, 227 | 228 | { 229 | "cell_type" : "code", 230 | "execution_count": null, 231 | "outputs": [], 232 | "metadata": {}, 233 | "source": [ "Vegas(\"Plot to show Binning options\").\n"," withURL(Movies).\n"," mark(Bar).\n"," encodeX(\"IMDB_Rating\", Quantitative, bin=Bin(step=2.0, maxbins=3.0)).\n"," encodeY(field=\"*\", Quantitative, aggregate=AggOps.Count).\n"," show\n" ] 234 | } 235 | , 236 | { 237 | "cell_type" : "markdown", 238 | "metadata": {}, 239 | "source": [ "# Plot to show Binning options" ] 240 | }, 241 | 242 | { 243 | "cell_type" : "code", 244 | "execution_count": null, 245 | "outputs": [], 246 | "metadata": {}, 247 | "source": [ "Vegas(\"Plot to show Binning options\").\n"," withURL(Movies).\n"," mark(Bar).\n"," encodeX(\"Worldwide_Gross\", Quant, bin=Bin(maxbins=20.0), sortOrder=SortOrder.Desc).\n"," encodeY(field=\"*\", Quant, aggregate=AggOps.Count).\n"," show\n" ] 248 | } 249 | , 250 | { 251 | "cell_type" : "markdown", 252 | "metadata": {}, 253 | "source": [ "# Plot to show usage of encodeText" ] 254 | }, 255 | 256 | { 257 | "cell_type" : "code", 258 | "execution_count": null, 259 | "outputs": [], 260 | "metadata": {}, 261 | "source": [ "Vegas(\"Plot to show usage of encodeText\").\n"," withURL(Cars).\n"," addTransform(\"OriginInitial\", \"datum.Origin[0]\").\n"," mark(Text).\n"," encodeX(\"Horsepower\", Quantitative).\n"," encodeY(\"Miles_per_Gallon\", Quantitative).\n"," encodeColor(field=\"Origin\", dataType= Nominal).\n"," encodeText(field=\"OriginInitial\", dataType= Nominal).\n"," show\n" ] 262 | } 263 | ], 264 | "metadata": { 265 | "kernelspec": { 266 | "display_name": "Scala 2.11", 267 | "language": "scala211", 268 | "name": "scala211" 269 | }, 270 | "language_info": { 271 | "codemirror_mode": "text/x-scala", 272 | "file_extension": ".scala", 273 | "mimetype": "text/x-scala", 274 | "name": "scala211", 275 | "pygments_lexer": "scala", 276 | "version": "2.11.6" 277 | } 278 | }, 279 | "nbformat": 4, 280 | "nbformat_minor": 0 281 | } 282 | -------------------------------------------------------------------------------- /notebooks/zeppelin_example.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "name": "Vegas Examples", 4 | "angularObjects": {}, 5 | "paragraphs": [ 6 | { 7 | "config": { "title": false }, 8 | "title": "", 9 | "text": "\n%dep\nz.load(\"org.vegas-viz:vegas-spark_2.11:0.3.9\")" 10 | } 11 | , 12 | { 13 | "config": { "title": false }, 14 | "title": "", 15 | "text": "\nimport vegas._\nimport vegas.data.External._" 16 | } 17 | , 18 | { 19 | "config": { "title": true }, 20 | "title": "A simple bar chart with embedded data.", 21 | "text": "Vegas(\"A simple bar chart with embedded data.\").\n withData(Seq(\n Map(\"a\" -> \"A\", \"b\" -> 28), Map(\"a\" -> \"B\", \"b\" -> 55), Map(\"a\" -> \"C\", \"b\" -> 43),\n Map(\"a\" -> \"D\", \"b\" -> 91), Map(\"a\" -> \"E\", \"b\" -> 81), Map(\"a\" -> \"F\", \"b\" -> 53),\n Map(\"a\" -> \"G\", \"b\" -> 19), Map(\"a\" -> \"H\", \"b\" -> 87), Map(\"a\" -> \"I\", \"b\" -> 52)\n )).\n encodeX(\"a\", Ordinal).\n encodeY(\"b\", Quantitative).\n mark(Bar).\n show" 22 | } 23 | , 24 | { 25 | "config": { "title": true }, 26 | "title": "A bar chart showing the US population distribution of age groups in 2000.", 27 | "text": "Vegas(\"A bar chart showing the US population distribution of age groups in 2000.\").\n withURL(Population).\n mark(Bar).\n filter(\"datum.year == 2000\").\n encodeY(\"age\", Ordinal, scale=Scale(bandSize=17)).\n encodeX(\"people\", Quantitative, aggregate=AggOps.Sum, axis=Axis(title=\"population\")).\n show" 28 | } 29 | , 30 | { 31 | "config": { "title": false }, 32 | "title": "", 33 | "text": "Vegas().\n withURL(Population).\n mark(Bar).\n addTransformCalculation(\"gender\", \"\"\"datum.sex == 2 ? \"Female\" : \"Male\"\"\"\").\n filter(\"datum.year == 2000\").\n encodeColumn(\"age\", Ord, scale=Scale(padding=4.0), axis=Axis(orient=Orient.Bottom, axisWidth=1.0, offset= -8.0)).\n encodeY(\"people\", Quantitative, aggregate=AggOps.Sum, axis=Axis(title=\"population\", grid=false)).\n encodeX(\"gender\", Nominal, scale=Scale(bandSize = 6.0), hideAxis=true).\n encodeColor(\"gender\", Nominal, scale=Scale(rangeNominals=List(\"#EA98D2\", \"#659CCA\"))).\n configFacet(cell=CellConfig(strokeWidth = 0)).\n show" 34 | } 35 | , 36 | { 37 | "config": { "title": false }, 38 | "title": "", 39 | "text": "Vegas().\n withURL(Unemployment).\n mark(Area).\n encodeX(\"date\", Temp, timeUnit=TimeUnit.Yearmonth, scale=Scale(nice=Nice.Month),\n axis=Axis(axisWidth=0, format=\"%Y\", labelAngle=0)).\n encodeY(\"count\", Quantitative, aggregate=AggOps.Sum).\n configCell(width=300, height=200).\n show" 40 | } 41 | , 42 | { 43 | "config": { "title": false }, 44 | "title": "", 45 | "text": "Vegas().\n withURL(Population).\n filter(\"datum.year == 2000\").\n addTransform(\"gender\", \"datum.sex == 2 ? \\\"Female\\\" : \\\"Male\\\"\").\n mark(Bar).\n encodeY(\"people\", Quant, AggOps.Sum, axis=Axis(title=\"population\")).\n encodeX(\"age\", Ord, scale=Scale(bandSize= 17)).\n encodeColor(\"gender\", Nominal, scale=Scale(rangeNominals=List(\"#EA98D2\", \"#659CCA\"))).\n configMark(stacked=StackOffset.Normalize).\n show" 46 | } 47 | , 48 | { 49 | "config": { "title": true }, 50 | "title": "A trellis scatterplot showing Horsepower and Miles per gallons, faceted by binned values of Acceleration.", 51 | "text": "Vegas(\"A trellis scatterplot showing Horsepower and Miles per gallons, faceted by binned values of Acceleration.\").\n withURL(Cars).\n mark(Point).\n encodeX(\"Horsepower\", Quantitative).\n encodeY(\"Miles_per_Gallon\", Quantitative).\n encodeRow(\"Acceleration\", Quantitative, enableBin=true).\n show" 52 | } 53 | , 54 | { 55 | "config": { "title": false }, 56 | "title": "", 57 | "text": "Vegas().\n withURL(Movies).\n mark(Point).\n encodeX(\"IMDB_Rating\", Quantitative, bin=Bin(maxbins=10.0)).\n encodeY(\"Rotten_Tomatoes_Rating\", Quantitative, bin=Bin(maxbins=10.0)).\n encodeSize(aggregate=AggOps.Count, field=\"*\", dataType=Quantitative).\n show" 58 | } 59 | , 60 | { 61 | "config": { "title": false }, 62 | "title": "", 63 | "text": "Vegas().\n withURL(Cars).\n mark(Point).\n encodeX(\"Horsepower\", Quantitative).\n encodeY(\"Miles_per_Gallon\", Quantitative).\n encodeColor(field=\"Origin\", dataType=Nominal).\n show" 64 | } 65 | , 66 | { 67 | "config": { "title": true }, 68 | "title": "A scatterplot showing horsepower and miles per gallons with binned acceleration on color.", 69 | "text": "Vegas(\"A scatterplot showing horsepower and miles per gallons with binned acceleration on color.\").\n withURL(Cars).\n mark(Point).\n encodeX(\"Horsepower\", Quantitative).\n encodeY(\"Miles_per_Gallon\", Quantitative).\n encodeColor(field=\"Acceleration\", dataType=Quantitative, bin=Bin(maxbins=5.0)).\n show" 70 | } 71 | , 72 | { 73 | "config": { "title": false }, 74 | "title": "", 75 | "text": "Vegas().\n withURL(Cars).\n mark(Area).\n encodeX(\"Acceleration\", Quantitative, bin=Bin()).\n encodeY(\"Horsepower\", Quantitative, AggOps.Mean, enableBin=false).\n encodeColor(field=\"Cylinders\", dataType=Nominal).\n show" 76 | } 77 | , 78 | { 79 | "config": { "title": true }, 80 | "title": "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.", 81 | "text": "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.\").\n withURL(Barley).\n mark(Point).\n encodeRow(\"site\", Ordinal).\n encodeX(\"yield\", Quantitative, aggregate=AggOps.Mean).\n encodeY(\"variety\", Ordinal, sortField=Sort(\"yield\", AggOps.Mean), scale=Scale(bandSize = 12.0)).\n encodeColor(field=\"year\", dataType=Nominal).\n show" 82 | } 83 | , 84 | { 85 | "config": { "title": true }, 86 | "title": "A scatterplot with custom star shapes.", 87 | "text": "Vegas(\"A scatterplot with custom star shapes.\").\n withURL(Cars).\n mark(Point).\n encodeX(\"Horsepower\", Quant).\n encodeY(\"Miles_per_Gallon\", Quant).\n encodeColor(\"Cylinders\", Nom).\n encodeSize(\"Weight_in_lbs\", Quant).\n 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\").\n show" 88 | } 89 | , 90 | { 91 | "config": { "title": true }, 92 | "title": "A scatterplot showing average horsepower and displacement for cars from different origins.", 93 | "text": "Vegas(\"A scatterplot showing average horsepower and displacement for cars from different origins.\").\n withURL(Cars).\n mark(Point).\n encodeX(\"Horsepower\", Quant, AggOps.Mean).\n encodeY(\"Displacement\", Quant, AggOps.Mean).\n encodeDetail(\"Origin\").\n show" 94 | } 95 | , 96 | { 97 | "config": { "title": true }, 98 | "title": "Stock prices of 5 Tech Companies Over Time.", 99 | "text": "Vegas(\"Stock prices of 5 Tech Companies Over Time.\").\n withURL(Stocks, formatType = DataFormat.Csv).\n mark(Line).\n encodeX(\"date\", Temp).\n encodeY(\"price\", Quant).\n encodeDetailFields(Field(field=\"symbol\", dataType=Nominal)).\n show" 100 | } 101 | , 102 | { 103 | "config": { "title": true }, 104 | "title": "Plot with hard-coded size value", 105 | "text": "Vegas(\"Plot with hard-coded size value\").\n withURL(Cars).\n mark(Circle).\n encodeY(\"Horsepower\", Quantitative).\n encodeX(\"Miles_per_Gallon\", Quantitative).\n encodeSize(value=201L).\n show" 106 | } 107 | , 108 | { 109 | "config": { "title": true }, 110 | "title": "Plots both mean and IQR as a background layer", 111 | "text": "Vegas.layered(\"Plots both mean and IQR as a background layer\").\n withURL(Population).\n withLayers(\n Layer().\n mark(Line).\n encodeX(\"age\", Ordinal).\n encodeY(\"people\", aggregate=AggOps.Mean),\n Layer().\n mark(Area).\n encodeX(\"age\", Ordinal).\n encodeY(\"people\", aggregate=AggOps.Q1).\n encodeY2(\"people\", aggregate=AggOps.Q3)\n ).\n show" 112 | } 113 | , 114 | { 115 | "config": { "title": true }, 116 | "title": "Plot with legend on the left and a different title ", 117 | "text": "Vegas(\"Plot with legend on the left and a different title \").\n withURL(Cars).\n mark(Point).\n encodeY(\"Horsepower\", Quantitative).\n encodeX(\"Miles_per_Gallon\", Quantitative).\n encodeColor(field=\"Origin\", dataType=Nominal, legend=Legend(orient = \"left\", title=\"Place of Origin\" )).\n encodeShape(field=\"Origin\", dataType=Nominal, legend=Legend(orient = \"left\", title=\"Place of Origin\",\n titleColor=\"red\")).\n show" 118 | } 119 | , 120 | { 121 | "config": { "title": true }, 122 | "title": "Plot to show Binning options", 123 | "text": "Vegas(\"Plot to show Binning options\").\n withURL(Movies).\n mark(Bar).\n encodeX(\"IMDB_Rating\", Quantitative, bin=Bin(step=2.0, maxbins=3.0)).\n encodeY(field=\"*\", Quantitative, aggregate=AggOps.Count).\n show" 124 | } 125 | , 126 | { 127 | "config": { "title": true }, 128 | "title": "Plot to show Binning options", 129 | "text": "Vegas(\"Plot to show Binning options\").\n withURL(Movies).\n mark(Bar).\n encodeX(\"Worldwide_Gross\", Quant, bin=Bin(maxbins=20.0), sortOrder=SortOrder.Desc).\n encodeY(field=\"*\", Quant, aggregate=AggOps.Count).\n show" 130 | } 131 | , 132 | { 133 | "config": { "title": true }, 134 | "title": "Plot to show usage of encodeText", 135 | "text": "Vegas(\"Plot to show usage of encodeText\").\n withURL(Cars).\n addTransform(\"OriginInitial\", \"datum.Origin[0]\").\n mark(Text).\n encodeX(\"Horsepower\", Quantitative).\n encodeY(\"Miles_per_Gallon\", Quantitative).\n encodeColor(field=\"Origin\", dataType= Nominal).\n encodeText(field=\"OriginInitial\", dataType= Nominal).\n show" 136 | } 137 | ] 138 | } 139 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.3.0 2 | 3 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn 2 | 3 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10") 4 | 5 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.0") 6 | 7 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") 8 | 9 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.1") 10 | 11 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.7") 12 | -------------------------------------------------------------------------------- /spark/src/main/scala/vegas/sparkExt/package.scala: -------------------------------------------------------------------------------- 1 | package vegas 2 | 3 | import org.apache.spark.sql.DataFrame 4 | import vegas.DSL.DataDSL 5 | 6 | package object sparkExt { 7 | 8 | val DefaultLimit = 10000 9 | 10 | implicit class VegasSpark[T](val specBuilder: DataDSL[T]) { 11 | 12 | def withDataFrame(df: DataFrame, limit: Int = DefaultLimit): T = { 13 | val columns: Array[String] = df.columns 14 | val count: Double = df.count 15 | val data = { 16 | if (count >= limit) df.sample(false, limit / count).collect() else df.collect() 17 | }.map { row => 18 | columns.zipWithIndex.map { case (name: String, index: Int) => 19 | // We should be able to pass in the required columns into here, so we don't need 20 | // to create a map containing all the columns. 21 | val value = row.get(index) 22 | name -> value 23 | }.toMap 24 | } 25 | specBuilder.withData(data) 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /spark/src/test/scala/vegas/sparkExt/SparkExtSpec.scala: -------------------------------------------------------------------------------- 1 | package vegas.sparkExt 2 | 3 | import org.apache.spark.sql._ 4 | import org.apache.spark.sql.types.{DoubleType, IntegerType, StructField, StructType} 5 | import org.scalatest.{BeforeAndAfterAll, FlatSpec, Matchers} 6 | import vegas._ 7 | import vegas.sparkExt.SparkExtSpec._ 8 | import vegas.spec.Spec.Data.Values 9 | 10 | /** 11 | * Created by DB Tsai on 10/17/16. 12 | */ 13 | class SparkExtSpec extends FlatSpec with BeforeAndAfterAll with Matchers { 14 | import vegas.util.Time._ 15 | 16 | @transient var sparkSession: SparkSession = _ 17 | 18 | var largeDataDF: DataFrame = _ 19 | var smallDataDF: DataFrame = _ 20 | var complexDataDF: DataFrame = _ 21 | 22 | override def beforeAll() { 23 | sparkSession = SparkSession.builder 24 | .master("local[*]") 25 | .appName("test") 26 | .config("spark.ui.enabled", false) 27 | .getOrCreate() 28 | 29 | largeDataDF = sparkSession.sqlContext.createDataFrame( 30 | sparkSession.sparkContext.parallelize((1 to 20000).map { i => 31 | val x = i / 20000.0 32 | val y = 1.5 * x + 3 33 | XYPair(x, y) 34 | })) 35 | 36 | smallDataDF = sparkSession.sqlContext.createDataFrame( 37 | sparkSession.sparkContext.parallelize((1 to 235).map { i => 38 | val x = i / 235.0 39 | val y = 1.5 * x + 3 40 | XYPair(x, y) 41 | })) 42 | 43 | complexDataDF = sparkSession.sqlContext.createDataFrame( 44 | sparkSession.sparkContext.parallelize(Seq( 45 | Person("Bob", 35, None, Some(true), None, mkSqlTimestamp(1984, 12, 25, 23, 59, 0)), 46 | Person("Alice", 12, Some(1.12), None, Some(mkSqlDate(2015, 12, 25)), mkSqlTimestamp(1984, 12, 25, 23, 59, 0)) 47 | ))) 48 | 49 | super.beforeAll() 50 | } 51 | 52 | override def afterAll() { 53 | if (sparkSession != null) sparkSession.stop() 54 | super.afterAll() 55 | } 56 | 57 | "With large data as input, the result" should "be sampled to ~sparkExt.DefaultLimit number of records" in { 58 | val data = Vegas().withDataFrame(largeDataDF).spec.data.get.values.get 59 | assert((data.size - sparkExt.DefaultLimit) / sparkExt.DefaultLimit < 0.05, "data.size should be around 1k") 60 | 61 | data.foreach { case Values(point: Map[String, Double] @unchecked) => 62 | val x = point("x") 63 | val y = point("y") 64 | y shouldEqual 1.5 * x + 3 65 | } 66 | } 67 | 68 | "With small data as input, the result" should "have all the input records" in { 69 | val data = Vegas().withDataFrame(smallDataDF).spec.data.get.values.get 70 | data.size shouldEqual 235 71 | 72 | data.map { case Values(point: Map[String, Double] @unchecked) => 73 | XYPair(point("x"), point("y")) 74 | }.zip(1 to 235).foreach { case (pair, index) => 75 | pair.x shouldEqual index / 235.0 76 | pair.y shouldEqual 1.5 * pair.x + 3 77 | } 78 | } 79 | 80 | "Data with nulls" should "preserve them" in { 81 | val nullDataDF = sparkSession.sqlContext.createDataFrame( 82 | sparkSession.sparkContext.parallelize(Seq(Row(1, null), Row(null, 3.14))), 83 | StructType( StructField("a", IntegerType, true) :: StructField("b", DoubleType, true) :: Nil ) 84 | ) 85 | val data = Vegas().withDataFrame(nullDataDF).spec.data.get.values.get 86 | 87 | data.map { case Values(d: Map[String, Any] @unchecked) => 88 | (d("a"), d("b")) 89 | } should === (Seq((1, null), (null, 3.14))) 90 | } 91 | 92 | "With complex data as input" should "encode it to the right types" in { 93 | val data = Vegas().withDataFrame(complexDataDF).spec.data.get.values.get 94 | 95 | data.map { case Values(d: Map[String, Any] @unchecked) => 96 | (d("name"), d("age"), d("height"), d("employee"), d("birthday"), d("createdAt")) 97 | } should === (Seq( 98 | ("Bob", 35L, null, true, null, "1984-12-25T23:59:00"), 99 | ("Alice", 12L, 1.12, null, "2015-12-25", "1984-12-25T23:59:00") 100 | )) 101 | } 102 | 103 | } 104 | 105 | object SparkExtSpec { 106 | case class XYPair(x: Double, y: Double) 107 | case class Person(name: String, age: Long, height: Option[Double], employee: Option[Boolean], 108 | birthday: Option[java.sql.Date], createdAt: java.sql.Timestamp) 109 | } 110 | -------------------------------------------------------------------------------- /spec/src/main/scala/vegas/spec/Spec.scala: -------------------------------------------------------------------------------- 1 | package vegas.spec 2 | 3 | import argus.macros.fromSchemaURL 4 | 5 | @fromSchemaURL( 6 | url = "https://vega.github.io/schema/vega-lite/v1.2.0.json", 7 | name = "Vega", 8 | outPath = Some("spec/target/scala-2.12/Spec.scala") 9 | ) 10 | object Spec 11 | -------------------------------------------------------------------------------- /spec/src/test/scala/FromSchemaSpec.scala: -------------------------------------------------------------------------------- 1 | import argus.macros._ 2 | import org.scalatest.{FlatSpec, Matchers} 3 | 4 | 5 | class FromSchemaAnnotationSpec extends FlatSpec with Matchers { 6 | 7 | // FIXME: https://github.com/vegas-viz/Vegas/issues/93 8 | "fromSchemaResource" should "be compiled" ignore { 9 | """ 10 | @fromSchemaResource( 11 | path = "/spec/src/main/resources/vega-lite-schema.json", 12 | name = "Vega", 13 | outPath = Some("spec/target/scala-2.11/SpecFromSchemaResource.scala") 14 | ) 15 | object SpecFromSchemaResource 16 | """ should compile 17 | } 18 | 19 | "fromSchemaURL" should "be compiled" in { 20 | """ 21 | @fromSchemaURL( 22 | url = "https://vega.github.io/schema/vega-lite/v1.2.0.json", 23 | name = "Vega", 24 | outPath = Some("spec/target/scala-2.11/SpecFromSchemaURL.scala") 25 | ) 26 | object SpecFromSchemaURL 27 | """ should compile 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /version.sbt: -------------------------------------------------------------------------------- 1 | version in ThisBuild := "0.3.12-SNAPSHOT" 2 | --------------------------------------------------------------------------------