vega / altair

Declarative statistical visualization library for Python
https://altair-viz.github.io/
BSD 3-Clause "New" or "Revised" License
9.35k stars 794 forks source link

Provide a method to get a valid projection #2651

Open thomassajot opened 2 years ago

thomassajot commented 2 years ago

When using Altair for plotting geographic objects, the visualization automatically chooses an appropriate projection. However if we overlay another object, I would like to rescale / crop the chart to have a better zoomed view

Example:

import altair as alt
# inspired by: https://altair-viz.github.io/gallery/london_tube.html

london_boroughs_url = 'https://raw.githubusercontent.com/vega/vega-datasets/next/data/londonBoroughs.json'
boroughs_centroids_url = 'https://raw.githubusercontent.com/vega/vega-datasets/next/data/londonCentroids.json'

boroughs = alt.topo_feature(london_boroughs_url, 'boroughs')

background = alt.Chart(boroughs).mark_geoshape(
    stroke='white',
    strokeWidth=2
).encode(
    color=alt.value('#eee'),
)

labels = alt.Chart(boroughs_centroids_url).mark_text().encode(
    longitude='cx:Q',
    latitude='cy:Q',
    text='bLabel:N',
    size=alt.value(8),
    opacity=alt.value(0.6)
).transform_calculate(
    "bLabel", "indexof (datum.name,' ') > 0  ? substring(datum.name,0,indexof(datum.name, ' ')) : datum.name"
)

london = background + labels
print(london.projection) # undefined !!
display(london)

The above display returns the following image: image

Where an appropriate projection has been chosen. If I add an additional object on the chart.

# source = ....
points = alt.Chart(source).mark_line().encode(
        longitude='longitude_degs:Q',
        latitude='latitude_degs:Q', order='index')
chart = london + points
display(chart)

The projection chosen is based on the largest fitting projection. However I would like to "zoom" in on the smaller object image

Something along the lines of:

chart = (london + points).project(points.projection)

But of course, points.projection is also undefined.


Please follow these steps to make it more efficient to respond to your feature request.

thomassajot commented 2 years ago

I can manually find an appropriate scale but this is not automated:

long_domain = source['longitude_degs'].agg([min, max]).tolist()
lat_domain = source['latitude_degs'].agg([min, max]).tolist()
center = ((long_domain[0] + long_domain[1]) / 2, (lat_domain[0] + lat_domain[1]) / 2, )
points = alt.Chart(source).mark_line().encode(
    longitude='longitude_degs:Q',
    latitude='latitude_degs:Q', 
    order='index') 
chart = (london + points).project(center=center, scale=80000)
display(chart)

image

jakevdp commented 2 years ago

This may be a better question for the vega-lite repository – Altair doesn't have access to the necessary information to auto-generate domains

thomassajot commented 2 years ago

See Vega Lite issue

thomassajot commented 2 years ago

@jakevdp is seems that a fit option can be given to Vega-Lite. Is this feature available in Altair? https://github.com/vega/vega-lite/issues/7448

joelostblom commented 2 years ago

This works for me on the development version of Altair:

import altair as alt
from vega_datasets import data

source = alt.topo_feature(data.world_110m.url, 'countries')
features = [
        {
          "geometry": {
            "coordinates": [[[-3, 52], [4, 52], [4, 45], [-3, 45], [-3, 52]]],
            "type": "Polygon"
          },
          "type": "Feature"
        },
        {
          "geometry": {
            "coordinates": [[[-3, 59], [4, 59], [4, 52], [-3, 52], [-3, 59]]],
            "type": "Polygon"
          },
          "type": "Feature"
        }
]
alt.Chart(source).mark_geoshape(clip=True).project(fit=features)

image

binste commented 1 year ago

@thomassajot There is a new page in the documentation on the geoshape mark including how to use project(fit=...). Does this resolve your original inquiry or do you think there is something still missing?