pygraphistry icon indicating copy to clipboard operation
pygraphistry copied to clipboard

feat(gfql): Expose ring layout functions via GFQL call()

Open lmeyerov opened this issue 2 months ago • 0 comments

Summary

Expose ring/radial layout functions via GFQL call() to enable time-series and hierarchical graph visualizations through the JSON wire protocol.

Background

Ring layouts exist in the Plotter API but are NOT currently exposed via GFQL call_safelist.py:

  • time_ring_layout() - Time-based radial layout (datetime64 column)
  • ring_categorical_layout() - Categorical radial layout
  • ring_continuous_layout() - Continuous radial layout

These layouts are already documented in the LLM guide (docs/source/gfql/spec/llm_guide.md) but cannot be used via GFQL JSON queries.

Motivation

Ring layouts are valuable for:

  • Time-series analysis: Events arranged chronologically in concentric rings
  • Hierarchical structures: Levels/depths visualized as rings
  • Categorical grouping: Categories organized radially

Currently users must use the Plotter API directly - GFQL users cannot access these layouts.

Implementation Tasks

1. Add to call_safelist.py (~30 min)

File: graphistry/compute/gfql/call_safelist.py

Add entries for all three ring layout functions:

'time_ring_layout': {
    'allowed_params': {
        'time_col', 'num_rings', 'time_start', 'time_end',
        'time_unit', 'min_r', 'max_r', 'reverse', 'play_ms', 'engine'
    },
    'required_params': set(),
    'param_validators': {
        'time_col': is_string_or_none,
        'num_rings': is_int_or_none,
        'time_unit': is_string_or_none,
        'min_r': is_number,
        'max_r': is_number,
        'reverse': is_bool,
        'play_ms': is_int,
        'engine': is_string
    },
    'description': 'Radial graph layout where nodes are positioned based on a datetime64 column'
},

'ring_categorical_layout': {
    'allowed_params': {
        'ring_col', 'num_rings', 'min_r', 'max_r', 'reverse',
        'play_ms', 'format_axis', 'engine'
    },
    'required_params': set(),
    'param_validators': {
        'ring_col': is_string_or_none,
        'num_rings': is_int_or_none,
        'min_r': is_number,
        'max_r': is_number,
        'reverse': is_bool,
        'play_ms': is_int,
        'engine': is_string
    },
    'description': 'Radial layout based on categorical node attribute'
},

'ring_continuous_layout': {
    'allowed_params': {
        'ring_col', 'num_rings', 'min_r', 'max_r', 'reverse',
        'play_ms', 'format_axis', 'engine'
    },
    'required_params': set(),
    'param_validators': {
        'ring_col': is_string_or_none,
        'num_rings': is_int_or_none,
        'min_r': is_number,
        'max_r': is_number,
        'reverse': is_bool,
        'play_ms': is_int,
        'engine': is_string
    },
    'description': 'Radial layout based on continuous node attribute'
}

2. Add Tests (~60 min)

File: graphistry/tests/compute/test_gfql_ring_layouts.py

Test cases:

def test_time_ring_layout_basic():
    """Test time_ring_layout via GFQL call()"""
    g = graphistry.nodes(pd.DataFrame({
        'id': ['a', 'b', 'c'],
        'time': pd.to_datetime(['2024-01-01', '2024-01-15', '2024-02-01'])
    }))
    
    # Via GFQL
    chain = {
        "type": "Chain",
        "chain": [{"type": "Call", "function": "time_ring_layout", "params": {"time_col": "time"}}]
    }
    g2 = g.chain(chain)
    
    assert 'x' in g2._nodes.columns
    assert 'y' in g2._nodes.columns

def test_ring_categorical_layout():
    """Test ring_categorical_layout via GFQL call()"""
    # ... similar test

def test_ring_continuous_layout():
    """Test ring_continuous_layout via GFQL call()"""
    # ... similar test

3. Run Tests (~15 min)

WITH_BUILD=0 WITH_LINT=0 WITH_TYPECHECK=0 WITH_TEST=1 \
  ./test-cpu-local-minimal.sh graphistry/tests/compute/test_gfql_ring_layouts.py -xvs

4. Update Documentation (~15 min)

LLM guide already documents these (see commit 059af3bb), but may need to update notes:

Current note in docs/source/gfql/spec/llm_guide.md:

Note: Ring layouts documented but not yet exposed via GFQL call_safelist

After implementation: Remove the note or update to indicate they're now available.

Acceptance Criteria

  • [ ] All 3 ring layout functions added to call_safelist.py
  • [ ] Parameter validation implemented for all functions
  • [ ] Tests pass for all 3 layouts (CPU tests sufficient)
  • [ ] GFQL JSON queries can successfully invoke ring layouts
  • [ ] Documentation updated if needed
  • [ ] No regressions in existing tests

Estimated Effort

Total: 2-3 hours

  • Add to safelist: 30 min
  • Write tests: 60 min
  • Run tests + validation: 30 min
  • Documentation updates: 15 min

References

  • Implementation: graphistry/layout/ring/time.py, graphistry/layouts.py
  • Existing methods: time_ring_layout(), ring_categorical_layout(), ring_continuous_layout()
  • Documentation: docs/source/gfql/spec/llm_guide.md (lines 444-460)
  • Related PR: #807 (LLM guide enhancements)

Related Issues

Part of GFQL layout completeness - follows FA2 default guidance established in #807

lmeyerov avatar Oct 19 '25 08:10 lmeyerov