# Graph Objects - Low-Level API

The `plotly.graph_objects` module provides fine-grained control over figure construction through Python classes
representing Plotly components.

## Core Classes

- **`go.Figure`** - Main figure container
- **`go.FigureWidget`** - Jupyter-compatible interactive widget
- **Trace types** - 40+ chart types (Scatter, Bar, Heatmap, etc.)
- **Layout components** - Axes, annotations, shapes, etc.

## Key Advantages

1. **Data validation** - Helpful error messages for invalid properties
2. **Built-in documentation** - Accessible via docstrings
3. **Flexible syntax** - Dictionary or attribute access
4. **Convenience methods** - `.add_trace()`, `.update_layout()`, etc.
5. **Magic underscore notation** - Compact nested property access
6. **Integrated I/O** - `.show()`, `.write_html()`, `.write_image()`

## Basic Figure Construction

### Creating Empty Figure

```python
import plotly.graph_objects as go

fig = go.Figure()
```

### Adding Traces

```python
# Method 1: Add traces one at a time
fig = go.Figure()
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6], name='Line 1'))
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 3, 4], name='Line 2'))

# Method 2: Pass data to constructor
fig = go.Figure(data=[
    go.Scatter(x=[1, 2, 3], y=[4, 5, 6], name='Line 1'),
    go.Scatter(x=[1, 2, 3], y=[2, 3, 4], name='Line 2')
])
```

## Common Trace Types

### Scatter (Lines and Markers)

```python
fig.add_trace(go.Scatter(
    x=[1, 2, 3, 4],
    y=[10, 11, 12, 13],
    mode='lines+markers',  # 'lines', 'markers', 'lines+markers', 'text'
    name='Trace 1',
    line=dict(color='red', width=2, dash='dash'),
    marker=dict(size=10, color='blue', symbol='circle')
))
```

### Bar

```python
fig.add_trace(go.Bar(
    x=['A', 'B', 'C'],
    y=[1, 3, 2],
    name='Bar Chart',
    marker=dict(color='lightblue'),
    text=[1, 3, 2],
    textposition='auto'
))
```

### Heatmap

```python
fig.add_trace(go.Heatmap(
    z=[[1, 2, 3], [4, 5, 6], [7, 8, 9]],
    x=['A', 'B', 'C'],
    y=['X', 'Y', 'Z'],
    colorscale='Viridis'
))
```

### 3D Scatter

```python
fig.add_trace(go.Scatter3d(
    x=[1, 2, 3],
    y=[4, 5, 6],
    z=[7, 8, 9],
    mode='markers',
    marker=dict(size=5, color='red')
))
```

## Layout Configuration

### Update Layout

```python
fig.update_layout(
    title='Figure Title',
    title_font_size=20,
    xaxis_title='X Axis',
    yaxis_title='Y Axis',
    width=800,
    height=600,
    template='plotly_white',
    showlegend=True,
    hovermode='closest'  # 'x', 'y', 'closest', 'x unified', False
)
```

### Magic Underscore Notation

Compact way to set nested properties:

```python
# Instead of:
fig.update_layout(title=dict(text='Title', font=dict(size=20)))

# Use underscores:
fig.update_layout(
    title_text='Title',
    title_font_size=20
)
```

### Axis Configuration

```python
fig.update_xaxes(
    title='X Axis',
    range=[0, 10],
    showgrid=True,
    gridwidth=1,
    gridcolor='lightgray',
    type='log',  # 'linear', 'log', 'date', 'category'
    tickformat='.2f',
    dtick=1  # Tick spacing
)

fig.update_yaxes(
    title='Y Axis',
    zeroline=True,
    zerolinewidth=2,
    zerolinecolor='black'
)
```

## Updating Traces

```python
# Update all traces
fig.update_traces(
    marker=dict(size=10, opacity=0.7)
)

# Update specific trace
fig.update_traces(
    marker=dict(color='red'),
    selector=dict(name='Line 1')
)

# Update by position
fig.data[0].marker.size = 15
```

## Adding Annotations

```python
fig.add_annotation(
    x=2, y=5,
    text='Important Point',
    showarrow=True,
    arrowhead=2,
    arrowsize=1,
    arrowwidth=2,
    arrowcolor='red',
    ax=40,  # Arrow x offset
    ay=-40  # Arrow y offset
)
```

## Adding Shapes

```python
# Rectangle
fig.add_shape(
    type='rect',
    x0=1, y0=2, x1=3, y1=4,
    line=dict(color='red', width=2),
    fillcolor='lightblue',
    opacity=0.3
)

# Line
fig.add_shape(
    type='line',
    x0=0, y0=0, x1=5, y1=5,
    line=dict(color='green', width=2, dash='dash')
)

# Convenience methods for horizontal/vertical lines
fig.add_hline(y=5, line_dash='dash', line_color='red')
fig.add_vline(x=3, line_dash='dot', line_color='blue')
```

## Figure Structure

Figures follow a tree hierarchy:

```python
fig = go.Figure(data=[trace1, trace2], layout=go.Layout(...))

# Access via dictionary syntax
fig['layout']['title'] = 'New Title'
fig['data'][0]['marker']['color'] = 'red'

# Or attribute syntax
fig.layout.title = 'New Title'
fig.data[0].marker.color = 'red'
```

## Complex Chart Types

### Candlestick

```python
fig.add_trace(go.Candlestick(
    x=df['date'],
    open=df['open'],
    high=df['high'],
    low=df['low'],
    close=df['close'],
    name='Stock Price'
))
```

### Sankey Diagram

```python
fig = go.Figure(data=[go.Sankey(
    node=dict(
        label=['A', 'B', 'C', 'D'],
        color='blue'
    ),
    link=dict(
        source=[0, 1, 0, 2],
        target=[2, 3, 3, 3],
        value=[8, 4, 2, 8]
    )
)])
```

### Surface (3D)

```python
fig = go.Figure(data=[go.Surface(
    z=z_data,  # 2D array
    x=x_data,
    y=y_data,
    colorscale='Viridis'
)])
```

## Working with DataFrames

Build traces from pandas DataFrames:

```python
import pandas as pd

df = pd.DataFrame({
    'x': [1, 2, 3, 4],
    'y': [10, 11, 12, 13]
})

fig = go.Figure()
for group_name, group_df in df.groupby('category'):
    fig.add_trace(go.Scatter(
        x=group_df['x'],
        y=group_df['y'],
        name=group_name,
        mode='lines+markers'
    ))
```

## When to Use Graph Objects

Use graph_objects when:

- Creating chart types not available in Plotly Express
- Building complex multi-trace figures from scratch
- Need precise control over individual components
- Creating specialized visualizations (3D mesh, isosurface, custom shapes)
- Building subplots with mixed chart types

Use Plotly Express when:

- Creating standard charts quickly
- Working with tidy DataFrame data
- Want automatic styling and legends
