Skip to content

Allow to layer simple faceted charts #3883

@thomascamminady

Description

@thomascamminady

What is your suggestion?

I'm wondering whether layering simple faceted charts should be possible. Specifically, I'm talking about situations like this:

import altair as alt
import pandas as pd

df = pd.DataFrame(
    {
        "time": [1, 2, 3, 4],
        "label1": ["A", "A", "B", "B"],
        "label2": ["C", "C", "D", "D"],
        "value1": [10, 20, 30, 40],
        "value2": [15, 25, 35, 45],
    }
)


# Option 1 works
base = alt.Chart(df).encode(x="time")
chart = alt.layer(
    base.mark_point().encode(y="value1"),
    base.mark_line().encode(y="value2"),
).facet(column="label1", row="label2")


# Option 2 does not work
# base = alt.Chart(df).mark_point().encode(x="time", column="label1", row="label2")
# alt.layer(
#     base.encode(y="value1"),
#     base.encode(y="value2"),
# )

In situations where we want to layer multiple charts that have defined the same facets, I would think that both options above are equivalent.

I tried this out inside the api.py part but can't finish it.

class LayerChart(TopLevelMixin, _EncodingMixin, core.TopLevelLayerSpec):
    """A Chart with layers within a single panel."""

    @utils.use_signature(core.TopLevelLayerSpec)
    def __init__(
        self,
        data: Optional[ChartDataType] = Undefined,
        layer: Sequence[LayerType] = (),
        **kwargs: Any,
    ) -> None:
        # Faceted charts can't be layered,
        # but if all layers have the same facets,
        # we can remove the facets from the spec and facet the layered chart later on.
        facet_encoding_dicts = [
            {
                "row": spec._get("encoding")._get("row"),
                "facet": spec._get("encoding")._get("facet"),
                "column": spec._get("encoding")._get("column"),
            }
            for spec in layer
        ]
        facet_encodings_are_identical_across_layers = False
        # Check if all facets are Undefined in the first dict
        if not all(_ is Undefined for _ in facet_encoding_dicts[0].values()):
            # Check if all dicts are the same
            for _ in facet_encoding_dicts[1:]:
                if _ != facet_encoding_dicts[0]:
                    break
            else:
                facet_encodings_are_identical_across_layers = True

        if facet_encodings_are_identical_across_layers:
            # Let us remove the facet encodings from each layer
            for spec in layer:
                encoding = spec._get("encoding")
                if encoding is not Undefined:
                    for channel in ["row", "facet", "column"]:
                        if encoding._get(channel) is not Undefined:
                            encoding.__setattr__(channel, Undefined)

        # TODO: check for conflicting interaction
        for spec in layer:
            _check_if_valid_subspec(spec, "LayerChart")
            _check_if_can_be_layered(spec)
        super().__init__(data=data, layer=list(layer), **kwargs)
        self.layer: list[ChartType]
        self.params: Optional[Sequence[_Parameter]]
        self.data: Optional[ChartDataType]
        self.data, self.layer = _combine_subchart_data(self.data, self.layer)
        # Currently (Vega-Lite 5.5) the same param can't occur on two layers
        self.layer = _remove_duplicate_params(self.layer)
        self.params, self.layer = _combine_subchart_params(self.params, self.layer)

        # Some properties are not allowed within layer; we'll move to parent.
        layer_props = ("height", "width", "view")
        combined_dict, self.layer = _remove_layer_props(self, self.layer, layer_props)

        for prop in combined_dict:
            self[prop] = combined_dict[prop]

        # I tried this, but that is wrong:
        # if facet_encodings_are_identical_across_layers:
        #     self = self.facet(
        #         facet=facet_encoding_dicts[0]["facet"],
        #         row=facet_encoding_dicts[0]["row"],
        #         column=facet_encoding_dicts[0]["column"],
        #     )

My idea is to loop over the layer and check if the encodings for row, facet, and column are identical for all layers. If so, I'd like to pull them out of the encoding and apply the faceting after layering the chart.

If this is something useful, I'd appreciate some help. If this is just fundamentally not possible and I have missed something, I'm also eager to hear that :)

Cheers!

Have you considered any alternative solutions?

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions