Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for convex hulls into dev #298

Merged
merged 32 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0dfdff9
Merge pull request #292 from perfectly-preserved-pie/dev
perfectly-preserved-pie Nov 19, 2024
9e2db3d
apply sticky-top class only on non-mobile devices so it doesn't scrol…
perfectly-preserved-pie Nov 19, 2024
0707a21
Add event listener to prevent focus propagation on MultiSelect dropdown
perfectly-preserved-pie Nov 19, 2024
8799ccc
Revert "Add event listener to prevent focus propagation on MultiSelec…
perfectly-preserved-pie Nov 19, 2024
3e0d5bc
Remove fixed height from subtype checklist to allow dynamic resizing
perfectly-preserved-pie Nov 19, 2024
47c118c
Add maxHeight to subtype multiselect
perfectly-preserved-pie Nov 19, 2024
1941cfe
Set searchable attribute to False for subtype checklist
perfectly-preserved-pie Nov 19, 2024
77d8e69
Enable searchable attribute for subtype checklist and add prevent foc…
perfectly-preserved-pie Nov 19, 2024
542507a
Revert "Enable searchable attribute for subtype checklist and add pre…
perfectly-preserved-pie Nov 19, 2024
90a0c30
Add custom behavior to prevent focus on multiselect dropdown
perfectly-preserved-pie Nov 19, 2024
512e039
Revert "Add custom behavior to prevent focus on multiselect dropdown"
perfectly-preserved-pie Nov 19, 2024
b309b91
Enable searchable attribute for subtype checklist and add combobox pr…
perfectly-preserved-pie Nov 19, 2024
cd336f6
Revert "Enable searchable attribute for subtype checklist and add com…
perfectly-preserved-pie Nov 19, 2024
41ff68e
New lease dataset for 11/25
perfectly-preserved-pie Nov 26, 2024
930ce5e
Fucking bad MLS data FUCK HOW DOES A 670SQFT APARTMENT HAVE 30 BATHRO…
perfectly-preserved-pie Nov 26, 2024
c131833
convex hull shading kinda works
perfectly-preserved-pie Nov 27, 2024
8005f69
Remove the dumb ass circles, but still kinda weird
perfectly-preserved-pie Nov 27, 2024
fa8b0c5
Retrieve all leaves so that the polygon matches all boundaries/markers
perfectly-preserved-pie Nov 27, 2024
5ae082b
Remove the previous polygon so it doesn't clutter up the map
perfectly-preserved-pie Nov 27, 2024
b10263e
First attempt at cluster colors
perfectly-preserved-pie Nov 27, 2024
6ecc7b0
Refactor cluster color scale and marker customization for improved cl…
perfectly-preserved-pie Nov 27, 2024
1bad28a
Make the cluster icon two concentric circles & change the marker coun…
perfectly-preserved-pie Nov 27, 2024
38db2f8
Don't bold the marker count
perfectly-preserved-pie Nov 27, 2024
197e9f2
Match default Leaflet.markercluster font and styling
perfectly-preserved-pie Nov 27, 2024
14fa14b
- Fix unintended page refresh when updating the map.
perfectly-preserved-pie Nov 28, 2024
6d84cb2
Remove dead code
perfectly-preserved-pie Nov 28, 2024
f12ea0f
Match Leaflet.markercluster default color scale
perfectly-preserved-pie Nov 28, 2024
f0312a8
Adjust color scale for clusters
perfectly-preserved-pie Nov 28, 2024
16d7120
Convex hulls for buy page
perfectly-preserved-pie Nov 28, 2024
ccfdcc5
Migrate from deprecated argument
perfectly-preserved-pie Nov 28, 2024
0f18ba4
Migrate from deprecated argument
perfectly-preserved-pie Nov 28, 2024
0748b6c
Merge branch 'convex-test1' of https://github.com/perfectly-preserved…
perfectly-preserved-pie Nov 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@

# Create the app
app = Dash(
__name__,
external_stylesheets=external_stylesheets,
use_pages=True,
external_scripts = [
'https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js' # Turf.js for convex hulls
],
external_stylesheets = external_stylesheets,
name = __name__,
# Add meta tags for mobile devices
# https://community.plotly.com/t/reorder-website-for-mobile-view/33669/5?
meta_tags = [
{"name": "viewport", "content": "width=device-width, initial-scale=1"}
],
use_pages = True,
)

# Set the page title
Expand Down
101 changes: 101 additions & 0 deletions assets/dashExtensions_default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
window.dashExtensions = Object.assign({}, window.dashExtensions, {
default: {
function0: function(feature, latlng, index, context) {
// A global reference to the currently displayed polygon
if (!context.currentPolygon) {
context.currentPolygon = null;
}

// Access all the leaves of the cluster
const leaves = index.getLeaves(feature.properties.cluster_id, Infinity); // Retrieve all children
const clusterSize = leaves.length;

// Define a color scale (mimicking Leaflet.markercluster behavior)
// Default colors are at https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/MarkerCluster.Default.css
// Because I'm coloring the polygons (instead of using the default blue) most of the default colors look way too faint on the map
// So I'm adjusting them here
const getColor = function(size) {
if (size < 50) return 'rgba(110, 204, 57, 0.6)'; // Small clusters
if (size < 200) return 'yellow'; // Medium clusters
if (size < 500) return 'orange'; // Larger clusters
return 'red'; // Very large clusters
};

// Get the appropriate color for the cluster size
const color = getColor(clusterSize);

// Collect coordinates for the cluster's children
let features = [];
for (let i = 0; i < leaves.length; ++i) {
const coords = leaves[i].geometry.coordinates;
const lng = coords[0];
const lat = coords[1];
features.push(turf.point([lng, lat]));
}

// Create a Turf.js feature collection
const fc = turf.featureCollection(features);

// Compute the convex hull
const convexHull = turf.convex(fc);

// If the convex hull exists, create it as a polygon
let polygonLayer = null;
if (convexHull) {
polygonLayer = L.geoJSON(convexHull, {
style: {
color: color, // Use the same color as the cluster icon
weight: 2, // Border thickness
fillOpacity: 0.2, // Polygon fill transparency
fillColor: color // Polygon fill color
}
});
}

// Create a custom marker for the cluster with Leaflet.markercluster font styling
const clusterMarker = L.marker(latlng, {
icon: L.divIcon({
html: `
<div style="position:relative; width:40px; height:40px; font-family: Arial, sans-serif; font-size: 12px;">
<div style="background-color:${color}; opacity:0.6;
border-radius:50%; width:40px; height:40px;
position:absolute; top:0; left:0;"></div>
<div style="background-color:${color};
border-radius:50%; width:30px; height:30px;
position:absolute; top:5px; left:5px;
display:flex; align-items:center; justify-content:center;
font-weight:normal; color:black; font-size:12px; font-family: Arial, sans-serif;">
${feature.properties.point_count_abbreviated}
</div>
</div>`,
className: 'marker-cluster', // Optional: add a class for further customization
iconSize: L.point(40, 40) // Adjust the icon size
})
});

// Add mouseover behavior to display the convex hull
clusterMarker.on('mouseover', function() {
// Remove the previously displayed polygon, if any
if (context.currentPolygon) {
context.map.removeLayer(context.currentPolygon);
}

// Add the new polygon to the map and update the reference
if (polygonLayer) {
polygonLayer.addTo(context.map);
context.currentPolygon = polygonLayer;
}
});

// Add behavior to remove the polygon when the mouse leaves the cluster
clusterMarker.on('mouseout', function() {
if (context.currentPolygon) {
context.map.removeLayer(context.currentPolygon);
context.currentPolygon = null;
}
});

return clusterMarker;
}
}
});
Binary file modified assets/datasets/lease.parquet
Binary file not shown.
1 change: 1 addition & 0 deletions assets/us-cities.json

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions functions/convex_hull.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from dash_extensions.javascript import assign

generate_convex_hulls = assign("""function(feature, latlng, index, context){
// A global reference to the currently displayed polygon
if (!context.currentPolygon) {
context.currentPolygon = null;
}

// Access all the leaves of the cluster
const leaves = index.getLeaves(feature.properties.cluster_id, Infinity); // Retrieve all children
const clusterSize = leaves.length;

// Define a color scale (mimicking Leaflet.markercluster behavior)
// Default colors are at https://github.com/Leaflet/Leaflet.markercluster/blob/master/dist/MarkerCluster.Default.css
// Because I'm coloring the polygons (instead of using the default blue) most of the default colors look way too faint on the map
// So I'm adjusting them here
const getColor = function(size) {
if (size < 50) return 'rgba(110, 204, 57, 0.6)'; // Small clusters
if (size < 200) return 'yellow'; // Medium clusters
if (size < 500) return 'orange'; // Larger clusters
return 'red'; // Very large clusters
};

// Get the appropriate color for the cluster size
const color = getColor(clusterSize);

// Collect coordinates for the cluster's children
let features = [];
for (let i = 0; i < leaves.length; ++i) {
const coords = leaves[i].geometry.coordinates;
const lng = coords[0];
const lat = coords[1];
features.push(turf.point([lng, lat]));
}

// Create a Turf.js feature collection
const fc = turf.featureCollection(features);

// Compute the convex hull
const convexHull = turf.convex(fc);

// If the convex hull exists, create it as a polygon
let polygonLayer = null;
if (convexHull) {
polygonLayer = L.geoJSON(convexHull, {
style: {
color: color, // Use the same color as the cluster icon
weight: 2, // Border thickness
fillOpacity: 0.2, // Polygon fill transparency
fillColor: color // Polygon fill color
}
});
}

// Create a custom marker for the cluster with Leaflet.markercluster font styling
const clusterMarker = L.marker(latlng, {
icon: L.divIcon({
html: `
<div style="position:relative; width:40px; height:40px; font-family: Arial, sans-serif; font-size: 12px;">
<div style="background-color:${color}; opacity:0.6;
border-radius:50%; width:40px; height:40px;
position:absolute; top:0; left:0;"></div>
<div style="background-color:${color};
border-radius:50%; width:30px; height:30px;
position:absolute; top:5px; left:5px;
display:flex; align-items:center; justify-content:center;
font-weight:normal; color:black; font-size:12px; font-family: Arial, sans-serif;">
${feature.properties.point_count_abbreviated}
</div>
</div>`,
className: 'marker-cluster', // Optional: add a class for further customization
iconSize: L.point(40, 40) // Adjust the icon size
})
});

// Add mouseover behavior to display the convex hull
clusterMarker.on('mouseover', function() {
// Remove the previously displayed polygon, if any
if (context.currentPolygon) {
context.map.removeLayer(context.currentPolygon);
}

// Add the new polygon to the map and update the reference
if (polygonLayer) {
polygonLayer.addTo(context.map);
context.currentPolygon = polygonLayer;
}
});

// Add behavior to remove the polygon when the mouse leaves the cluster
clusterMarker.on('mouseout', function() {
if (context.currentPolygon) {
context.map.removeLayer(context.currentPolygon);
context.currentPolygon = null;
}
});

return clusterMarker;
}""")
7 changes: 4 additions & 3 deletions pages/buy_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from dash_extensions.javascript import Namespace
from dash.dependencies import Input, Output, State
from flask import request
from functions.convex_hull import generate_convex_hulls
from loguru import logger
from user_agents import parse
import dash
Expand All @@ -12,9 +13,8 @@
import dash_leaflet.express as dlx
import pandas as pd
import sys
import uuid
from loguru import logger
import time
import uuid

dash.register_page(
__name__,
Expand Down Expand Up @@ -274,12 +274,13 @@ def update_map(
id=str(uuid.uuid4()),
data=geojson,
cluster=True,
clusterToLayer=generate_convex_hulls,
onEachFeature=ns("on_each_feature"),
zoomToBoundsOnClick=True,
superClusterOptions={ # https://github.com/mapbox/supercluster#options
'radius': 160,
'minZoom': 3,
},
options=dict(onEachFeature=ns("on_each_feature"))
)

# Callback to toggle the visibility of dynamic components
Expand Down
8 changes: 5 additions & 3 deletions pages/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def create_subtype_checklist(self):
id='subtype_checklist',
data=data,
value=initial_values,
searchable=True,
searchable=False,
nothingFoundMessage="No options found",
clearable=True,
style={"margin-bottom": "10px"},
Expand All @@ -245,7 +245,8 @@ def create_subtype_checklist(self):
style={
"overflow-y": "scroll",
"overflow-x": 'hidden',
"height": '120px'
"maxHeight": '120px',
#"height": '120px'
})
])
return subtype_checklist
Expand Down Expand Up @@ -1103,7 +1104,8 @@ def create_map_card(self):
body = True,
# Make the graph stay in view as the page is scrolled down
# https://getbootstrap.com/docs/4.0/utilities/position/
className = 'sticky-top'
# Apply sticky-top class only on non-mobile devices
className='d-block d-md-block sticky-top'
)

return map_card
Expand Down
5 changes: 3 additions & 2 deletions pages/lease_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from dash_extensions.javascript import Namespace
from dash.dependencies import Input, Output, State
from flask import request
from functions.convex_hull import generate_convex_hulls
from loguru import logger
from user_agents import parse
import dash
Expand Down Expand Up @@ -238,15 +239,15 @@ def update_map(subtypes_chosen, pets_chosen, terms_chosen, garage_spaces, rental
# Generate the map
return dl.GeoJSON(
id=str(uuid.uuid4()),
#children=[dl.Popup(id='2popup')],
data=geojson,
cluster=True,
clusterToLayer=generate_convex_hulls,
onEachFeature=ns("on_each_feature"),
zoomToBoundsOnClick=True,
superClusterOptions={ # https://github.com/mapbox/supercluster#options
'radius': 160,
'minZoom': 3,
},
options=dict(onEachFeature=ns("on_each_feature"))
)

# Create a callback to manage the collapsing behavior
Expand Down
Loading
Loading