Search code examples
altairvega-lite

Concatenation issue of sorted bar chart layer with mark circle sorted layer in Altair


I'm trying to concatenate two sorted layers in Altair. When I do this it adds extra tick labels.

I've tried scale resolve, axis=None but it doesn't work.

In addition, it looks like this issue relates to https://github.com/altair-viz/altair/issues/820 and https://github.com/vega/vega-lite/issues/3710 but I haven't managed to solve my issue based on these threads.

AS-IS issue with extra labels on the right

My code is below. I run it in Colab Altair 4.2.0.

base = alt.Chart(df_viz2)

bar = base.mark_bar().encode(
    y = alt.Y(field='region_name', type='nominal',  title=None, 
              sort=alt.Sort(
                  field = 'sirens_duration', 
                  op = 'mean', 
                  order='descending'),
              axis = alt.Axis(
                  offset=80
                  ,grid=False
                  ,domain=False
                  ,ticks=False
              )
            )
    ,x = alt.X(field='sirens_duration', aggregate = 'mean', 
               axis = alt.Axis(
                  orient='top', 
                  grid=False, 
                  domain=False, 
                  labels=False,
                  ticks=False, 
                  title='Сумарна тривалість', 
                  titleAlign='right', 
                  titleX=100,
                  titleFontWeight='lighter'
                 ) 
    )
    ,color = alt.condition(
        alt.datum.region_name == 'Луганська',
        alt.value('#60bdb7'),
        alt.value('black')
    )
)

bar_text = bar.mark_text(align='left', baseline='middle', dx=4, fontSize = 14).encode(
    text = alt.Text(field = 'sirens_dur_days_in_ua', type = 'nominal'))


bubble = base.mark_circle(color='black', align='right').encode(
    y = alt.Y(field='region_name', type='nominal', sort=alt.Sort(field='sirens_duration',  order='descending'), 
              title=None, 
              axis = alt.Axis(
                          grid=False, 
                          domain=False, 
                          labels=False,
                          ticks=False)
             )
    ,size = alt.Size('sirens_count', legend=None)
  )

alt.layer(bar, bar_text, bubble).configure_view(
    strokeWidth = 0).properties(
        width = 400, height = 500).configure_axis(
    labelFontSize=16
    ,labelFont='Calibri'
).configure_axisX(
    titleFontSize=10
).resolve_scale(
    y='independent'
)

My goal is to mimic the following data visualization

The most disturbing regions

Data in JSON to reproduce the Chart

{
"0": {
    "region": "\u041b\u0443\u0433\u0430\u043d\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 53.1119097222,
    "sirens_count": 3,
    "region_name": "\u041b\u0443\u0433\u0430\u043d\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "53 days 02:41:09",
    "sirens_dur_days_in_ua": "53 \u0434. 02 \u0433\u043e\u0434. 41 \u0445\u0432.",
    "sirens_duration_sec": 4588869.0
},
"1": {
    "region": "\u0425\u0430\u0440\u043a\u0456\u0432\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 16.9388541667,
    "sirens_count": 544,
    "region_name": "\u0425\u0430\u0440\u043a\u0456\u0432\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "16 days 22:31:57",
    "sirens_dur_days_in_ua": "16 \u0434. 22 \u0433\u043e\u0434. 31 \u0445\u0432.",
    "sirens_duration_sec": 1463517.0
},
"2": {
    "region": "\u041a\u0438\u0457\u0432",
    "sirens_duration": 14.7969444444,
    "sirens_count": 359,
    "region_name": "\u041a\u0438\u0457\u0432",
    "sirens_dur_days": "14 days 19:07:36",
    "sirens_dur_days_in_ua": "14 \u0434. 19 \u0433\u043e\u0434. 07 \u0445\u0432.",
    "sirens_duration_sec": 1278456.0
},
"3": {
    "region": "\u0416\u0438\u0442\u043e\u043c\u0438\u0440\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 13.8627430556,
    "sirens_count": 315,
    "region_name": "\u0416\u0438\u0442\u043e\u043c\u0438\u0440\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "13 days 20:42:21",
    "sirens_dur_days_in_ua": "13 \u0434. 20 \u0433\u043e\u0434. 42 \u0445\u0432.",
    "sirens_duration_sec": 1197741.0
},
"4": {
    "region": "\u0417\u0430\u043f\u043e\u0440\u0456\u0437\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 11.8004861111,
    "sirens_count": 293,
    "region_name": "\u0417\u0430\u043f\u043e\u0440\u0456\u0437\u044c\u043a\u0430",
    "sirens_dur_days": "11 days 19:12:42",
    "sirens_dur_days_in_ua": "11 \u0434. 19 \u0433\u043e\u0434. 12 \u0445\u0432.",
    "sirens_duration_sec": 1019562.0
},
"5": {
    "region": "\u0414\u043d\u0456\u043f\u0440\u043e\u043f\u0435\u0442\u0440\u043e\u0432\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 11.3181018519,
    "sirens_count": 357,
    "region_name": "\u0414\u043d\u0456\u043f\u0440\u043e\u043f\u0435\u0442\u0440\u043e\u0432\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "11 days 07:38:04",
    "sirens_dur_days_in_ua": "11 \u0434. 07 \u0433\u043e\u0434. 38 \u0445\u0432.",
    "sirens_duration_sec": 977884.0
},
"6": {
    "region": "\u041f\u043e\u043b\u0442\u0430\u0432\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 11.2759953704,
    "sirens_count": 304,
    "region_name": "\u041f\u043e\u043b\u0442\u0430\u0432\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "11 days 06:37:26",
    "sirens_dur_days_in_ua": "11 \u0434. 06 \u0433\u043e\u0434. 37 \u0445\u0432.",
    "sirens_duration_sec": 974246.0
},
"7": {
    "region": "\u0427\u0435\u0440\u043a\u0430\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 10.9991550926,
    "sirens_count": 284,
    "region_name": "\u0427\u0435\u0440\u043a\u0430\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "10 days 23:58:47",
    "sirens_dur_days_in_ua": "10 \u0434. 23 \u0433\u043e\u0434. 58 \u0445\u0432.",
    "sirens_duration_sec": 950327.0
},
"8": {
    "region": "\u0414\u043e\u043d\u0435\u0446\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 10.1539583333,
    "sirens_count": 323,
    "region_name": "\u0414\u043e\u043d\u0435\u0446\u044c\u043a\u0430",
    "sirens_dur_days": "10 days 03:41:42",
    "sirens_dur_days_in_ua": "10 \u0434. 03 \u0433\u043e\u0434. 41 \u0445\u0432.",
    "sirens_duration_sec": 877302.0
},
"9": {
    "region": "\u0427\u0435\u0440\u043d\u0456\u0433\u0456\u0432\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 9.9871180556,
    "sirens_count": 276,
    "region_name": "\u0427\u0435\u0440\u043d\u0456\u0433\u0456\u0432\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "9 days 23:41:27",
    "sirens_dur_days_in_ua": "9 \u0434. 23 \u0433\u043e\u0434. 41 \u0445\u0432.",
    "sirens_duration_sec": 862887.0
},
"10": {
    "region": "\u041c\u0438\u043a\u043e\u043b\u0430\u0457\u0432\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 9.5389467593,
    "sirens_count": 213,
    "region_name": "\u041c\u0438\u043a\u043e\u043b\u0430\u0457\u0432\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "9 days 12:56:05",
    "sirens_dur_days_in_ua": "9 \u0434. 12 \u0433\u043e\u0434. 56 \u0445\u0432.",
    "sirens_duration_sec": 824165.0
},
"11": {
    "region": "\u041a\u0438\u0457\u0432\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 9.5028472222,
    "sirens_count": 213,
    "region_name": "\u041a\u0438\u0457\u0432\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "9 days 12:04:06",
    "sirens_dur_days_in_ua": "9 \u0434. 12 \u0433\u043e\u0434. 04 \u0445\u0432.",
    "sirens_duration_sec": 821046.0
},
"12": {
    "region": "\u0421\u0443\u043c\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 8.9156712963,
    "sirens_count": 207,
    "region_name": "\u0421\u0443\u043c\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "8 days 21:58:34",
    "sirens_dur_days_in_ua": "8 \u0434. 21 \u0433\u043e\u0434. 58 \u0445\u0432.",
    "sirens_duration_sec": 770314.0
},
"13": {
    "region": "\u041a\u0456\u0440\u043e\u0432\u043e\u0433\u0440\u0430\u0434\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 8.4262152778,
    "sirens_count": 215,
    "region_name": "\u041a\u0456\u0440\u043e\u0432\u043e\u0433\u0440\u0430\u0434\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "8 days 10:13:45",
    "sirens_dur_days_in_ua": "8 \u0434. 10 \u0433\u043e\u0434. 13 \u0445\u0432.",
    "sirens_duration_sec": 728025.0
},
"14": {
    "region": "\u0412\u0456\u043d\u043d\u0438\u0446\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 8.3401967593,
    "sirens_count": 253,
    "region_name": "\u0412\u0456\u043d\u043d\u0438\u0446\u044c\u043a\u0430",
    "sirens_dur_days": "8 days 08:09:53",
    "sirens_dur_days_in_ua": "8 \u0434. 08 \u0433\u043e\u0434. 09 \u0445\u0432.",
    "sirens_duration_sec": 720593.0
},
"15": {
    "region": "\u041e\u0434\u0435\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 6.6181944444,
    "sirens_count": 184,
    "region_name": "\u041e\u0434\u0435\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "6 days 14:50:12",
    "sirens_dur_days_in_ua": "6 \u0434. 14 \u0433\u043e\u0434. 50 \u0445\u0432.",
    "sirens_duration_sec": 571812.0
},
"16": {
    "region": "\u0422\u0435\u0440\u043d\u043e\u043f\u0456\u043b\u044c\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 5.9015046296,
    "sirens_count": 137,
    "region_name": "\u0422\u0435\u0440\u043d\u043e\u043f\u0456\u043b\u044c\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "5 days 21:38:10",
    "sirens_dur_days_in_ua": "5 \u0434. 21 \u0433\u043e\u0434. 38 \u0445\u0432.",
    "sirens_duration_sec": 509890.0
},
"17": {
    "region": "\u0425\u043c\u0435\u043b\u044c\u043d\u0438\u0446\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 5.8516435185,
    "sirens_count": 134,
    "region_name": "\u0425\u043c\u0435\u043b\u044c\u043d\u0438\u0446\u044c\u043a\u0430",
    "sirens_dur_days": "5 days 20:26:22",
    "sirens_dur_days_in_ua": "5 \u0434. 20 \u0433\u043e\u0434. 26 \u0445\u0432.",
    "sirens_duration_sec": 505582.0
},
"18": {
    "region": "\u0412\u043e\u043b\u0438\u043d\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 5.7078125,
    "sirens_count": 163,
    "region_name": "\u0412\u043e\u043b\u0438\u043d\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "5 days 16:59:15",
    "sirens_dur_days_in_ua": "5 \u0434. 16 \u0433\u043e\u0434. 59 \u0445\u0432.",
    "sirens_duration_sec": 493155.0
},
"19": {
    "region": "\u0420\u0456\u0432\u043d\u0435\u043d\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 5.3617361111,
    "sirens_count": 149,
    "region_name": "\u0420\u0456\u0432\u043d\u0435\u043d\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "5 days 08:40:54",
    "sirens_dur_days_in_ua": "5 \u0434. 08 \u0433\u043e\u0434. 40 \u0445\u0432.",
    "sirens_duration_sec": 463254.0
},
"20": {
    "region": "\u041b\u044c\u0432\u0456\u0432\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 5.125625,
    "sirens_count": 137,
    "region_name": "\u041b\u044c\u0432\u0456\u0432\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "5 days 03:00:54",
    "sirens_dur_days_in_ua": "5 \u0434. 03 \u0433\u043e\u0434. 00 \u0445\u0432.",
    "sirens_duration_sec": 442854.0
},
"21": {
    "region": "\u0406\u0432\u0430\u043d\u043e-\u0424\u0440\u0430\u043d\u043a\u0456\u0432\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 4.7872800926,
    "sirens_count": 112,
    "region_name": "\u0406\u0432\u0430\u043d\u043e-\u0424\u0440\u0430\u043d\u043a\u0456\u0432\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "4 days 18:53:41",
    "sirens_dur_days_in_ua": "4 \u0434. 18 \u0433\u043e\u0434. 53 \u0445\u0432.",
    "sirens_duration_sec": 413621.0
},
"22": {
    "region": "\u0427\u0435\u0440\u043d\u0456\u0432\u0435\u0446\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 3.7838078704,
    "sirens_count": 78,
    "region_name": "\u0427\u0435\u0440\u043d\u0456\u0432\u0435\u0446\u044c\u043a\u0430",
    "sirens_dur_days": "3 days 18:48:41",
    "sirens_dur_days_in_ua": "3 \u0434. 18 \u0433\u043e\u0434. 48 \u0445\u0432.",
    "sirens_duration_sec": 326921.0
},
"23": {
    "region": "\u0417\u0430\u043a\u0430\u0440\u043f\u0430\u0442\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 3.2797800926,
    "sirens_count": 62,
    "region_name": "\u0417\u0430\u043a\u0430\u0440\u043f\u0430\u0442\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "3 days 06:42:53",
    "sirens_dur_days_in_ua": "3 \u0434. 06 \u0433\u043e\u0434. 42 \u0445\u0432.",
    "sirens_duration_sec": 283373.0
},
"24": {
    "region": "\u0425\u0435\u0440\u0441\u043e\u043d\u0441\u044c\u043a\u0430 \u043e\u0431\u043b\u0430\u0441\u0442\u044c",
    "sirens_duration": 0.2153587963,
    "sirens_count": 10,
    "region_name": "\u0425\u0435\u0440\u0441\u043e\u043d\u0441\u044c\u043a\u0430",
    "sirens_dur_days": "0 days 05:10:07",
    "sirens_dur_days_in_ua": "0 \u0434. 05 \u0433\u043e\u0434. 10 \u0445\u0432.",
    "sirens_duration_sec": 18607.0
}

}


Solution

  • The double labels occur because you are resolving the y-scale as independent. If you remove that, the sorting will look messed up, but that is because you have missed the sorting operation for the bubble chart. Adding that in and taking a slightly simpler approach with adding the labels to the bubble chart and then concatenating horizontally, you can get this:

    base = alt.Chart(df_viz2)
    
    bar = base.mark_bar().encode(
        y = alt.Y(field='region_name', type='nominal',  title=None, 
                  sort=alt.Sort(
                      field = 'sirens_duration', 
                      op = 'mean', 
                      order='descending'),
                  axis = None
                )
        ,x = alt.X(field='sirens_duration', aggregate = 'mean', 
                   axis = alt.Axis(
                      orient='top', 
                      grid=False, 
                      domain=False, 
                      labels=False,
                      ticks=False, 
                      title='Сумарна тривалість', 
                      titleAlign='right', 
                      titleX=100,
                      titleFontWeight='lighter'
                     ) 
        )
        ,color = alt.condition(
            alt.datum.region_name == 'Луганська',
            alt.value('#60bdb7'),
            alt.value('black')
        )
    )
    
    bar_text = bar.mark_text(align='left', baseline='middle', dx=4, fontSize = 14).encode(
        text = alt.Text(field = 'sirens_dur_days_in_ua', type = 'nominal'))
    
    
    bubble = base.mark_circle(color='black', align='right').encode(
        y = alt.Y(field='region_name', type='nominal', sort=alt.Sort(field='sirens_duration',  op='mean', order='descending'), 
                  title=None, 
                  axis = alt.Axis(
                       offset=10
                      ,grid=False
                      ,domain=False
                      ,ticks=False
                 ))
        ,size = alt.Size('sirens_count', legend=None)
      )
    
    alt.hconcat(
        bubble,  bar + bar_text
    ).configure_view(
        strokeWidth = 0
    ).configure_axis(
        labelFontSize=16
        ,labelFont='Calibri'
    ).configure_axisX(
        titleFontSize=10
    )
    

    enter image description here