cassiebuhler commited on
Commit
5dc4444
·
1 Parent(s): 9d4bb84

stacked bar charts are also colored by map

Browse files
Files changed (3) hide show
  1. app/app.py +53 -4
  2. app/utils.py +167 -115
  3. app/variables.py +17 -17
app/app.py CHANGED
@@ -169,6 +169,55 @@ chatbot_toggles = {key: False for key in [
169
  'fire', 'rxburn', 'disadvantaged_communities',
170
  'svi',
171
  ]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  #############
173
 
174
  filters = {}
@@ -342,11 +391,11 @@ colors = (
342
  # df - charts; df_tab - printed table (omits colors)
343
  if 'out' not in locals():
344
  df, df_tab, df_percent, df_bar_30x30 = summary_table(ca, column, select_colors, color_choice, filter_cols, filter_vals,colorby_vals)
345
- total_percent = df_percent.percent_CA.sum().round(2)
346
 
347
  else:
348
  df = summary_table_sql(ca, column, colors, ids)
349
- total_percent = df.percent_CA.sum().round(2)
350
 
351
 
352
  # charts displayed based on color_by variable
@@ -374,12 +423,12 @@ with main:
374
  with stats_col:
375
  with st.container():
376
 
377
- st.markdown(f"{total_percent}% CA Covered", help = "Updates based on displayed data")
378
  st.altair_chart(area_plot(df, column), use_container_width=True)
379
 
380
  if 'df_bar_30x30' in locals(): #if we use chatbot, we won't have these graphs.
381
  if column not in ["status", "gap_code"]:
382
- st.altair_chart(stacked_bar(df_bar_30x30, column,'percent_group','status', color_choice + ' by 30x30 Status'), use_container_width=True)
383
 
384
  if show_richness:
385
  st.altair_chart(richness_chart, use_container_width=True)
 
169
  'fire', 'rxburn', 'disadvantaged_communities',
170
  'svi',
171
  ]}
172
+
173
+ def run_sql(query,color_choice):
174
+ """
175
+ Filter data based on an LLM-generated SQL query and return matching IDs.
176
+
177
+ Args:
178
+ query (str): The natural language query to filter the data.
179
+ color_choice (str): The column used for plotting.
180
+ """
181
+ output = few_shot_structured_llm.invoke(query)
182
+ sql_query = output.sql_query
183
+ explanation =output.explanation
184
+
185
+ if not sql_query: # if the chatbot can't generate a SQL query.
186
+ st.success(explanation)
187
+ return pd.DataFrame({'id' : []})
188
+
189
+ result = ca.sql(sql_query).execute()
190
+ if result.empty :
191
+ explanation = "This query did not return any results. Please try again with a different query."
192
+ st.warning(explanation, icon="⚠️")
193
+ st.caption("SQL Query:")
194
+ st.code(sql_query,language = "sql")
195
+ if 'geom' in result.columns:
196
+ return result.drop('geom',axis = 1)
197
+ else:
198
+ return result
199
+
200
+ elif ("id" and "geom" in result.columns):
201
+ style = get_pmtiles_style_llm(style_options[color_choice], result["id"].tolist())
202
+ legend, position, bg_color, fontsize = getLegend(style_options,color_choice)
203
+
204
+ m.add_legend(legend_dict = legend, position = position, bg_color = bg_color, fontsize = fontsize)
205
+ m.add_pmtiles(ca_pmtiles, style=style, opacity=alpha, tooltip=True, fit_bounds=True)
206
+ m.fit_bounds(result.total_bounds.tolist())
207
+ result = result.drop('geom',axis = 1) #printing to streamlit so I need to drop geom
208
+ else:
209
+ st.write(result) # if we aren't mapping, just print out the data
210
+
211
+ with st.popover("Explanation"):
212
+ st.write(explanation)
213
+ st.caption("SQL Query:")
214
+ st.code(sql_query,language = "sql")
215
+
216
+ return result
217
+
218
+
219
+
220
+
221
  #############
222
 
223
  filters = {}
 
391
  # df - charts; df_tab - printed table (omits colors)
392
  if 'out' not in locals():
393
  df, df_tab, df_percent, df_bar_30x30 = summary_table(ca, column, select_colors, color_choice, filter_cols, filter_vals,colorby_vals)
394
+ total_percent = 100*df_percent.percent_CA.sum()
395
 
396
  else:
397
  df = summary_table_sql(ca, column, colors, ids)
398
+ total_percent = 100*df.percent_CA.sum()
399
 
400
 
401
  # charts displayed based on color_by variable
 
423
  with stats_col:
424
  with st.container():
425
 
426
+ st.markdown(f"{total_percent}% CA Protected", help = "Total percentage of 30x30 conserved lands, updates based on displayed data")
427
  st.altair_chart(area_plot(df, column), use_container_width=True)
428
 
429
  if 'df_bar_30x30' in locals(): #if we use chatbot, we won't have these graphs.
430
  if column not in ["status", "gap_code"]:
431
+ st.altair_chart(stacked_bar(df_bar_30x30, column,'percent_group','status', color_choice + ' by 30x30 Status',colors), use_container_width=True)
432
 
433
  if show_richness:
434
  st.altair_chart(richness_chart, use_container_width=True)
app/utils.py CHANGED
@@ -24,15 +24,19 @@ def colorTable(select_colors,color_choice,column):
24
  .to_pandas()
25
  )
26
  return colors
27
-
 
 
 
28
  def get_summary(ca, combined_filter, column, main_group, colors=None):
29
  df = ca.filter(combined_filter)
30
  #total acres for each group
 
31
  group_totals = df.group_by(main_group).aggregate(total_acres=_.acres.sum())
32
  df = ca.filter(combined_filter)
33
  df = (df
34
  .group_by(*column) # unpack the list for grouping
35
- .aggregate(percent_CA=100 * _.acres.sum() / ca_area_acres,
36
  acres = _.acres.sum(),
37
  mean_richness = (_.richness * _.acres).sum() / _.acres.sum(),
38
  mean_rsr = (_.rsr * _.acres).sum() / _.acres.sum(),
@@ -43,12 +47,12 @@ def get_summary(ca, combined_filter, column, main_group, colors=None):
43
  mean_disadvantaged = (_.disadvantaged_communities * _.acres).sum() / _.acres.sum(),
44
  mean_svi = (_.svi * _.acres).sum() / _.acres.sum(),
45
  )
46
- .mutate(percent_CA=_.percent_CA.round(1),
47
- acres=_.acres.round(1))
48
  )
49
-
50
  df = df.inner_join(group_totals, main_group)
51
- df = df.mutate(percent_group=(100 * _.acres / _.total_acres).round(1))
52
  if colors is not None and not colors.empty: #only the df will have colors, df_tab doesn't since we are printing it.
53
  df = df.inner_join(colors, column[-1])
54
  df = df.cast({col: "string" for col in column})
@@ -70,59 +74,92 @@ def summary_table(ca, column, select_colors, color_choice, filter_cols, filter_v
70
  filters.append(getattr(_, column).isin(colorby_vals[column]))
71
  combined_filter = reduce(lambda x, y: x & y, filters) #combining all the filters into ibis filter expression
72
 
73
- df_percent = get_summary(ca, combined_filter, [column],column, colors) # df used for percentage, excludes non-conserved.
 
 
74
  df_tab = get_summary(ca, combined_filter, filter_cols, column, colors = None) #df used for printed table
75
- if column == "status": #need to include non-conserved in summary stats
76
- combined_filter = (combined_filter) | (_.status.isin(['30x30-conserved','other-conserved','unknown','non-conserved']))
 
 
77
  df = get_summary(ca, combined_filter, [column], column, colors) # df used for charts
78
 
79
  df_bar_30x30 = None # no stacked charts if we have status/gap_code
80
  if column not in ["status","gap_code"]: # df for stacked 30x30 status bar chart
81
  colors = colorTable(select_colors,"30x30 Status",'status')
82
- combined_filter_status = (combined_filter) | (_.status.isin(['30x30-conserved','other-conserved','unknown','non-conserved']))
83
- df_bar_30x30 = get_summary(ca, combined_filter_status, [column, 'status'], column, colors) # df used for charts
 
84
  return df, df_tab, df_percent, df_bar_30x30
85
 
86
 
87
 
 
 
 
 
 
 
 
 
88
  def get_hex(df, color,sort_order):
89
  return list(df.drop_duplicates(subset=color, keep="first")
90
  .set_index(color)
91
  .reindex(sort_order)
92
  .dropna()["color"])
93
-
94
 
95
- def stacked_bar(df, x, y, color, title):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  # bar order
97
- if x == "established": # order labels in chronological order, not alphabetic.
98
  sort = '-x'
99
- elif x == "access_type": #order based on levels of openness
100
- sort=['Open', 'Restricted', 'No Public', "Unknown"]
101
- elif x == "manager_type":
102
- sort = ["Federal","Tribal","State","Special District", "County", "City", "HOA","Joint","Non Profit","Private","Unknown"]
103
- elif x == "status":
104
- sort = ["30x30-conserved","other-conserved","unknown","non-conserved"]
105
- elif x == "ecoregion":
106
- sort = ['SE. Great Basin','Mojave Desert','Sonoran Desert','Sierra Nevada','SoCal Mountains & Valleys','Mono',
107
- 'Central CA Coast','Klamath Mountains','NorCal Coast','NorCal Coast Ranges',
108
- 'NW. Basin & Range','Colorado Desert','Central Valley Coast Ranges','SoCal Coast',
109
- 'Sierra Nevada Foothills','Southern Cascades','Modoc Plateau','Great Valley (North)','NorCal Interior Coast Ranges',
110
- 'Great Valley (South)']
111
- else:
 
 
 
 
112
  sort = 'x'
113
 
114
- # label order
115
- if x == "manager_type": #labels are too long, making vertical
116
  angle = 270
117
- height = 373
118
- elif x == 'ecoregion': # make labels vertical and figure taller
 
119
  angle = 270
120
  height = 430
121
- else: #other labels are horizontal
122
  angle = 0
123
  height = 310
124
 
125
- # stacked bar order
126
  sort_order = ['30x30-conserved', 'other-conserved', 'unknown', 'non-conserved']
127
  y_titles = {
128
  'ecoregion': 'Ecoregion (%)',
@@ -131,12 +168,16 @@ def stacked_bar(df, x, y, color, title):
131
  'easement': 'Easement (%)',
132
  'access_type': 'Access (%)'
133
  }
134
- ytitle = y_titles.get(x, y) # Default to `y` if not in the dictionary
135
  color_hex = get_hex(df[[color, 'color']], color, sort_order)
136
- sort_order = sort_order[0:len(color_hex)]
137
  df["stack_order"] = df[color].apply(lambda val: sort_order.index(val) if val in sort_order else len(sort_order))
138
-
139
- if x == "ecoregion":
 
 
 
 
140
  label_transform = (
141
  "replace("
142
  "replace("
@@ -150,40 +191,102 @@ def stacked_bar(df, x, y, color, title):
150
  "'and', '&'),"
151
  "'California', 'CA')"
152
  )
153
- else:
154
- label_transform = f"datum.{x}" # Default label transformation
155
 
156
- chart = alt.Chart(df).mark_bar().transform_calculate(
157
- label=label_transform
 
 
158
  ).encode(
159
- x=alt.X("label:N", sort = sort, title=None, axis=alt.Axis(labelLimit=150, labelAngle=angle)), # Shorten axis labels
160
- y=alt.Y(y, title=ytitle).scale(domain=(0,100)),
 
161
  color=alt.Color(
162
  color,
163
- sort=sort_order, # Controls legend order
164
  scale=alt.Scale(domain=sort_order, range=color_hex)
165
  ),
166
- order=alt.Order(
167
- "stack_order:Q",
168
- sort="ascending"
169
- ),
170
  tooltip=[
171
- alt.Tooltip("label", type="nominal"), # Use transformed label
172
- alt.Tooltip("percent_CA", type="quantitative", format=",.2f"),
173
- alt.Tooltip("percent_group", type="quantitative", format=",.2f"),
174
  alt.Tooltip("acres", type="quantitative", format=",.0f"),
175
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  ).configure_legend(
177
- direction = 'horizontal',
178
- orient='top',
179
- columns = 3,
180
- title = None,
181
- labelOffset = 2,
182
- offset = 10
183
- ).properties(width="container", height=height, title=title
184
- ).configure_title(fontSize=18, align = "center",anchor='middle',offset = 10)
185
- return chart
186
-
 
187
 
188
  def area_plot(df, column): # Percent protected pie chart
189
  base = alt.Chart(df).encode(
@@ -196,7 +299,7 @@ def area_plot(df, column): # Percent protected pie chart
196
  alt.Color("color:N").scale(None).legend(None),
197
  tooltip=[
198
  alt.Tooltip(column, type="nominal"),
199
- alt.Tooltip("percent_CA", type="quantitative", format=",.2f"),
200
  alt.Tooltip("acres", type="quantitative", format=",.0f"),
201
  ]
202
  )
@@ -230,6 +333,8 @@ def bar_chart(df, x, y, title): #display summary stats for color_by column
230
  sort = '-x'
231
  elif x == "access_type": #order based on levels of openness
232
  sort=['Open', 'Restricted', 'No Public', "Unknown"]
 
 
233
  elif x == "manager_type":
234
  sort = ["Federal","Tribal","State","Special District", "County", "City", "HOA","Joint","Non Profit","Private","Unknown"]
235
  elif x == "ecoregion":
@@ -422,56 +527,3 @@ def get_pmtiles_style_llm(paint, ids):
422
  }
423
  return style
424
 
425
- def run_sql(query,color_choice):
426
- """
427
- Filter data based on an LLM-generated SQL query and return matching IDs.
428
-
429
- Args:
430
- query (str): The natural language query to filter the data.
431
- color_choice (str): The column used for plotting.
432
- """
433
- output = few_shot_structured_llm.invoke(query)
434
- sql_query = output.sql_query
435
- explanation =output.explanation
436
-
437
- if not sql_query: # if the chatbot can't generate a SQL query.
438
- st.success(explanation)
439
- return pd.DataFrame({'id' : []})
440
-
441
- result = ca.sql(sql_query).execute()
442
- if result.empty :
443
- explanation = "This query did not return any results. Please try again with a different query."
444
- st.warning(explanation, icon="⚠️")
445
- st.caption("SQL Query:")
446
- st.code(sql_query,language = "sql")
447
- if 'geom' in result.columns:
448
- return result.drop('geom',axis = 1)
449
- else:
450
- return result
451
-
452
- elif ("id" and "geom" in result.columns):
453
- style = get_pmtiles_style_llm(style_options[color_choice], result["id"].tolist())
454
- legend, position, bg_color, fontsize = getLegend(style_options,color_choice)
455
-
456
- m.add_legend(legend_dict = legend, position = position, bg_color = bg_color, fontsize = fontsize)
457
- m.add_pmtiles(ca_pmtiles, style=style, opacity=alpha, tooltip=True, fit_bounds=True)
458
- m.fit_bounds(result.total_bounds.tolist())
459
- result = result.drop('geom',axis = 1) #printing to streamlit so I need to drop geom
460
- else:
461
- st.write(result) # if we aren't mapping, just print out the data
462
-
463
- with st.popover("Explanation"):
464
- st.write(explanation)
465
- st.caption("SQL Query:")
466
- st.code(sql_query,language = "sql")
467
-
468
- return result
469
-
470
-
471
-
472
- def summary_table_sql(ca, column, colors, ids): # get df for charts + df_tab for printed table
473
- filters = [_.id.isin(ids)]
474
- combined_filter = reduce(lambda x, y: x & y, filters) #combining all the filters into ibis filter expression
475
- df = get_summary(ca, combined_filter, [column], colors) # df used for charts
476
- return df
477
-
 
24
  .to_pandas()
25
  )
26
  return colors
27
+
28
+
29
+
30
+
31
  def get_summary(ca, combined_filter, column, main_group, colors=None):
32
  df = ca.filter(combined_filter)
33
  #total acres for each group
34
+ # if colors is not None and not colors.empty:
35
  group_totals = df.group_by(main_group).aggregate(total_acres=_.acres.sum())
36
  df = ca.filter(combined_filter)
37
  df = (df
38
  .group_by(*column) # unpack the list for grouping
39
+ .aggregate(percent_CA= _.acres.sum() / ca_area_acres,
40
  acres = _.acres.sum(),
41
  mean_richness = (_.richness * _.acres).sum() / _.acres.sum(),
42
  mean_rsr = (_.rsr * _.acres).sum() / _.acres.sum(),
 
47
  mean_disadvantaged = (_.disadvantaged_communities * _.acres).sum() / _.acres.sum(),
48
  mean_svi = (_.svi * _.acres).sum() / _.acres.sum(),
49
  )
50
+ .mutate(percent_CA=_.percent_CA.round(3),
51
+ acres=_.acres.round(0))
52
  )
53
+ # if colors is not None and not colors.empty:
54
  df = df.inner_join(group_totals, main_group)
55
+ df = df.mutate(percent_group=( _.acres / _.total_acres).round(3))
56
  if colors is not None and not colors.empty: #only the df will have colors, df_tab doesn't since we are printing it.
57
  df = df.inner_join(colors, column[-1])
58
  df = df.cast({col: "string" for col in column})
 
74
  filters.append(getattr(_, column).isin(colorby_vals[column]))
75
  combined_filter = reduce(lambda x, y: x & y, filters) #combining all the filters into ibis filter expression
76
 
77
+ only_conserved = (combined_filter) & (_.status.isin(['30x30-conserved']))
78
+ df_percent = get_summary(ca, only_conserved, [column],column, colors) # df used for percentage, excludes non-conserved.
79
+
80
  df_tab = get_summary(ca, combined_filter, filter_cols, column, colors = None) #df used for printed table
81
+
82
+ if "non-conserved" in list(chain.from_iterable(filter_vals)):
83
+ combined_filter = (combined_filter) | (_.status.isin(['non-conserved']))
84
+
85
  df = get_summary(ca, combined_filter, [column], column, colors) # df used for charts
86
 
87
  df_bar_30x30 = None # no stacked charts if we have status/gap_code
88
  if column not in ["status","gap_code"]: # df for stacked 30x30 status bar chart
89
  colors = colorTable(select_colors,"30x30 Status",'status')
90
+ df_bar_30x30 = get_summary(ca, combined_filter, [column, 'status'], column, colors) # df used for charts
91
+
92
+
93
  return df, df_tab, df_percent, df_bar_30x30
94
 
95
 
96
 
97
+
98
+ def summary_table_sql(ca, column, colors, ids): # get df for charts + df_tab for printed table
99
+ filters = [_.id.isin(ids)]
100
+ combined_filter = reduce(lambda x, y: x & y, filters) #combining all the filters into ibis filter expression
101
+ df = get_summary(ca, combined_filter, [column], column, colors) # df used for charts
102
+ return df
103
+
104
+
105
  def get_hex(df, color,sort_order):
106
  return list(df.drop_duplicates(subset=color, keep="first")
107
  .set_index(color)
108
  .reindex(sort_order)
109
  .dropna()["color"])
 
110
 
111
+ def transform_label(label, x_field):
112
+ # converting labels for that gnarly stacked bar chart
113
+ if x_field == "access_type":
114
+ return label.replace(" Access", "")
115
+ elif x_field == "ecoregion":
116
+ label = label.replace("Northern California", "NorCal")
117
+ label = label.replace("Southern California", "SoCal")
118
+ label = label.replace("Southeastern", "SE.")
119
+ label = label.replace("Northwestern", "NW.")
120
+ label = label.replace("and", "&")
121
+ label = label.replace("California", "CA")
122
+ return label
123
+ else:
124
+ return label
125
+
126
+
127
+ def stacked_bar(df, x, y, color, title, colors):
128
+ label_colors = colors['color'].to_list()
129
  # bar order
130
+ if x == "established": # order labels in chronological order, not alphabetic.
131
  sort = '-x'
132
+ elif x == "access_type": # order based on levels of openness
133
+ sort = ['Open', 'Restricted', 'No Public', "Unknown"]
134
+ elif x == "easement":
135
+ sort = ['True', 'False']
136
+ elif x == "manager_type":
137
+ sort = ["Federal", "Tribal", "State", "Special District", "County", "City", "HOA",
138
+ "Joint", "Non Profit", "Private", "Unknown"]
139
+ elif x == "status":
140
+ sort = ["30x30-conserved", "other-conserved", "unknown", "non-conserved"]
141
+ elif x == "ecoregion":
142
+ sort = ['SE. Great Basin', 'Mojave Desert', 'Sonoran Desert', 'Sierra Nevada',
143
+ 'SoCal Mountains & Valleys', 'Mono', 'Central CA Coast', 'Klamath Mountains',
144
+ 'NorCal Coast', 'NorCal Coast Ranges', 'NW. Basin & Range', 'Colorado Desert',
145
+ 'Central Valley Coast Ranges', 'SoCal Coast', 'Sierra Nevada Foothills',
146
+ 'Southern Cascades', 'Modoc Plateau', 'Great Valley (North)',
147
+ 'NorCal Interior Coast Ranges', 'Great Valley (South)']
148
+ else:
149
  sort = 'x'
150
 
151
+ if x == "manager_type":
 
152
  angle = 270
153
+ height = 350
154
+
155
+ elif x == 'ecoregion':
156
  angle = 270
157
  height = 430
158
+ else:
159
  angle = 0
160
  height = 310
161
 
162
+ # stacked bar order
163
  sort_order = ['30x30-conserved', 'other-conserved', 'unknown', 'non-conserved']
164
  y_titles = {
165
  'ecoregion': 'Ecoregion (%)',
 
168
  'easement': 'Easement (%)',
169
  'access_type': 'Access (%)'
170
  }
171
+ ytitle = y_titles.get(x, y)
172
  color_hex = get_hex(df[[color, 'color']], color, sort_order)
173
+ sort_order = sort_order[0:len(color_hex)]
174
  df["stack_order"] = df[color].apply(lambda val: sort_order.index(val) if val in sort_order else len(sort_order))
175
+
176
+ # shorten labels to fit on chart
177
+ label_transform = f"datum.{x}"
178
+ if x == "access_type":
179
+ label_transform = f"replace(datum.{x}, ' Access', '')"
180
+ elif x == "ecoregion":
181
  label_transform = (
182
  "replace("
183
  "replace("
 
191
  "'and', '&'),"
192
  "'California', 'CA')"
193
  )
 
 
194
 
195
+ # to match the colors in the map to each label, need to write some ugly code..
196
+ # bar chart w/ xlabels hidden
197
+ chart = alt.Chart(df).mark_bar(height = 500).transform_calculate(
198
+ xlabel=label_transform
199
  ).encode(
200
+ x=alt.X("xlabel:N", sort=sort, title=None,
201
+ axis=alt.Axis(labelLimit=150, labelAngle=angle, labelColor="transparent")),
202
+ y=alt.Y(y, title=ytitle, axis=alt.Axis(labelPadding=5)).scale(domain=(0, 1)),
203
  color=alt.Color(
204
  color,
205
+ sort=sort_order,
206
  scale=alt.Scale(domain=sort_order, range=color_hex)
207
  ),
208
+ order=alt.Order("stack_order:Q", sort="ascending"),
 
 
 
209
  tooltip=[
210
+ alt.Tooltip(x, type="nominal"),
211
+ alt.Tooltip(color, type="nominal"),
212
+ alt.Tooltip("percent_group", type="quantitative", format=",.1%"),
213
  alt.Tooltip("acres", type="quantitative", format=",.0f"),
214
  ]
215
+ )
216
+ transformed_labels = [transform_label(str(lab), x) for lab in colors[x]]
217
+ labels_df = colors
218
+ labels_df['xlabel'] = transformed_labels
219
+ # 2 layers, 1 for the symbol and 1 for the text
220
+ if angle == 0:
221
+ symbol_layer = alt.Chart(labels_df).mark_point(
222
+ filled=True,
223
+ shape="circle",
224
+ size=100,
225
+ xOffset = 0,
226
+ yOffset=130,
227
+ align = 'left',
228
+ tooltip = False
229
+ ).encode(
230
+ x=alt.X("xlabel:N", sort=sort),
231
+ color=alt.Color("color:N", scale=None)
232
+ )
233
+ text_layer = alt.Chart(labels_df).mark_text(
234
+ dy=115, # shifts the text to the right of the symbol
235
+ dx = 0,
236
+ yOffset=0,
237
+ xOffset = 0,
238
+ align='center',
239
+ color="black",
240
+ tooltip = False
241
+ ).encode(
242
+ x=alt.X("xlabel:N", sort=sort),
243
+ text=alt.Text("xlabel:N")
244
+ )
245
+ # vertical labels
246
+ elif angle == 270:
247
+ symbol_layer = alt.Chart(labels_df).mark_point(
248
+ xOffset = 0,
249
+ yOffset= 100,
250
+ filled=True,
251
+ shape="circle",
252
+ size=100,
253
+ tooltip = False
254
+ ).encode(
255
+ x=alt.X("xlabel:N", sort=sort),
256
+ color=alt.Color("color:N", scale=None)
257
+ )
258
+ text_layer = alt.Chart(labels_df).mark_text(
259
+ dy=0,
260
+ dx = -110,
261
+ angle=270,
262
+ align='right',
263
+ color="black",
264
+ tooltip = False
265
+ ).encode(
266
+ x=alt.X("xlabel:N", sort=sort),
267
+ text=alt.Text("xlabel:N")
268
+ )
269
+
270
+ custom_labels = alt.layer(symbol_layer, text_layer)
271
+ final_chart = alt.layer(chart, custom_labels)
272
+
273
+ # put it all together
274
+ final_chart = final_chart.properties(
275
+ width="container",
276
+ height=height,
277
+ title=title
278
  ).configure_legend(
279
+ direction='horizontal',
280
+ orient='top',
281
+ columns=3,
282
+ title=None,
283
+ labelOffset=2,
284
+ offset=10,
285
+ symbolType = 'square'
286
+ ).configure_title(
287
+ fontSize=18, align="center", anchor='middle', offset=10
288
+ )
289
+ return final_chart
290
 
291
  def area_plot(df, column): # Percent protected pie chart
292
  base = alt.Chart(df).encode(
 
299
  alt.Color("color:N").scale(None).legend(None),
300
  tooltip=[
301
  alt.Tooltip(column, type="nominal"),
302
+ alt.Tooltip("percent_CA", type="quantitative", format=",.1%"),
303
  alt.Tooltip("acres", type="quantitative", format=",.0f"),
304
  ]
305
  )
 
333
  sort = '-x'
334
  elif x == "access_type": #order based on levels of openness
335
  sort=['Open', 'Restricted', 'No Public', "Unknown"]
336
+ elif x == "easement":
337
+ sort=['True','False']
338
  elif x == "manager_type":
339
  sort = ["Federal","Tribal","State","Special District", "County", "City", "HOA","Joint","Non Profit","Private","Unknown"]
340
  elif x == "ecoregion":
 
527
  }
528
  return style
529
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/variables.py CHANGED
@@ -40,11 +40,11 @@ white = "#FFFFFF"
40
  # gap codes 3 and 4 are off by default.
41
  default_boxes = {
42
  0: False,
43
- 3: False,
44
- 4: False,
45
- "other-conserved":False,
46
- "unknown":False,
47
- "non-conserved":False
48
  }
49
 
50
  # Maplibre styles. (should these be functions?)
@@ -130,26 +130,26 @@ ecoregion = {
130
  'property': 'ecoregion',
131
  'type': 'categorical',
132
  'stops': [
133
- ['Sierra Nevada Foothills', "#1f77b4"],
134
- ['Southern Cascades', "#ff7f0e"],
135
  ['Southeastern Great Basin', "#2ca02c"],
136
- ['Southern California Mountains and Valleys', "#d62728"],
137
  ['Sonoran Desert', "#9467bd"],
 
 
 
 
 
 
 
138
  ['Northwestern Basin and Range', "#8c564b"],
139
  ['Colorado Desert', "#e377c2"],
140
  ['Central Valley Coast Ranges', "#7f7f7f"],
141
- ['Great Valley (South)', "#bcbd22"],
142
- ['Sierra Nevada', "#17becf"],
143
- ['Northern California Coast Ranges', "#aec7e8"],
144
- ['Northern California Interior Coast Ranges', "#ffbb78"],
145
- ['Mojave Desert', "#98df8a"],
146
- ['Mono', "#ff9896"],
147
  ['Southern California Coast', "#c5b0d5"],
 
 
148
  ['Modoc Plateau', "#c49c94"],
149
- ['Klamath Mountains', "#f7b6d2"],
150
- ['Northern California Coast', "#c7c7c7"],
151
  ['Great Valley (North)', "#dbdb8d"],
152
- ['Central California Coast', "#9edae5"],
153
  ],
154
  'default': white
155
  }
 
40
  # gap codes 3 and 4 are off by default.
41
  default_boxes = {
42
  0: False,
43
+ # 3: False,
44
+ # 4: False,
45
+ # "other-conserved":False,
46
+ # "unknown":False,
47
+ # "non-conserved":False
48
  }
49
 
50
  # Maplibre styles. (should these be functions?)
 
130
  'property': 'ecoregion',
131
  'type': 'categorical',
132
  'stops': [
 
 
133
  ['Southeastern Great Basin', "#2ca02c"],
134
+ ['Mojave Desert', "#98df8a"],
135
  ['Sonoran Desert', "#9467bd"],
136
+ ['Sierra Nevada', "#17becf"],
137
+ ['Southern California Mountains and Valleys', "#d62728"],
138
+ ['Mono', "#ff9896"],
139
+ ['Central California Coast', "#9edae5"],
140
+ ['Klamath Mountains', "#f7b6d2"],
141
+ ['Northern California Coast', "#c7c7c7"],
142
+ ['Northern California Coast Ranges', "#aec7e8"],
143
  ['Northwestern Basin and Range', "#8c564b"],
144
  ['Colorado Desert', "#e377c2"],
145
  ['Central Valley Coast Ranges', "#7f7f7f"],
 
 
 
 
 
 
146
  ['Southern California Coast', "#c5b0d5"],
147
+ ['Sierra Nevada Foothills', "#1f77b4"],
148
+ ['Southern Cascades', "#ff7f0e"],
149
  ['Modoc Plateau', "#c49c94"],
150
+ ['Great Valley (South)', "#bcbd22"],
151
+ ['Northern California Interior Coast Ranges', "#ffbb78"],
152
  ['Great Valley (North)', "#dbdb8d"],
 
153
  ],
154
  'default': white
155
  }