Ford Johnson
  • Home
  • Projects
  • Notes
  • About

Making data less intimidating, more insightful.

That journey from raw data to understanding can feel tough. I simplify it by turning complex datasets into visuals and stories that actually make sense. My background in sociology and criminology helps me see the human side of the numbers, so the insights aren’t just data points, they’re real takeaways that can help you make better decisions.

Ford Johnson, Data Analyst.

Visualizing data for impact

Transforming raw data into understandable and actionable visuals. My expertise includes creating interactive dashboards and static graphics using tools such as Tableau, Looker, and R (with ggplot2), ensuring clarity and effective communication of key findings.

Driving growth with web data

Utilizing web analytics tools such as Google Analytics to monitor user engagement, analyze traffic sources, and measure the success of marketing initiatives. I focus on providing data-driven recommendations for website optimization and improved marketing ROI.

Cloud data infrastructure

Experienced in developing and deploying scalable data pipelines and analytical environments on cloud platforms, particularly Google Cloud Platform (GCP). I leverage services like BigQuery, Cloud Storage, and Dataform for robust data processing and insight generation.

d3 = require("d3@6")


data = { 
  const data = {
  "nodes": [
    {"id": "R", "group": 3 },
    {"id": "SQL", "group": 3 },
    {"id": "Python", "group": 3 },
    {"id": "JavaScript", "group": 3 },
    {"id": "HTML", "group": 4 },
    {"id": "CSS", "group": 4 },
    {"id": "SCSS", "group": 4 },
    {"id": "YAML", "group": 4 },
    {"id": "markdown", "group": 4 },
    {"id": "GitHub", "group": 2 },
    {"id": "Git", "group": 2 },
    {"id": "GitHub Actions", "group": 2 },
    {"id": "PostgreSQL", "group": 2 },
    {"id": "BigQuery", "group": 2 },
    {"id": "DataForm", "group": 2 },
    {"id": "tidyverse", "group": 1 },
    {"id": "dplyr", "group": 1 },
    {"id": "rvest", "group": 1 },
    {"id": "tidyr", "group": 1 },
    {"id": "lubridate", "group": 1 },
    {"id": "ggplot2", "group": 1 },
    {"id": "patchwork", "group": 1 },
    {"id": "ggdist", "group": 1 },
    {"id": "ggtext", "group": 1 },
    {"id": "ggextra", "group": 1 },
    {"id": "ggridges", "group": 1 },
    {"id": "ggmagnify", "group": 1 },
    {"id": "cowplot", "group": 1 },
    {"id": "plotly", "group": 1 },
    {"id": "shiny", "group": 1 },
    {"id": "caret", "group": 1 },
    {"id": "htmlwidgets", "group": 1 },
    {"id": "DT", "group": 1 },
    {"id": "leaflet", "group": 1 },
    {"id": "network3D", "group": 1 },
    {"id": "janitor", "group": 1 },
    {"id": "dbplyr", "group": 1 },
    {"id": "RPostgres", "group": 1 },
    {"id": "Quarto", "group": 4 },
    {"id": "React", "group": 1 },
    {"id": "ejs", "group": 4 },
    {"id": "D3", "group": 1 },
    {"id": "Observable Framework", "group": 1 },
    {"id": "SQLite", "group": 1 },
    {"id": "pandas", "group": 1 },
    {"id": "numpy", "group": 1 },
    {"id": "NLTK", "group": 1 },
    {"id": "seaborn", "group": 1 },
    {"id": "fastapi", "group": 1 },
    {"id": "SQLAlchemy", "group": 1 },
    {"id": "BeautifulSoup", "group": 1 },
    {"id": "matplotlib", "group": 1 },
    {"id": "sqlite3", "group": 1 },
    {"id": "psycopg2", "group": 1 },
    {"id": "Google Analytics", "group": 2 },
    {"id": "Search Console", "group": 2 },
  ],
  "links": [
    {"source": "R", "target": "tidyverse", "value": 1},
    {"source": "tidyverse", "target": "dplyr", "value": 1},
    {"source": "tidyverse", "target": "tidyr", "value": 1},
    {"source": "tidyverse", "target": "lubridate", "value": 1},
    {"source": "tidyverse", "target": "rvest", "value": 1},
    {"source": "tidyverse", "target": "ggplot2", "value": 1},
    {"source": "ggplot2", "target": "patchwork", "value": 1},
    {"source": "ggplot2", "target": "ggdist", "value": 1},
    {"source": "ggplot2", "target": "ggtext", "value": 1},
    {"source": "ggplot2", "target": "ggextra", "value": 1},
    {"source": "ggplot2", "target": "ggridges", "value": 1},
    {"source": "ggplot2", "target": "ggmagnify", "value": 1},
    {"source": "ggplot2", "target": "cowplot", "value": 1},
    {"source": "dplyr", "target": "dbplyr", "value": 1},
    {"source": "dbplyr", "target": "RPostgres", "value": 1},
    {"source": "RPostgres", "target": "PostgreSQL", "value": 1},
    {"source": "PostgreSQL", "target": "SQL", "value": 1},
    {"source": "R", "target": "plotly", "value": 1},
    {"source": "R", "target": "shiny", "value": 1},
    {"source": "R", "target": "caret", "value": 1},
    {"source": "R", "target": "htmlwidgets", "value": 1},
    {"source": "htmlwidgets", "target": "DT", "value": 1},
    {"source": "htmlwidgets", "target": "leaflet", "value": 1},
    {"source": "htmlwidgets", "target": "network3D", "value": 1},
    {"source": "R", "target": "janitor", "value": 1},
    {"source": "R", "target": "Quarto", "value": 1},
    {"source": "JavaScript", "target": "Quarto", "value": 1},
    {"source": "JavaScript", "target": "leaflet", "value": 1},
    {"source": "JavaScript", "target": "React", "value": 1},
    {"source": "JavaScript", "target": "HTML", "value": 1},
    {"source": "JavaScript", "target": "ejs", "value": 1},
    {"source": "JavaScript", "target": "D3", "value": 1},
    {"source": "JavaScript", "target": "plotly", "value": 1},
    {"source": "JavaScript", "target": "Observable Framework", "value": 1},
    {"source": "HTML", "target": "Quarto", "value": 1},
    {"source": "HTML", "target": "ejs", "value": 1},
    {"source": "YAML", "target": "Quarto", "value": 1},
    {"source": "CSS", "target": "Quarto", "value": 1},
    {"source": "SCSS", "target": "Quarto", "value": 1},
    {"source": "markdown", "target": "Quarto", "value": 1},
    {"source": "GitHub", "target": "Git", "value": 1},
    {"source": "GitHub", "target": "GitHub Actions", "value": 1},
    {"source": "GitHub Actions", "target": "YAML", "value": 1},
    {"source": "Python", "target": "plotly", "value": 1},
    {"source": "Python", "target": "pandas", "value": 1},
    {"source": "Python", "target": "numpy", "value": 1},
    {"source": "Python", "target": "Quarto", "value": 1},
    {"source": "Python", "target": "NLTK", "value": 1},
    {"source": "Python", "target": "seaborn", "value": 1},
    {"source": "Python", "target": "fastapi", "value": 1},
    {"source": "fastapi", "target": "SQLAlchemy", "value": 1},
    {"source": "Python", "target": "BeautifulSoup", "value": 1},
    {"source": "Python", "target": "matplotlib", "value": 1},
    {"source": "SQLAlchemy", "target": "sqlite3", "value": 1},
    {"source": "sqlite3", "target": "SQLite", "value": 1},
    {"source": "SQLAlchemy", "target": "psycopg2", "value": 1},
    {"source": "psycopg2", "target": "PostgreSQL", "value": 1},
    {"source": "SQL", "target": "BigQuery", "value": 1},
    {"source": "BigQuery", "target": "DataForm", "value": 1},
    {"source": "BigQuery", "target": "JavaScript", "value": 1},
    {"source": "DataForm", "target": "JavaScript", "value": 1},
    {"source": "SQL", "target": "SQLite", "value": 1},
    {"source": "BigQuery", "target": "Google Analytics", "value": 1},
    {"source": "BigQuery", "target": "Search Console", "value": 1}
  ]
}
return data
};



chart = {
  const links = data.links.map(d => Object.create(d));
  const nodes = data.nodes.map(d => Object.create(d));

  const simulation = d3.forceSimulation(nodes)
      .force("link", d3.forceLink(links).id(d => d.id).distance(70))
      .force("charge", d3.forceManyBody().strength(-420))
      .force("x", d3.forceX())
      .force("y", d3.forceY())
      .force("collide", d3.forceCollide(d => {
        const textLength = d.id.length * 6; // Adjust based on font
        return Math.max(18, textLength / 2); // Leave space for labels
      }));

  const svg = d3.create("svg")
      .attr("viewBox", [-width / 2, -height / 2, width, height])
      .style("font", "16px sans-serif") // Slightly smaller text
      .style("overflow", "visible");

  const link = svg.append("g")
    .selectAll("line")
    .data(links)
    .join("line")
      .attr("stroke", "#999")
      .attr("stroke-opacity", 0.6)
      .attr("stroke-width", d => Math.sqrt(d.value));

  const node = svg.append("g")
      .attr("fill", "currentColor")
      .attr("stroke-linecap", "round")
      .attr("stroke-linejoin", "round")
    .selectAll("g")
    .data(nodes)
    .join("g")
      .call(drag(simulation));

  node.append("circle")
      .attr("fill", color)
      .attr("stroke", "black")
      .attr("stroke-width", 1.5)
      .attr("r", 8); // slightly larger circle

  node.append("text")
      .attr("x", 10)
      .attr("y", "0.31em")
      .text(d => d.id)
    .clone(true).lower()
      .attr("fill", "none")
      .attr("stroke", "white")
      .attr("stroke-width", 3);

  simulation.on("tick", () => {
    node.attr("transform", d => `translate(${d.x},${d.y})`);
    link
      .attr("x1", d => d.source.x)
      .attr("x2", d => d.target.x)
      .attr("y1", d => d.source.y)
      .attr("y2", d => d.target.y);
  });

  invalidation.then(() => simulation.stop());

  return svg.node();
}


height = 1000

color = {
  const scale = d3.scaleOrdinal(d3.schemeCategory10);
  return d => scale(d.group);
}

drag = simulation => {
  
  function dragstarted(event) {
    if (!event.active) simulation.alphaTarget(0.3).restart();
    event.subject.fx = event.subject.x;
    event.subject.fy = event.subject.y;
  }
  
  function dragged(event) {
    event.subject.fx = event.x;
    event.subject.fy = event.y;
  }
  
  function dragended(event) {
    if (!event.active) simulation.alphaTarget(0);
    event.subject.fx = null;
    event.subject.fy = null;
  }
  
  return d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended);
}

Beyond Data

When I’m not working with data, I’m probably out hiking, trying to capture the perfect shot with my camera, or learning to golf. I love exploring the world and finding new perspectives, whether it’s through travel, photography, or just enjoying the great outdoors.