541 lines
15 KiB
Markdown
541 lines
15 KiB
Markdown
# CLAUDE.md - Globe Visualization 6 Development Context
|
|
|
|
## Project Overview
|
|
|
|
This is **Iteration 6** in a progressive Mapbox GL JS learning series. Each iteration builds upon previous learnings while introducing new techniques from web research. This iteration focuses on **interactive filtering and UI controls** for data exploration.
|
|
|
|
## Development Assignment
|
|
|
|
**Task**: Create an interactive globe visualization demonstrating advanced filtering techniques learned from Mapbox documentation.
|
|
|
|
**Theme**: Global University Rankings & Research Output
|
|
- 120 top research universities worldwide
|
|
- Multi-dimensional data: ranking, publications, citations, funding, Nobel prizes
|
|
- Interactive filtering by region and research metrics
|
|
- Real-time statistics dashboard
|
|
|
|
## Web Research Integration
|
|
|
|
**Source**: Mapbox GL JS Filter Markers Example
|
|
**URL**: https://docs.mapbox.com/mapbox-gl-js/example/filter-markers/
|
|
|
|
### Techniques Extracted from Web Source:
|
|
|
|
#### 1. Core Filtering Method
|
|
```javascript
|
|
// From web source - basic pattern
|
|
map.setLayoutProperty(layerID, 'visibility', checked ? 'visible' : 'none');
|
|
|
|
// Enhanced approach learned
|
|
map.setFilter('layer-id', filterExpression);
|
|
```
|
|
|
|
The web source demonstrated layer visibility toggling. I learned the more sophisticated `setFilter()` approach which filters features within a layer rather than hiding the entire layer.
|
|
|
|
#### 2. Filter Expression Syntax
|
|
```javascript
|
|
// From web source - equality filter
|
|
['==', 'icon', symbol]
|
|
|
|
// My applications
|
|
['==', 'region', selectedRegion] // Equality
|
|
['<=', ['get', 'ranking'], maxRank] // Less than or equal
|
|
['>=', ['get', 'publications'], minPubs] // Greater than or equal
|
|
['in', ['get', 'region'], ['literal', regions]] // Multiple values
|
|
```
|
|
|
|
#### 3. Checkbox UI Pattern
|
|
```javascript
|
|
// From web source
|
|
checkbox.addEventListener('change', (e) => {
|
|
map.setLayoutProperty(
|
|
layerID,
|
|
'visibility',
|
|
e.target.checked ? 'visible' : 'none'
|
|
);
|
|
});
|
|
|
|
// My adaptation
|
|
checkbox.addEventListener('change', () => {
|
|
updateFilterState();
|
|
applyFilters();
|
|
});
|
|
```
|
|
|
|
#### 4. Feature Property Access
|
|
```javascript
|
|
// Learned pattern for accessing feature properties in expressions
|
|
['get', 'propertyName'] // Retrieves property value from feature
|
|
```
|
|
|
|
### My Enhancements Beyond Web Source:
|
|
|
|
1. **Compound Filter Expressions**:
|
|
- Used `all` operator to combine multiple filter conditions
|
|
- Implemented range filtering with `>=` and `<=`
|
|
- Multi-value filtering with `in` and `literal`
|
|
|
|
2. **Range Sliders**:
|
|
- Extended beyond checkboxes to continuous controls
|
|
- Real-time value display and updates
|
|
- Multiple independent metric thresholds
|
|
|
|
3. **Filter Coordination**:
|
|
- Mutual exclusivity for "All" vs specific regions
|
|
- State management across 6 filter dimensions
|
|
- Reset functionality restoring all defaults
|
|
|
|
4. **Statistics Calculation**:
|
|
- Query rendered features after filtering
|
|
- Aggregate calculations (sum, average)
|
|
- Live dashboard updates
|
|
|
|
5. **Multi-Layer Filtering**:
|
|
- Coordinated filters across universities and labels layers
|
|
- Maintained label visibility rules while respecting filters
|
|
|
|
## Technical Implementation Details
|
|
|
|
### Filter Architecture
|
|
|
|
**Filter State Object**:
|
|
```javascript
|
|
let currentFilters = {
|
|
regions: ['all'], // Selected regions
|
|
rankingRange: [1, 120], // Min/max ranking
|
|
minPublications: 0, // Publication threshold
|
|
minCitations: 0, // Citation threshold
|
|
minFunding: 0, // Funding threshold
|
|
minNobelPrizes: 0 // Nobel prize threshold
|
|
};
|
|
```
|
|
|
|
**Filter Expression Builder**:
|
|
```javascript
|
|
function applyFilters() {
|
|
let filterExpression = ['all']; // AND combiner
|
|
|
|
// Region filter (OR within selected)
|
|
if (!selectedRegions.includes('all')) {
|
|
filterExpression.push([
|
|
'in',
|
|
['get', 'region'],
|
|
['literal', selectedRegions]
|
|
]);
|
|
}
|
|
|
|
// Ranking filter
|
|
filterExpression.push(['<=', ['get', 'ranking'], maxRank]);
|
|
|
|
// Metric thresholds
|
|
if (minPubs > 0) {
|
|
filterExpression.push(['>=', ['get', 'publications'], minPubs]);
|
|
}
|
|
|
|
// Apply to layer
|
|
map.setFilter('universities-layer', filterExpression);
|
|
|
|
// Update statistics
|
|
updateStatistics();
|
|
}
|
|
```
|
|
|
|
### UI Control Implementation
|
|
|
|
**Checkbox Filters** (7 regions):
|
|
```javascript
|
|
regions.forEach((region, index) => {
|
|
const checkbox = createElement('input', {
|
|
type: 'checkbox',
|
|
id: `region-${index}`,
|
|
value: region,
|
|
checked: region === 'All'
|
|
});
|
|
|
|
checkbox.addEventListener('change', () => {
|
|
// Handle "All" mutual exclusivity
|
|
if (region === 'All' && checkbox.checked) {
|
|
uncheckOtherRegions();
|
|
} else if (checkbox.checked) {
|
|
uncheckAll();
|
|
}
|
|
applyFilters();
|
|
});
|
|
});
|
|
```
|
|
|
|
**Range Sliders** (5 metrics):
|
|
```javascript
|
|
slider.addEventListener('input', (e) => {
|
|
const value = parseInt(e.target.value);
|
|
currentFilters.metric = value;
|
|
displayElement.textContent = formatValue(value);
|
|
applyFilters(); // Immediate update
|
|
});
|
|
```
|
|
|
|
### Statistics Dashboard
|
|
|
|
**Query Filtered Features**:
|
|
```javascript
|
|
function updateStatistics() {
|
|
const features = map.queryRenderedFeatures({
|
|
layers: ['universities-layer']
|
|
});
|
|
|
|
const stats = {
|
|
count: features.length,
|
|
avgResearch: average(features, 'researchScore'),
|
|
totalPubs: sum(features, 'publications'),
|
|
totalCitations: sum(features, 'citations'),
|
|
totalNobel: sum(features, 'nobelPrizes')
|
|
};
|
|
|
|
updateDashboard(stats);
|
|
}
|
|
```
|
|
|
|
This queries only the currently visible (filtered) features, enabling real-time statistics that update with every filter change.
|
|
|
|
## Data Structure
|
|
|
|
### University Data Model
|
|
|
|
```javascript
|
|
{
|
|
type: "Feature",
|
|
geometry: {
|
|
type: "Point",
|
|
coordinates: [longitude, latitude]
|
|
},
|
|
properties: {
|
|
name: "University Name",
|
|
country: "Country",
|
|
region: "Region",
|
|
ranking: 1-120, // Global position
|
|
publications: 5500-19800, // Annual papers
|
|
citations: 122000-456000, // Total citations
|
|
funding: 400-4200, // Annual funding ($M)
|
|
nobelPrizes: 0-161, // Total Nobel laureates
|
|
researchScore: 55-99 // Composite score
|
|
}
|
|
}
|
|
```
|
|
|
|
### Data Coverage
|
|
|
|
**120 Universities Across 6 Regions**:
|
|
- North America: 27 (USA, Canada)
|
|
- Europe: 25 (UK, Germany, France, Switzerland, etc.)
|
|
- Asia-Pacific: 42 (China, Japan, Singapore, Australia, India, etc.)
|
|
- Middle East: 9 (Israel, Qatar, UAE, Saudi Arabia, Turkey)
|
|
- Africa: 6 (South Africa, Egypt, Kenya, Nigeria)
|
|
- Latin America: 11 (Brazil, Argentina, Chile, Mexico, Colombia)
|
|
|
|
**Data Realism**:
|
|
- Rankings based on major global university rankings
|
|
- Publications scaled to realistic annual output
|
|
- Citations reflect cumulative academic impact
|
|
- Funding approximates actual research budgets
|
|
- Nobel prizes are historically accurate
|
|
|
|
### Metric Distributions
|
|
|
|
- **Research Score**: 55-99 (composite excellence measure)
|
|
- **Publications**: 5,500-19,800 (annual research output)
|
|
- **Citations**: 122,000-456,000 (total academic citations)
|
|
- **Funding**: $400M-$4,200M (annual research funding)
|
|
- **Nobel Prizes**: 0-161 (total affiliated laureates)
|
|
|
|
## Visual Design System
|
|
|
|
### Color Scheme - Research Excellence
|
|
|
|
8-stop gradient from deep red (emerging) to royal blue (exceptional):
|
|
|
|
```javascript
|
|
'circle-color': [
|
|
'interpolate',
|
|
['linear'],
|
|
['get', 'researchScore'],
|
|
55, '#8b0000', // Deep red
|
|
65, '#dc143c', // Crimson
|
|
75, '#ff6347', // Tomato
|
|
80, '#ffa500', // Orange
|
|
85, '#ffd700', // Gold
|
|
90, '#32cd32', // Lime green
|
|
95, '#00bfff', // Deep sky blue
|
|
99, '#0066ff' // Royal blue
|
|
]
|
|
```
|
|
|
|
### Size Encoding - Ranking Position
|
|
|
|
Inverse relationship (better rank = larger circle):
|
|
|
|
```javascript
|
|
'circle-radius': [
|
|
'interpolate',
|
|
['linear'],
|
|
['get', 'ranking'],
|
|
1, 18, // Top rank
|
|
10, 14,
|
|
25, 11,
|
|
50, 9,
|
|
75, 7,
|
|
100, 5,
|
|
120, 4 // Lower rank
|
|
]
|
|
```
|
|
|
|
### Stroke Styling
|
|
|
|
Dynamic stroke color matches research score:
|
|
|
|
```javascript
|
|
'circle-stroke-color': [
|
|
'interpolate',
|
|
['linear'],
|
|
['get', 'researchScore'],
|
|
55, '#ff4444', // Red
|
|
80, '#ffaa00', // Orange
|
|
95, '#00ffff' // Cyan
|
|
]
|
|
```
|
|
|
|
## Globe Configuration
|
|
|
|
### Atmosphere Settings
|
|
|
|
```javascript
|
|
map.setFog({
|
|
color: 'rgba(8, 16, 32, 0.95)', // Dark base
|
|
'high-color': 'rgba(25, 60, 120, 0.5)', // Blue horizon
|
|
'horizon-blend': 0.3, // Blend distance
|
|
'space-color': '#000510', // Deep space
|
|
'star-intensity': 0.7 // Star brightness
|
|
});
|
|
```
|
|
|
|
### Auto-Rotation
|
|
|
|
```javascript
|
|
let userInteracting = false;
|
|
|
|
function rotateGlobe() {
|
|
if (!userInteracting && !rotationPaused) {
|
|
const center = map.getCenter();
|
|
center.lng += 0.05; // Slow rotation
|
|
map.setCenter(center);
|
|
}
|
|
requestAnimationFrame(rotateGlobe);
|
|
}
|
|
```
|
|
|
|
Pauses during user interaction, resumes when idle.
|
|
|
|
## UI/UX Design Patterns
|
|
|
|
### Glassmorphism Theme
|
|
|
|
```css
|
|
.panel {
|
|
background: rgba(0, 0, 0, 0.9);
|
|
backdrop-filter: blur(15px);
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
}
|
|
```
|
|
|
|
Creates semi-transparent panels with blur effect for modern aesthetic.
|
|
|
|
### Responsive Layout
|
|
|
|
**Desktop**:
|
|
- Controls panel: Right side
|
|
- Statistics: Bottom left
|
|
- Legend: Bottom right
|
|
- Title: Top center
|
|
|
|
**Mobile** (< 768px):
|
|
- Controls: Bottom overlay (scrollable)
|
|
- Statistics: Top left (compact)
|
|
- Legend: Hidden
|
|
- Title: Reduced size
|
|
|
|
### Interactive Feedback
|
|
|
|
- Slider values update in real-time
|
|
- Statistics dashboard refreshes with filters
|
|
- Hover popups show detailed metrics
|
|
- Cursor changes on interactive elements
|
|
- Smooth transitions on all updates
|
|
|
|
## Performance Considerations
|
|
|
|
### Efficient Filtering
|
|
|
|
- **No data reloading**: Filters operate on existing source
|
|
- **Expression-based**: Computed on GPU where possible
|
|
- **Debounced updates**: Statistics calculated after filter application
|
|
- **Feature querying**: Only visible features processed
|
|
|
|
### Optimized Rendering
|
|
|
|
- **Single data source**: All 120 universities in one GeoJSON
|
|
- **Layer reuse**: Same source for circles and labels
|
|
- **Conditional labels**: Only top 30 show text
|
|
- **Stroke width**: Modest 2px to reduce overdraw
|
|
|
|
### Memory Management
|
|
|
|
- **Static data**: Universities loaded once
|
|
- **No data duplication**: Filters reference same source
|
|
- **Minimal DOM**: Controls generated once
|
|
- **Event delegation**: Where possible
|
|
|
|
## Validation Checklist
|
|
|
|
- [x] Uses Mapbox GL JS v3.0.1
|
|
- [x] Globe projection enabled
|
|
- [x] Atmosphere effects configured
|
|
- [x] 120 universities with accurate coordinates
|
|
- [x] 6 data metrics per university
|
|
- [x] Region checkbox filters (7 options)
|
|
- [x] Ranking range slider
|
|
- [x] 4 metric threshold sliders
|
|
- [x] Compound filter expressions with `all`, `in`, `>=`, `<=`
|
|
- [x] Real-time statistics dashboard
|
|
- [x] Interactive popups with full details
|
|
- [x] Reset filters functionality
|
|
- [x] Pause/resume rotation control
|
|
- [x] Top 30 university labels
|
|
- [x] 8-stop color gradient for research scores
|
|
- [x] Inverse size scaling for rankings
|
|
- [x] Auto-rotation with interaction pause
|
|
- [x] Responsive design (desktop & mobile)
|
|
- [x] Web source attribution in README
|
|
|
|
## Learning Outcomes
|
|
|
|
**Completing this iteration demonstrates understanding of**:
|
|
|
|
1. **Filter Expressions**: Syntax and composition with Mapbox expressions
|
|
2. **Dynamic Filtering**: Real-time layer updates with `setFilter()`
|
|
3. **Compound Conditions**: Using `all`, `in`, `>=`, `<=` operators
|
|
4. **UI Controls**: Checkboxes and sliders for filter input
|
|
5. **Feature Querying**: `queryRenderedFeatures()` for statistics
|
|
6. **State Management**: Coordinating multiple filter dimensions
|
|
7. **Interactive Exploration**: User-driven data discovery
|
|
8. **Real-time Updates**: Synchronizing UI, filters, and statistics
|
|
9. **Filter Coordination**: Multiple layers with shared filter logic
|
|
10. **Practical Application**: Adapting web examples to complex use cases
|
|
|
|
## Progressive Learning Demonstrated
|
|
|
|
**Building on Previous Iterations**:
|
|
- **Iter 1**: Basic globe setup, circle layers
|
|
- **Iter 2**: Color gradients, visual styling
|
|
- **Iter 3**: Data-driven expressions
|
|
- **Iter 4**: Multi-layer composition
|
|
- **Iter 6**: Interactive filtering & exploration ← **Current**
|
|
|
|
**New Capabilities Added**:
|
|
- Multi-criteria filtering system
|
|
- Checkbox and slider UI controls
|
|
- Compound filter expressions
|
|
- Real-time statistics calculation
|
|
- Coordinated filter state management
|
|
- Reset and rotation control
|
|
|
|
**Synthesis Achievement**:
|
|
Combines visual techniques (Iter 1-3), layer composition (Iter 4), and interactive controls (Iter 6) into a comprehensive data exploration tool.
|
|
|
|
## Development Notes
|
|
|
|
### Filter Expression Best Practices
|
|
|
|
1. **Use `all` for AND logic**: Combine multiple conditions
|
|
2. **Use `in` for OR logic**: Match any of multiple values
|
|
3. **Use `literal` for arrays**: Wrap array values in filter expressions
|
|
4. **Use `get` for properties**: Access feature properties safely
|
|
5. **Order matters**: Put most selective filters first for performance
|
|
|
|
### Common Pitfalls Avoided
|
|
|
|
- ❌ Reloading data on filter change (inefficient)
|
|
- ✅ Using `setFilter()` on existing source (efficient)
|
|
|
|
- ❌ Hiding entire layer with visibility (limited flexibility)
|
|
- ✅ Filtering features within layer (precise control)
|
|
|
|
- ❌ Calculating statistics from raw data (inaccurate)
|
|
- ✅ Querying rendered features (matches visible state)
|
|
|
|
- ❌ Conflicting filter states (confusing UX)
|
|
- ✅ Coordinated state management (clear behavior)
|
|
|
|
### Extension Opportunities
|
|
|
|
**Future enhancements**:
|
|
- Animated transitions between filter states
|
|
- Saved filter presets (e.g., "Elite Tier", "High Impact")
|
|
- Export filtered data to CSV/JSON
|
|
- Comparison mode (before/after filtering)
|
|
- URL parameters for shareable filtered views
|
|
- Multi-map views showing different filter perspectives
|
|
- 3D extrusions for research output metrics
|
|
- Connection lines between collaborating institutions
|
|
|
|
## Files Created
|
|
|
|
1. **index.html** - Main visualization with complete UI
|
|
2. **src/index.js** - Filtering logic and interactivity
|
|
3. **src/data/data.js** - 120 universities with 6 metrics each
|
|
4. **README.md** - Comprehensive documentation
|
|
5. **CLAUDE.md** - This development context file
|
|
|
|
## Local Development
|
|
|
|
### Running the Visualization
|
|
|
|
```bash
|
|
# Option 1: Python 3
|
|
python3 -m http.server 8000
|
|
|
|
# Option 2: Python 2
|
|
python -m SimpleHTTPServer 8000
|
|
|
|
# Option 3: Node.js
|
|
npx http-server -p 8000
|
|
|
|
# Option 4: PHP
|
|
php -S localhost:8000
|
|
```
|
|
|
|
Then open: http://localhost:8000
|
|
|
|
### Browser Requirements
|
|
|
|
- Modern browser with WebGL support
|
|
- Recommended: Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
|
|
- Minimum screen resolution: 1024x768
|
|
|
|
### Testing Checklist
|
|
|
|
- [ ] Globe renders with atmosphere
|
|
- [ ] 120 universities visible on load
|
|
- [ ] Region checkboxes filter correctly
|
|
- [ ] "All" checkbox mutual exclusivity works
|
|
- [ ] All 5 sliders update filters in real-time
|
|
- [ ] Statistics dashboard shows correct values
|
|
- [ ] Hover popups display university details
|
|
- [ ] Labels appear for top 30 only
|
|
- [ ] Reset button restores all defaults
|
|
- [ ] Pause rotation button toggles correctly
|
|
- [ ] Auto-rotation works and pauses on interaction
|
|
- [ ] Responsive layout on mobile (< 768px)
|
|
|
|
---
|
|
|
|
*This iteration successfully demonstrates mastery of Mapbox filtering techniques learned from official documentation, applied to a comprehensive research dataset with sophisticated multi-dimensional exploration capabilities.*
|