Circuitry for Jupyter Notebook Users

If you're coming from Jupyter notebooks, Circuitry offers a familiar yet more powerful approach to data analysis, visualization, and automation. This guide will help you transition from notebook-based workflows to visual workflow automation.

Already have notebooks? File → Import… converts a .ipynb file directly into a Circuitry CodeBook — see Import from Other Tools.

From Notebooks to Visual Workflows

Key Differences

Jupyter NotebooksCircuitry
Linear cell executionVisual node-based workflows
Manual cell-by-cell runsAutomated end-to-end execution
Code in cellsCode in dedicated Code nodes
One kernel, one language per notebookPer-cell language — Python, JavaScript, TypeScript, Go, Rust, C, C++ or Zig in the same document
Individual plotsChart, DataViz, and StatPlot nodes
Manual data passingAutomatic data flow between nodes
Single environmentMultiple execution contexts

One notebook, many languages

The difference worth dwelling on is the language row. A notebook is normally tied to one kernel, so the language you start in is the language you finish in. In a CodeBook every code cell picks its own, and cells pass data to each other regardless of what they're written in.

That changes who a notebook is for. If you work in Go, Rust, C, C++, Zig, JavaScript or TypeScript, you no longer have to translate your thinking into Python to get a notebook's benefits — you can explore, measure and narrate in the language you're actually productive in, and still drop into Python for the parts where its libraries are the reason you'd reach for it.

The trade-off is scope: Python cells share one persistent state, so variables carry from cell to cell. Cells in the other languages chain by data instead — each takes the previous cell's output as its input. See the CodeBook Tour for how that works in practice.

Core Concepts for Jupyter Users

1. Code Nodes Replace Cells

Instead of notebook cells, Circuitry uses Code nodes, and each one chooses its own language — Python, JavaScript, TypeScript, Go, Rust, C, C++ or Zig:

  • Python and JavaScript run on the device you're working on, with nothing to install
  • Pre-loaded Python libraries: NumPy, Pandas, Matplotlib, SciPy, Scikit-learn
  • Additional packages via micropip
  • Go, Rust, C, C++ and Zig are compiled the first time you run them and after each change — see Code nodes
  • The previous step's value arrives as input["value"], and a cell's last line is its value, as in a notebook — or return it explicitly

Example: Converting a Jupyter Cell

Jupyter Notebook:

import pandas as pd
import numpy as np

# Load data
data = pd.read_csv('data.csv')

# Process data
data['processed'] = data['value'] * 2
result = data.groupby('category').mean()
print(result)

Circuitry Code Node:

import pandas as pd
import numpy as np

# Input data flows from the previous node
data = pd.DataFrame(input["value"])

# Process data
data['processed'] = data['value'] * 2

# The last line is the value passed to the next node, as in a notebook
data.groupby('category').mean()

2. Visualization Nodes Replace Inline Plots

Circuitry provides three specialized visualization nodes that replace matplotlib/seaborn plots in notebooks:

Chart Node (Matplotlib-based)

  • Best for: Traditional statistical charts (bar, line, scatter, histogram, pie)
  • Configuration: JSON-based with chart type, data columns, styling
  • Output: Base64 PNG images

DataViz Node (Plotly-based)

  • Best for: Interactive visualizations (heatmaps, 3D plots, dashboards)
  • Configuration: Plotly.js compatible settings
  • Output: Interactive plots and static images

StatPlot Node (Seaborn-based)

  • Best for: Statistical analysis plots (regression, distributions, correlations)
  • Configuration: Seaborn style and palette options
  • Output: Publication-ready statistical plots

3. Data Flow Instead of Variable Passing

In Jupyter, you manually pass variables between cells. In Circuitry, data flows automatically:

Jupyter approach:

# Cell 1
raw_data = load_data()

# Cell 2
cleaned_data = clean_data(raw_data)

# Cell 3
plot_data(cleaned_data)

Circuitry approach:

  • Node 1: Load data → outputs raw_data
  • Node 2: Clean data (receives raw_data as input["value"]) → outputs cleaned_data
  • Node 3: Plot data (receives cleaned_data as input["value"])

In a CodeBook, Python cells also share one set of variables, so raw_data made in one cell is still there in the next — the notebook habit works as-is. Data flow is what carries a value into a cell written in another language, or into the next node of a workflow.

Migration Patterns

Pattern 1: Data Loading and Processing

Before (Jupyter):

# Cell 1: Load
import pandas as pd
df = pd.read_csv('sales.csv')

# Cell 2: Clean
df = df.dropna()
df['date'] = pd.to_datetime(df['date'])

# Cell 3: Analyze
monthly_sales = df.groupby(df['date'].dt.month).sum()

After (Circuitry):

  1. HTTP Request Node: Fetch CSV data from API
  2. Code Node (Data Cleaning):
    import pandas as pd
    df = pd.DataFrame(input["value"])
    df = df.dropna()
    df['date'] = pd.to_datetime(df['date'])
    df
    
  3. Code Node (Analysis):
    import pandas as pd
    df = pd.DataFrame(input["value"])
    df.groupby(df['date'].dt.month).sum()
    

Pattern 2: Visualization Workflows

Before (Jupyter):

# Analysis cell
correlation_matrix = df.corr()

# Plotting cell
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(10, 8))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm')
plt.title('Feature Correlations')
plt.show()

After (Circuitry):

  1. Code Node (Analysis):

    import pandas as pd
    df = pd.DataFrame(input["value"])
    df.corr()
    
  2. StatPlot Node Configuration:

    {
      "plotType": "heatmap",
      "title": "Feature Correlations",
      "width": 10,
      "height": 8,
      "style": "whitegrid",
      "customCode": "sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm')"
    }
    

Pattern 3: Machine Learning Pipelines

Before (Jupyter):

# Data prep
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# Training
model = RandomForestClassifier()
model.fit(X_train, y_train)

# Evaluation
predictions = model.predict(X_test)
accuracy = accuracy_score(y_test, predictions)
print(f"Accuracy: {accuracy}")

After (Circuitry):

  1. Code Node (Data Split):

    from sklearn.model_selection import train_test_split
    import pandas as pd
    
    df = pd.DataFrame(input["value"])
    X = df.drop('target', axis=1)
    y = df['target']
    
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
    
    {
      'X_train': X_train.to_dict('records'),
      'X_test': X_test.to_dict('records'),
      'y_train': y_train.tolist(),
      'y_test': y_test.tolist()
    }
    
  2. Code Node (Training):

    from sklearn.ensemble import RandomForestClassifier
    import pandas as pd
    
    data = input["value"]
    X_train = pd.DataFrame(data['X_train'])
    y_train = data['y_train']
    
    model = RandomForestClassifier()
    model.fit(X_train, y_train)
    
    # In practice, you'd serialize the model
    {
      'model_trained': True,
      'X_test': data['X_test'],
      'y_test': data['y_test']
    }
    
  3. Code Node (Evaluation):

    # Evaluate model and create results
    accuracy = 0.95  # placeholder
    {'accuracy': accuracy}
    

Advanced Features for Data Scientists

1. Template Variables

Use {{variable}} syntax to make workflows dynamic:

# Code node with template variables
model_type = "{{model_type}}"  # Set via environment variables
test_size = {{test_size}}      # Numeric templates

if model_type == "random_forest":
    from sklearn.ensemble import RandomForestClassifier
    model = RandomForestClassifier()

2. Conditional Workflows

Use Condition nodes for branching logic:

  • If data quality score > 0.8 → Continue with complex model
  • Else → Use simple baseline model

3. Parallel Processing

Use Fork/Join nodes for parallel execution:

  • Fork: Split data for multiple model training
  • Join: Combine results for ensemble methods

4. Automation

Unlike notebooks, Circuitry workflows can be:

  • Triggered by webhooks for real-time data processing
  • Scheduled for batch processing
  • Chained with other workflows
  • Deployed as APIs

Best Practices for Migration

1. Start Small

  • Begin with simple data loading + visualization workflows
  • Gradually add complexity with conditions and loops
  • Practice data flow concepts before complex logic

2. Leverage Code Nodes

  • Use Code nodes for complex Python logic
  • Keep visualization in Chart/DataViz/StatPlot nodes
  • Break large notebook cells into focused nodes

3. Design for Reusability

  • Create modular workflows that can be called from other workflows
  • Use template variables for configurable parameters
  • Document node purposes with clear names

4. Test Data Flow

  • Use the execution output panel to verify data passing
  • Add debug Code nodes to inspect intermediate results
  • Leverage the visual workflow graph to understand dependencies

5. Optimize for Performance

  • Use Image nodes to display static plots without re-execution
  • Cache expensive computations in separate nodes
  • Consider breaking long-running processes into smaller nodes

Common Gotchas

1. What a Cell Passes On

A cell's value is its last line when that line is an expression, exactly as in Jupyter — or whatever you return at the top level. print() shows output but is not passed on. Values travel between cells and nodes as data, so a DataFrame is passed on as a list of rows (one record per row), and the next cell reads it with input["value"]:

# Cell 1
df = load_data()
df            # ✅ the value: passed on as a list of records

# Cell 2
rows = input["value"]
pd.DataFrame(rows)

One thing to avoid: a dictionary with its own "value" key is taken for the value itself and unwrapped. Pick another name for that key.

2. Plots

plt.show() shows every open figure in the cell's output, as in a notebook. So does ending a cell on a plotting call (plt.plot(x, y), df.plot()) or on the figure itself. %matplotlib inline is not needed; the line is accepted and does nothing.

import matplotlib.pyplot as plt

plt.plot([1, 4, 9, 16])
plt.title("Squares")
plt.show()

To hand the picture to the next cell instead (an Image cell directly below draws it), save the figure to PNG and end the cell with it:

import io, base64
import matplotlib.pyplot as plt

df.plot()

buf = io.BytesIO()
plt.savefig(buf, format="png", bbox_inches="tight")
plt.close()
"data:image/png;base64," + base64.b64encode(buf.getvalue()).decode()

Matplotlib — Pictures from Numbers explains the frame; the CodeBook tutorial uses it end to end.

3. Library Imports

Install a package the way you would in a notebook. !pip install and %pip install work in every interactive run: in the browser, on a computer with Circuit, and on iPhone and iPad:

!pip install seaborn
import seaborn as sns

Each package installs the first time and is already there on later runs. On iPhone and iPad, a package that needs compiled parts runs on the page's Python automatically. To show charts and tables from a Python file (fig.show(), plt.show(), display(df)), see Charts and Tables. More on installing: Installing Python Packages.

4. File Handling

Browser-based execution has different file access patterns:

# Instead of local file reading
data = pd.read_csv('local_file.csv')  # ❌ May not work

# Use HTTP requests or pass data via workflow
data = pd.DataFrame(input["value"])  # ✅ Works

5. display() and IPython

The line most notebooks start with works:

from IPython.display import display, HTML, Markdown, Math, Image, clear_output

display(df)                          # a table
display(Markdown("## Results"))      # formatted text
display(Math(r"e^{i\pi} + 1 = 0"))   # typeset maths
display(Image(filename="chart.png", width=400))

What is displayed appears in the cell's output, in order with what the cell prints. display() and get_ipython() also work without the import.

  • The last line is shown too. A DataFrame, a figure, a sympy expression or HTML(...) on a cell's last line appears as its table, picture or formula, and is still the value the cell passes on.
  • Available: display, update_display and display_id, clear_output, HTML, Markdown, Math, Latex, Image, SVG, JSON, Audio, Video, IFrame, YouTubeVideo, Code, Pretty, ProgressBar.
  • HTML runs sealed off. Markup a program displays is shown in a frame of its own that cannot reach your documents or your account. Some sites refuse to appear inside an IFrame; the link under it opens the page.
  • Not available: Javascript(...) does not run (its place says so), and there is no ipywidgets.
  • Magics: %matplotlib inline, %load_ext, %autoreload and %config are accepted and do nothing; %time and %timeit run their statement once. Shell lines (!ls), %%bash and other magics do not run: remove them or write the Python for them. !pip install is the exception, see above.

Resources for Learning

  1. Your First CodeBook - Every kind of cell, step by step, in Python
  2. Code Node Guide - Detailed Code node and Python execution documentation
  3. Template Variables - Dynamic workflow configuration
  4. Workflow Patterns - Common workflow designs

Example Notebooks to Workflows

Data Analysis Workflow

Goal: Analyze sales data and create dashboard

Jupyter: 15+ cells with manual execution Circuitry: 6 connected nodes with automatic execution

  • Data loading → Cleaning → Analysis → Multiple visualizations → Dashboard

ML Model Training

Goal: Train and evaluate classification model

Jupyter: Manual feature engineering, training, evaluation Circuitry: Automated pipeline with parallel model comparison

  • Data prep → Feature engineering → Fork (multiple models) → Join → Best model selection

The transition from Jupyter notebooks to Circuitry workflows opens up new possibilities for automation, collaboration, and production deployment of your data science work.