Giter Club home page Giter Club logo

Comments (6)

manzt avatar manzt commented on May 18, 2024 1

Thanks for all the thoughtful ideas. This is sort of what I had in mind already and have started implementing to some extent.

One of the reasons the partial specification is supported in Gosling (and in Vega-Lite I think) is to prevent code repetition, and my guess is that they do not necessarily support the partial specification since this issue (repetition) can be easily handled by the programmatical approach.

Completely agree. I'm thinking that for now, we don't expose the PartialTrack and instead just work with complete Track's. This will make the JSON larger at the end, but that is OK IMO. If it becomes an issue, we can always write a script to strip out duplicated (unecessary) fields :)

Additionally, thank you for all the examples and explanation. It helps a lot for me to understand gosling's data-model much better. You have actually identified a bug with the current implementation. The class Track should only be able to hold fields for a gosling Track, but the properties API is sneaky because it allows a hook to add additional fields (which) actually turn the Track into a View! For example,

track4 = Track(multivec).encode(
    x=gos.Channel("position:G", axis="top"),
    y="peak:Q",
    row="sample:N",
    color=gos.Channel("sample:N", legend=True),
    tooltip=tooltip,
).properties(
    alignment="overlay",
    tracks=[
        gos.PartialTrack().mark_line(),
        gos.PartialTrack().mark_point().encode(
            size=gos.Channel("peak:Q", range=[0, 2]),
        )
    ],
)

"Works" because the constructor for Track is called with proper arguments and passes validation. The assignment in the .properties method adds these fields to the Track instance, but effectively changes the type from a track to a SingleView. The way that the validation ends up working, this is still valid in the end. But for example if you call Track like below,

gos.Track(
    multivec,
    alignment="overlay",
    tracks=[
        gos.PartialTrack().mark_line(),
        gos.PartialTrack().mark_point().encode(
            size=gos.Channel("peak:Q", range=[0, 2]),
        )
    ],
)

# gosling.schemapi.SchemaValidationError: Invalid specification
#
#        gosling.api.Track->0, validating 'additionalProperties'
#
#        Additional properties are not allowed ('alignment', 'tracks' were unexpected)

It results in an error. The idea with properties is to have a nice shorthand modify the class instance. So technically,

gos.Track(data, ...)

# and 

gos.Track(data).properties(...) 

should be equivalent. The fact that the example still compiled with the Track(data).properties(alignemnt=..., tracks=...) confused me about what the types in gosling actually are. But now I can see clearly what's going on.

Thanks for all your help. I'm goign to work on the API today and will ping you when I have something cleaned up :)

from gos.

manzt avatar manzt commented on May 18, 2024

The challenge here is that gosling.Chart inherits from gosling.schema.Root (and additional "mixin" classes that add short-hands for setting properties / displaying the Root in a notebook). The Root type is different from both SingleView and MultipleViews (adds title, subtitle, description), so I think it's important to establish a "root" type unambiguously.

In general, I'm happy to reconsider the naming, but I do think this "root" type isn't exactly a "View". Similar to gos.Track we could extend the SingleView and MultipleView methods with a .chart mixin:

import gosling as gos

gos.MultipleViews(...).chart(title='Overview and Detail Views')

gos.SingleView(...).chart()

I see that View is defined in https://github.com/gosling-lang/gosling.js/blob/eafc64c22000c2c140fd003aa5980acbb39d7db8/src/core/gosling.schema.ts#L19, but since the type isn't referenced anywhere as a dependency in GoslingSpec, the definition isn't added to generated schema.json. If it were, we could use that type to generate:

gos.View(arrangement='horizontal', views=[overview, detail]).chart()

from gos.

sehilyi avatar sehilyi commented on May 18, 2024

I didn't realize that the View is not directly included in the GoslingSpec. I think I can include View somewhere, e.g., at the root level of GoslingSpec, so that it can be included in the python package as well. The two terms "tracks" and "views" are taken from the taxonomy of genomics visualization, so we probably would need to keep that as is in the package.

My one concern was that the name of Chart() could be confusing to users given that we already have two terms, i.e., "track" and "view", that denote visualizations or sets of visualizations. If we want to keep the "root" type unambiguously, I think we could use another name (e.g., "Root" or something else).

Similar to #12, my initial thought was to somehow combine the View and "root" types and expose only View to users (e.g., when a user defines "title" and/or "subtitle" at the root level, then the package considers this as the "root" type). But, I agree we may want to distinguish the two, and not sure what would be better at this point.

I think one main difference between gosling.js (JSON-based) and the Python package (programmatical way) from the users' perspective is that in the python package users might need to care about the types (e.g., Track, PatialTrack, MultipleViews, SingleView), which can be an extra burden. But, in JSON specs, the compiler determines the types for users. So, in general, I thought it might be better if we expose a small set of types/methods to users by merging very similar types/methods so that users do not necessarily have to memorize a larger number of types/methods.

from gos.

manzt avatar manzt commented on May 18, 2024

If we want to keep the "root" type unambiguously, I think we could use another name (e.g., "Root" or something else).

I see your point, and mostly agree. Perhaps Root is likely a good option. I liked Chart because then there was a nice link between gos.Chart and the Track(...).chart() methods. To me, I thought it was more intuitive than gos.Track().root() for example.

I think one main difference between gosling.js (JSON-based) and the Python package (programmatical way) from the users' perspective is that in the python package users might need to care about the types (e.g., Track, PatialTrack, MultipleViews, SingleView), which can be an extra burden.

The generated python code performs validation, which is why it is difficult to group these classes in the way you suggest. Every generated class derives from SchemaBase and has many utility methods (e.g. to_json, to_dict, __setattr__) as well as a strict constructor. If you try to pass data as arguments that are invalid, the code will raise an Exception.

import gosling as gos

gos.Chart(tracks=[gos.PartialTrack()]) # raises an exception because a Track is required
gos.Chart(tracks=[gos.Track()]) # raises an excpetion because no data/width/height are passed to constructor

The generated classes unambiguously define types, meaning you can't pass and object that is maybe a Track or a PartialTrack into a constructor that requires a Track. Similarly, you cannot pass title or subtitle on a SingleView because these fields are not defined on a single view.

from gos.

sehilyi avatar sehilyi commented on May 18, 2024

Thank you for the clarification! It helped me to understand better the technical aspect.

Since overlaying tracks in Gosling is very similar with layering in Vega-Lite, I wondered how they support layering in the Altair.

Overlaying tracks in Gosling:

{
   alignment: 'overlay',
   data: {...}, // common properties of Track A-B that should be inherited are defined here
   tracks: [
      { /* Track A */ },  // partial spec
      { /* Track B */ }   // partial spec
   ]
}

Overlaying charts in Vega-Lite (link):

{
   data: {...}, // common properties of Chart A-B that should be inherited are defined here
   layer: [
      { /* Chart A */ },  // partial spec
      { /* Chart B */ }   // partial spec
   ]
}

Overlaying charts in Altair:

chartA = alt.Chart(df).encode(...) # Complete spec that requires data, mark, etc
chartB = alt.Chart(df).encode(...) # Complete spec that requires data, mark, etc

alt.layer(chartA, chartB) # or `chartA + chartB`

It looks like Altair does not seem to expose Vega-Lite's partial specification (at least in the docs). One of the reasons the partial specification is supported in Gosling (and in Vega-Lite I think) is to prevent code repetition, and my guess is that they do not necessarily support the partial specification since this issue (repetition) can be easily handled by the programmatical approach. I guess we could consider letting users use Track() consistently if we want to keep fewer types/methods:

overlay_base = gos.Track(data=mv)

bar_base.properties(
    ..., 
    alignment='overlay',
    tracks=[
        overlay_base.mark_bar().encode(...), # overlaying `Track` is already possible in the package
        overlay_base.mark_brush().encode(...),
        overlay_base.mark_brush().encode(...)
    ]
)

Similarly, in theory, we could let users use Chart() for all variants of views to users (Root, MultipleViews, SingleView), and it will be the users' responsibility to define title and subtitle only in the root level. But, in this way, we can let users remember/use a fewer number of types. (I used this approach in my notebook).

So, the questions, I think, are which types we want to advertise users to use (in the docs and example codes) and what parts we will need to update in the Gosling schema for this if any.

from gos.

sehilyi avatar sehilyi commented on May 18, 2024

Another thing I realized is that Altair does not seem to expose types at all (or may a few) that are beyond a chart level. For example, instead of alt.HConcatSpec(), Altair let users use | or alt.hconcat() to generate { hconcat: [{...}, {...}] } specs. As I personally find them really useful, I wonder if we want to (or you are planning to) support such operations and probably deprecate the use of tracks=[...]:

a = overlay_base.mark_bar().encode(...)
b = overlay_base.mark_brush().encode(...)
c = overlay_base.mark_brush().encode(...)

d = (a + b + c) # SingleTrack

If we want to support both the track and view composition, we could do something like the following?:

v1 = gos.overlay(a, b) # { alignemet: "overlay", tracks: [ /* a */, /* b */ ] }
v2 = gos.stack(a, b) # { alignemet: "stack", tracks: [ /* a */, /* b */ ] }

gos.horizontal(v1, v2) # { arrangement: "horizontal", views: [/* v1 */, /* v2 */] }
gos.vertical(v1, v2) # { arrangement: "vertical", views: [/* v1 */, /* v2 */] }
gos.parallel(v1, v2) # { arrangement: "parallel", views: [/* v1 */, /* v2 */] }
gos.serial(v1, v2) # { arrangement: "serial", views: [/* v1 */, /* v2 */] }

# specify additional properties
gos.stack(a, b, spacing=30, layout='linear', xDomain={...})
...

(Sorry, comments became too long than I expected 😅 )

from gos.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.