Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript Syntax for Vega-Lite (similar to Altair) #2280

Closed
domoritz opened this issue Apr 26, 2017 · 22 comments
Closed

JavaScript Syntax for Vega-Lite (similar to Altair) #2280

domoritz opened this issue Apr 26, 2017 · 22 comments

Comments

@domoritz
Copy link
Member

domoritz commented Apr 26, 2017

Shared on slack by @jheer.

The refereneces are with regard to the Vega-Lite Infovis paper.

/// FIGURE 2
var weather = vl.data()
  .url('data/weather.csv')
  .format('csv');

// Line chart with aggregation
var fig2a = vl.line()
  .data(weather)
  .x(vl.month('date'))
  .y(vl.mean('temp_max'))
  .color(vl.field('location').type(vl.Nominal));
// fig2a.toJSON() --> generate Vega-Lite JSON

// Correlation between wind and temperature
var fig2b = vl.point()
  .data(weather)
  .x(vl.bin('temp_max'))
  .y(vl.bin('wind'))
  .size(vl.count())
  .color(vl.field('location').type(vl.Nominal));

// Stacked bar chart of weather types
var fig2c = vl.bar()
  .data(weather)
  .x(vl.field('location').type(vl.Nominal));
  .y(vl.count())
  .color(vl.field('weather').type(vl.Nominal));

/// FIGURE 3
var seattleWeather = vl.data()
  .url('data/weather.csv')
  .format('csv')
  .filter('datum.location === "Seattle"');

var noGrid = vl.axis().grid(false);

// Dual axis layered chart
var fig3a = vl.layers()
  .add(vl.bar()
    .data(seattleWeather)
    .x(vl.month('date'))
    .y(vl.mean('precipitation').axis(noGrid))
    .color(vl.value('#77b2c7')))
  .add(vl.line()
    .data(seattleWeather)
    .x(vl.month('date'))
    .y(vl.mean('temp_max').axis(noGrid))
    .color(vl.value('#ce323c"}')))
  .resolve(vl.resolve().y('independent'));

var fig3b = vl.vconcat()
  .add(fig2a)
  .add(vl.point()
    .data(weather.copy()
      .filter('datum.precipitation > 0'))
    .x(vl.count())
    .y(vl.field('location').type(vl.Nominal))
    .color(vl.year('date')));

/// FIGURE 4

// Faceted charts
var fig4a = vl.facet(fig2a)
  .column(vl.field('location'));
var fig4a = vl.facet()
  .column(vl.field('location'))
  .spec(fig2a);

// Repeated charts
var fig4b = vl.repeat(fig2a.copy().y(vl.mean(vl.column)))
  .column(['temp_max', 'precipitation']);
var fig4b = vl.repeat()
  .column(['temp_max', 'precipitation'])
  .spec(fig2a.copy().y(vl.mean(vl.column)));

/// FIGURE 5
var cars = vl.data().url('data/cars.json');

var scatter = vl.circle()
  .x(vl.field('Horsepower').type(vl.Q))
  .y(vl.field('MPG').type(vl.Q))
  .color(vl.rule()
    .if('id', vl.field('Origin').type(vl.N))
    .else(vl.value('grey')))
  .size(vl.value(100));

// Highlight a single point on click
var fig5a = scetter.copy()
  .select('id', vl.single());

// Highlight a list of individual points
var fig5b = scatter.copy()
  .select('id', vl.multi().toggle(true));

// "Paintbrush": highlight multiple points on hover
var fig5c = scatter.copy()
  .select('id', vl.multi().on('mouseover').toggle(true));

// Highlight a single Origin
var fig5d = scatter.copy()
  .select('id', vl.single().fields('Origin'));

// Highlight a list of Origins
var fig5e = scatter.copy()
  .select('id', vl.multi().toggle(true).fields('Origin'));

/// FIGURE 6

// Rectangular brush
var fig6a = vl.circle()
  .x(vl.field('Horsepower').type(vl.Q))
  .y(vl.field('MPG').type(vl.Q))
  .color(vl.rule()
    .if('region', vl.field('Origin').type(vl.N))
    .else(vl.value('grey')))
  .size(vl.value(100))
  .select('region', vl.interval());

// Moving the brush
var fig6b = fig6a.copy()
  .select('region', vl.interval().translate(true));

// Single-dimension brush
var fig6c = fig6a.copy()
  .select('region', vl.interval().translate(true).channels('x'));

/// FIGURE 7
var fig7 = fig6a.copy()
  .select('region', vl.interval()
    .on('[mousedown[event.shiftKey], mouseup] > mousemove'))
  .select('grid', vl.interval()
    .scales(true)
    .zoom(true)
    .translate('[mousedown[!event.shiftKey], mouseup] > mousemove'));

/// FIGURE 8

// A Single Brush, and Panning & Zooming in a Scatterplot Matri
var fig8a = vl.repeat()
  .row(['Displacement', 'Miles_per_Gallon'])
  .column(['Displacement', 'Miles_per_Gallon'])
  .spec(vl.circle()
    .data(cars)
    .x(vl.field(vl.column).type(vl.Q))
    .y(vl.field(vl.row).type(vl.Q))
    .color(vl.rule()
      .if('region', vl.field('Origin').type(vl.N))
      .else(vl.value('grey')))
    .size(vl.value(100))
    .select('region', vl.interval()
      .translate(true).zoom(true).resolve('single')
      .on('[mousedown[event.shiftKey], mouseup] > mousemove'))
    .select('grid', vl.interval()
      .scales(true).zoom(true).resolve('single')
      .on('[mousedown[!event.shiftKey], mouseup] > mousemove')));

// Independent Brushes
var fig8b = fig8a.copy();
fig8b.spec().select('region').resolve('independent');
fig8b.spec().select('grid').resolve('independent');

// Unioned Brushes
var fig8c = fig8a.copy();
fig8c.spec().select('region').resolve('union');
fig8c.spec().select('grid').resolve('union');

// Intersected Brushes
var fig8d = fig8a.copy();
fig8d.spec().select('region').resolve('intersect');
fig8d.spec().select('grid').resolve('intersect');

/// FIGURE 9
var stocks = vl.data.url('data/sp500.csv').format('csv');

// Overview + Detail
var fig9a = vl.vconcat([
  vl.area()
    .x(vl.field('date').type(vl.T))
    .y(vl.field('price').type(vl.Q))
    .select('region', vl.interval().channels('x'))
    .height(100),
  vl.area()
    .x(vl.field('date').type(vl.T))
    .y(vl.field('price').type(vl.Q)
         .scale(vl.scale().domain(vl.selection('region'))))
    .height(300)
]);

// Index Chart
var fig9b = vl.layers([
  vl.line()
    .data(stocks.copy()
      .lookup('index', vl.selection('indexPt').keys('symbol'))
      .calculate('field', 'indexed_price')
      .calculate('expr', '(datum.price - datum.index.price) / datum.index.price'))
    .x(vl.field('date').type(vl.Temporal))
    .y(vl.field('indexed_price').type(vl.Quantitative))
    .color(vl.field('symbol').type(vl.Nominal))
    .select('indexPt', vl.single()
      .on('mousemove').fields('date').nearest(true)),
  vl.rule()
    .x(vl.selection('indexPt.date').type(vl.T))
    .color(vl.value('red'))
]);

/// FIGURE 10
var flights = vl.data().url('data/flights-2k.json');

var fig10 = vl.repeat()
  .column(['hour', 'delay', 'distance'])
  .spec(vl.bar()
    .data(flights.copy().filter(vl.selection('region')))
    .x(vl.bin(vl.column))
    .y(vl.count())
    .color(vl.value('steelblue'))
    .select('region', vl.interval()
      .channels('x').resolve('intersect_others')));

/// FIGURE 11

// Single-Point Layered Cross Filtering
var fig11a = vl.repeat()
  .column(['hour', 'delay', 'distance'])
  .spec(vl.layers([
    vl.bar()
      .data(flights)
      .x(vl.bin(vl.column))
      .y(vl.count())
      .color(vl.value('steelblue'))
      .select('selectedBins', vl.single().on('mousemove').channels('x')),
    vl.bar()
      .data(flights.copy().filter(vl.selection('selectedBins')))
      .x(vl.bin(vl.column))
      .y(vl.count())
      .color(vl.value('goldenrod'))
  ]);

// Multi-Point Layered Cross Filtering
var fig11b = vl.repeat()
  .column(['hour', 'delay', 'distance'])
  .spec(vl.layers([
    vl.bar()
      .data(flights)
      .x(vl.bin(vl.column))
      .y(vl.count())
      .color(vl.value('steelblue'))
      .select('selectedBins', vl.multi().on('click').channels('x')),
    vl.bar()
      .data(flights.copy().filter(vl.selection('selectedBins')))
      .x(vl.bin(vl.column))
      .y(vl.count())
      .color(vl.value('goldenrod'))
  ]);

// Continuous Layered Cross Filtering
var fig11b = vl.repeat()
  .column(['hour', 'delay', 'distance'])
  .spec(vl.layers([
    vl.bar()
      .data(flights)
      .x(vl.bin(vl.column))
      .y(vl.count())
      .color(vl.value('steelblue'))
      .select('selectedBins', vl.interval().channels('x')),
    vl.bar()
      .data(flights.copy().filter(vl.selection('selectedBins')))
      .x(vl.bin(vl.column))
      .y(vl.count())
      .color(vl.value('goldenrod'))
  ]);

cc @mbostock

@kanitw kanitw added this to the x.x (Nice to Have) milestone Apr 26, 2017
@mbostock
Copy link

From a type-safety & conciseness perspective, one question I have is whether you can declare typed fields for your dataset symbolically, rather than repeating string identifiers. So for example rather than this:

line.x(vl.field("date").type(vl.Temporal));

You would first declare the typed field like this:

var date = vl.field("date").type(vl.Temporal);

Or in slightly shorter form:

var date = vl.Temporal("date");

And then later use it:

line.x(date);

It looks like this is mostly possible already, but there appear to be a few places where you must refer to a field again by name, rather than by symbol; for example with calculated fields or the repeat operator.

Likewise, could there be a more symbolic approach to derived fields? Like instead of this:

var temp_mean = vl.mean("temp_max");

You could say something like this?

var temp_max = vl.Quantitative("temp_max"),
    temp_mean = vl.mean(temp_max);

Or maybe this?

var temp_max = vl.Quantitative("temp_max"),
    temp_mean = temp_max.mean();

Also, it would be great to “autobox” constant values rather than requiring vl.value. For example, instead of this:

bar.color(vl.value("steelblue"));

You could say this:

bar.color("steelblue");

I don’t know if these things are possible but these are first impressions. Thanks for inviting me to the discussion.

@john-guerra
Copy link
Contributor

Would love to see this happen

@kanitw
Copy link
Member

kanitw commented May 9, 2018

I think it's nice that we have this issue documenting this idea and it could be nice if someone try doing it. However, the JSON syntax is already pretty convenient for Javascript users and they can already get free typing with Typescripts and get free autocompletion like we do in the editor.

Implementing this would require a lot of efforts and even more maintenance. Both @domoritz and I think that it's not worth the efforts for us to do this ourselves. So I will close this for now.

@kanitw kanitw closed this as completed May 9, 2018
@RandomFractals
Copy link
Contributor

https://github.com/gjmcn/to-vega does a great job implementing this idea, and recently has been updated to vega-lite v3 schema

@domoritz
Copy link
Member Author

@kanitw I'd like to revisit this. The issue with just JSON and typescript is that you don't get errors where they are happening (see below). Moreover, Observable will add support for runtime autocomplete and this won't support Typescript. I think there is value in a JS API even if we don't implement it soon.

screen shot 2018-12-21 at 07 27 21

@kanitw
Copy link
Member

kanitw commented Dec 21, 2018

The issue with just JSON and typescript is that you don't get errors where they are happening (see below).

It seems like this would provide better error for TS users as the underline will show if a parameter doesn't matching type. However, in pure JS, you won't get a red underline in your method call either? If so, this might only benefit TS users.

In any case, for small specs like this, the error message for which part is breaking the type isn't too bad. For bigger spec like multi-views, people can declare subpart of the specs and get error specific to each subpart.

Overall, I agree that a TS/JS API (rather than JSON) provide benefits for showing errors, but maybe not so big?

Observable will add support for runtime autocomplete and this won't support Typescript.

This is an important point. However, we better understand how they generate the autocompletion and make sure that adding the JS APIs will really solve the task.

@domoritz
Copy link
Member Author

They will just autocomplete the properties that are available on an object.

@kanitw
Copy link
Member

kanitw commented Dec 22, 2018 via email

@domoritz
Copy link
Member Author

Either. But a function with properties is an object in disguise to me.

@RandomFractals
Copy link
Contributor

related: is this vega lite json schema up to date?

http://json.schemastore.org/vega-lite

I am planning to use it for vl.json validation in the vscode viewer I am working on

https://github.com/RandomFractals/vscode-vega-viewer

@domoritz
Copy link
Member Author

Always use https://github.com/vega/schema.

@domoritz
Copy link
Member Author

You should always just follow the URL in the spec. I don't understand why you need separate validation from what vscode already does without a plugin.

@RandomFractals
Copy link
Contributor

RandomFractals commented Dec 23, 2018

Dom,

It's mostly for the vg/vl json lang. dialect hookup in vscode ext for file matching and validation for me to open vega viewer preview and add explorer/contexts Vega Preview contributes (vscode hookup specific), not for the editor or authoring of that json that is accomplished via default vscode json schema reference, validation and hints as you point out.

that former part is typically done with the following json block in package.json vscode ext, or somewhat along those lines:

    "languages": [
      {
        "id": "json",
        "aliases": ["VegaJSON"],
        "extensions": [".vg.json"]
      },
      {
        "id": "json",
        "aliases": ["VegaLightJSON"],
        "extensions": [".vl.json"]
      }
    ],
    "jsonValidation": [
      {
        "fileMatch": "*.vg.json",
        "url": "https://vega.github.io/schema/vega/v4.json"
      },
      {
        "fileMatch": "*.vl.json",
        "url": "https://vega.github.io/schema/vega-lite/v3.json"
      }
    ]

I am hoping to push an alpha v. for preview soon. I think it will be clearer in context then.

btw, vega embed is no go for that plugin since vscode webview panel dosn't allow iframe sandbox="allow-popus" to open content in browser due to sandboxing, but I'll have options to save as svg or png. I did get an interactive vega chart working in my local prototype, with tooltips etc. unlike that vega-preview plugin. Just finishing up the IDE dressing parts now.

@RandomFractals
Copy link
Contributor

RandomFractals commented Dec 27, 2018

another option would be to generate typed JS and TS apis and JSON spec converters from vega JSON schemas using quicktype lib:

https://github.com/quicktype/quicktype

Worth a look for api driven vega spec creation.

@domoritz
Copy link
Member Author

Quicktype is cool but is there a way to compile ts interfaces to js?

@RandomFractals
Copy link
Contributor

RandomFractals commented Dec 27, 2018

I just created a quick test repo to see what quicktype does:

https://github.com/RandomFractals/vega-lite-api

It does give us quick spec json validation, to/from, and auto-complete for TS, as expected. See main.ts and src/spec.ts.

Not sure if this is worth considering since you already have your types defined in vega-typings repo.

However, it could be a quick way to gen. vega-lite spec api for C#, GO and other langs if you don't have it already and want to create vega-lite spec programmatically on the server side... :)

@domoritz
Copy link
Member Author

I want ts to js, not json to ts or json to js.

@RandomFractals
Copy link
Contributor

RandomFractals commented Dec 30, 2018

You can gen js from ts types, but I think your typings might need some cleanup for that to work with quicktype.

Personally, I am more excited about creating json types interfaces with quicktype for C#, Go and Java, where there is no library for generating vega specs. I've tried it locally, but there are some objects that will need to be cleaned up in your types and schemas.

So, for those reasons, I'd pick the schema json as the one source of truth for vega grammar.

Also, I believe I've seen some issues logged and closed regarding vega types being added to

https://github.com/DefinitelyTyped/DefinitelyTyped

I think it would be benefitial to the dev community to have them posted there to make them more accessible since most JS devs are familiar with how to add types via @types

@domoritz
Copy link
Member Author

Vega comes with types. I deleted them from definitely typed.

@RandomFractals
Copy link
Contributor

ok. getting back to other langs and typed interfaces:

I generated CS vega types from your schema in case you guys want to explore that route later:

https://github.com/RandomFractals/vega-lite-api/blob/master/src/Spec.cs

A simple CS demo app with spec gen on the server side could be an intersting project to explore since you don't have any api or support in CS or other server side langs and real world projects do do that all the time, based on my expi with data viz libs and dashboards.

Thinks about it...

Feel free to open new epic issue for this topic discussion

@gjmcn
Copy link
Contributor

gjmcn commented Jan 9, 2019

I have started a new project: Vizsla, a simple JavaScript API for Vega-Lite.

Observable demo

(My plan was to update and improve to-vega, but so many major changes were required that a new library seemed more appropriate.)

@kanitw
Copy link
Member

kanitw commented Mar 23, 2019

I think there are multiple APIs already. Jeff has also started https://github.com/vega/vega-lite-api.

I think it's time to close this issue. (Feel free to keep commenting here to discuss, but I'll close the issue since there is no actionable item specific to this repository anymore.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants