← Back to Blog

Region Statistics: Visualizing Player Activity Across New Eden

EVE Frontier's universe spans hundreds of star systems grouped into regions—distinct areas of space with unique characteristics, resources, and strategic importance. At EF-Map, we wanted to help players understand these regions not just geographically, but behaviorally: which regions are hotspots for mining? Where do most PvP encounters happen? Which areas are safe for solo exploration?

Building our region statistics system required aggregating millions of on-chain events into digestible, actionable insights. Here's how we turned blockchain data into geographic intelligence.

The Challenge: Making Sense of Spatial Data

EVE Frontier's blockchain records every significant action: mining operations, ship destructions, Smart Assembly deployments, territory claims. This creates a rich historical record of player activity—but it's also overwhelming. A single busy day can generate 50,000+ events across 200+ systems.

Our goal: aggregate this event stream by region and visualize patterns over time. We wanted to answer questions like:

The naive approach—querying the blockchain for each region individually—would take minutes and hammer RPC endpoints. We needed a smarter architecture.

Solution: Postgres Spatial Aggregation

We built a two-tier system: blockchain indexer + spatial aggregation pipeline.

Tier 1: Event Indexing

Our Primordium indexer subscribes to all Smart Assembly events on-chain and stores them in Postgres with spatial metadata:

CREATE TABLE blockchain_events (
    event_id BIGSERIAL PRIMARY KEY,
    event_type VARCHAR(50), -- 'mining', 'destruction', 'deployment'
    system_id VARCHAR(66),
    region_id VARCHAR(66),
    character_id VARCHAR(66),
    timestamp TIMESTAMPTZ,
    block_number BIGINT,
    -- Event-specific data
    value_isk DECIMAL,
    item_count INTEGER,
    metadata JSONB
);

-- Index for fast region-based queries
CREATE INDEX idx_events_region_time 
    ON blockchain_events(region_id, timestamp DESC);

Every event is tagged with both its system and region. This redundancy makes region-level aggregation fast—we can query an entire region without joining to a systems table.

Tier 2: Hourly Aggregation

Every hour, a cron job computes region statistics for the past 24 hours:

-- Mining activity by region
SELECT 
    region_id,
    COUNT(*) as mining_events,
    SUM(value_isk) as total_value,
    COUNT(DISTINCT character_id) as unique_miners
FROM blockchain_events
WHERE event_type = 'mining'
  AND timestamp > NOW() - INTERVAL '24 hours'
GROUP BY region_id;

-- PvP activity by region
SELECT 
    region_id,
    COUNT(*) as destructions,
    SUM(value_isk) as total_destroyed_value,
    COUNT(DISTINCT character_id) as unique_attackers
FROM blockchain_events
WHERE event_type = 'destruction'
  AND timestamp > NOW() - INTERVAL '24 hours'
GROUP BY region_id;

These aggregates get written to a summary table that the frontend queries:

CREATE TABLE region_stats (
    region_id VARCHAR(66),
    stat_date DATE,
    mining_events INTEGER,
    mining_value DECIMAL,
    pvp_kills INTEGER,
    pvp_value DECIMAL,
    unique_characters INTEGER,
    PRIMARY KEY (region_id, stat_date)
);

Now we can fetch all region stats for the last 30 days in a single query instead of millions of individual event lookups.

Frontend Visualization: Heat Maps

On the map interface, we render regions as colored overlays based on activity levels:

const getRegionHeatColor = (stats: RegionStats): string => {
    // Normalize activity to 0-1 scale
    const maxActivity = Math.max(...allRegions.map(r => r.activity));
    const normalized = stats.activity / maxActivity;
    
    // Map to color gradient (blue → yellow → red)
    if (normalized < 0.33) {
        return `hsl(240, 80%, ${50 + normalized * 50}%)`; // Blue
    } else if (normalized < 0.67) {
        return `hsl(60, 80%, 50%)`; // Yellow
    } else {
        return `hsl(0, 80%, ${50 - (normalized - 0.67) * 50}%)`; // Red
    }
};

This creates an instant visual read: hot regions glow red, quiet areas stay dark blue. Users can toggle between different metrics (mining, PvP, deployments) to see different activity patterns.

Interactive Tooltips

Hovering over a region shows detailed statistics:

<div className="region-tooltip">
    <h3>{region.name}</h3>
    <div className="stat-row">
        <span>Mining Events:</span>
        <span>{stats.mining_events.toLocaleString()}</span>
    </div>
    <div className="stat-row">
        <span>Total Value Mined:</span>
        <span>{formatISK(stats.mining_value)}</span>
    </div>
    <div className="stat-row">
        <span>Active Miners:</span>
        <span>{stats.unique_characters}</span>
    </div>
    <div className="trend">
        {stats.trend > 0 ? '📈' : '📉'} 
        {Math.abs(stats.trend)}% vs. last week
    </div>
</div>

The trend calculation compares current week vs. previous week to highlight emerging hotspots or declining activity.

Temporal Patterns: Time Series Analysis

We don't just show current snapshots—we track trends over time. Our Stats page includes a time series chart:

const RegionActivityChart = ({ regionId, days = 30 }) => {
    const [data, setData] = useState<TimeSeriesData[]>([]);
    
    useEffect(() => {
        fetch(`/api/region-stats?region=${regionId}&days=${days}`)
            .then(res => res.json())
            .then(stats => {
                const series = stats.map(day => ({
                    date: day.stat_date,
                    mining: day.mining_events,
                    pvp: day.pvp_kills,
                    deployments: day.deployment_count
                }));
                setData(series);
            });
    }, [regionId, days]);
    
    return (
        <LineChart data={data} width={600} height={300}>
            <Line dataKey="mining" stroke="#4ade80" />
            <Line dataKey="pvp" stroke="#f87171" />
            <Line dataKey="deployments" stroke="#60a5fa" />
        </LineChart>
    );
};

This reveals behavioral patterns: some regions show weekly cycles (active on weekends), others have sustained growth (new infrastructure attracting players), and some experience events (sudden PvP spikes during territorial wars).

Performance Optimization: Caching Strategy

Region stats don't change frequently—computing them every request would be wasteful. We implemented a multi-layer cache:

Layer 1: Cloudflare KV (30-minute TTL)

Layer 2: Browser cache (5-minute TTL)

Layer 3: Postgres materialized views (hourly refresh)

This caching hierarchy reduced our API response time from 2 seconds to 40ms while keeping data reasonably fresh.

Real-World Insights from the Data

Analyzing region statistics has revealed fascinating patterns:

1. Mining Migration: Players follow resource depletion. When a region's asteroid belts are exhausted, we see mining activity drop 70% over 2-3 days, then shift to neighboring regions.

2. PvP Chokepoints: Certain regions consistently show high destruction rates—they're strategic choke points between high-value areas. Smart corporations camp these routes.

3. Territorial Control: Regions with sustained deployment activity (Smart Assemblies, infrastructure) correlate strongly with lower PvP rates. Established territories are safer.

4. Weekend Warriors: PvP activity spikes 40% on weekends, while mining stays relatively constant. Different player demographics have different play patterns.

Lessons for Spatial Analytics

Building this system taught us several principles for geographic data visualization:

1. Aggregate early, aggregate often. Don't query raw events in the frontend—pre-compute summaries.

2. Multiple time scales matter. Show 24-hour, 7-day, and 30-day views. Each reveals different patterns.

3. Normalize for visibility. Absolute numbers are less useful than percentiles or trends. A region with "500 events" means nothing without context.

4. Layer your cache. CDN, browser, and database caches compound to create sub-50ms response times.

5. Interactive filters unlock insights. Let users toggle metrics, time ranges, and comparison modes. Static visualizations hide patterns.

Future Enhancements

We're exploring several additions to region statistics:

Region statistics transform raw blockchain data into strategic intelligence. Whether you're a solo explorer looking for quiet systems, a corporation evaluating territory expansion, or a trader identifying supply chain bottlenecks, these metrics help you make data-driven decisions in a complex, dynamic universe.

Ready to explore regional activity patterns? Check out the interactive heat maps and time series charts on the Stats page.

Related Posts

data visualizationanalyticspostgresspatial queriesheat maps