Skip to content

Commit

Permalink
add interative VitePress homepage
Browse files Browse the repository at this point in the history
  • Loading branch information
jbloom committed Dec 10, 2024
1 parent cac6adc commit 3babf4f
Show file tree
Hide file tree
Showing 80 changed files with 418,615 additions and 1 deletion.
35 changes: 35 additions & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs

name: Deploy VitePress site to Pages

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [20.x]

steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm ci
- run: npm run docs:build
- name: Deploy
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v4
with:
# Change to your GitHub Pages repository
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: homepage/.vitepress/dist
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,10 @@ results/dms-viz/*
!results/escape_logos
results/escape_logos/*
!results/escape_logos/*.svg

!.github
node_modules/
!homepage/.vitepress/
homepage/.vitepress/cache/
homepage/.vitepress/dist/
!homepage/public/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This repo contains data and analyses from deep mutational scanning experiments o
The deep mutational scan only consists of the ectodomain of Rabies G and few sites flanking the ectodomain.
Consistent with the rabies literature, the sites are numbering using the scheme were 1 is assigned to the first site of the ectodomain, not the first site of the protein itself.

For analysis and documentation of the study, navigate to [https://dms-vep.org/RABV_Pasteur_G_DMS/](https://dms-vep.org/RABV_Pasteur_G_DMS/).
For user-friendly links to interactive visualization of the data and key numerical results, see [https://dms-vep.org/RABV_Pasteur_G_DMS/](https://dms-vep.org/RABV_Pasteur_G_DMS/).

## Organization of this repo

Expand Down
6 changes: 6 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
pipeline_path: dms-vep-pipeline-3 # typically will be `dms-vep-pipeline` for real pipelines
docs: docs # typically will be `docs` for real pipelines

# ----------------------------------------------------------------------------
# Build VitePress homepage
# ----------------------------------------------------------------------------
homepage: homepage/public
build_vitepress_homepage: true

# ----------------------------------------------------------------------------
# Details on repo, used for docs. Change this to details for your project.
# ----------------------------------------------------------------------------
Expand Down
22 changes: 22 additions & 0 deletions homepage/.vitepress/config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { defineConfig } from "vitepress";

// https://vitepress.dev/reference/site-config
export default defineConfig({
lang: "en-US",
title: "Deep mutational scanning of rabies G (Pasteur strain)",
description:
"Pseudovirus deep mutational scanning to measure how rabies G mutations affect cell entry and escape from a panel of monoclonal antibodies",
base: "/RABV_Pasteur_G_DMS/",
appearance: false,
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: "Home", link: "/" },
{ text: "Appendix", link: "/appendix", target: "_self" },
],
socialLinks: [{ icon: "github", link: "https://github.com/dms-vep/RABV_Pasteur_G_DMS" }],
footer: {
message: 'Study by Arjun Aditham, Caelan Radford, Caleb, Carr, & <a href="https://jbloomlab.org">Jesse Bloom</a>',
},
},
});
177 changes: 177 additions & 0 deletions homepage/.vitepress/theme/Altair.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<script>
import * as vega from 'vega';
import * as vegaLite from 'vega-lite';
import vegaEmbed from 'vega-embed';
import { parseVegaSpecFromHTML } from './parseVegaSpec';
export default {
name: 'VegaChart',
props: {
specUrl: {
type: String,
required: true,
},
showShadow: {
type: Boolean,
default: true,
}
},
data() {
return {
isExpanded: false,
};
},
async mounted() {
await this.loadChart(this.specUrl);
},
methods: {
// Parse and load the chart from the URL
async loadChart(url) {
try {
let spec;
// Determine if the URL is local or remote
if (!url.startsWith('http://') && !url.startsWith('https://')) {
const htmlContent = await this.fetchLocalFile(url);
spec = await parseVegaSpecFromHTML(htmlContent);
} else {
// Get the path to check the file format
const urlObj = new URL(url);
const path = urlObj.pathname;
// Get the response from the URL
const response = await fetch(url);
// Check if URL ends with '.html'
if (path.endsWith('.html')) {
const htmlContent = await response.text();
spec = await parseVegaSpecFromHTML(htmlContent);
}
// Check if URL ends with '.json'
else if (path.endsWith('.json')) {
spec = await response.json();
}
else {
console.error('Unsupported file format');
return;
}
}
this.renderChart(spec);
} catch (error) {
console.error('Error loading Vega spec:', error);
}
},
// Fetch the text from local files
async fetchLocalFile(filePath) {
const response = await fetch(filePath);
if (!response.ok) throw new Error('Failed to fetch local file');
console.log('response.text()', response);
return await response.text();
},
// Render the chart using VegaEmbed
renderChart(spec) {
vegaEmbed(this.$refs.vegaContainer, spec, {
renderer: 'canvas',
vega,
vegaLite,
actions: false,
}).then((result) => {
console.log('Chart rendered successfully');
}).catch(console.error);
},
// Toggle the expanded view
toggleExpand() {
this.isExpanded = !this.isExpanded;
},
},
};
</script>

<template>
<div class="vega-chart-container" :class="{ 'expanded': isExpanded, 'no-box-shadow': !showShadow }">
<div ref="vegaContainer">
</div>
<button @click="toggleExpand" @keydown.esc="isExpanded = false" class="expand-btn">
<i v-if="isExpanded" class="bi bi-arrows-angle-contract"></i>
<i v-else class="bi bi-arrows-angle-expand"></i>
</button>
</div>
</template>

<style>
.vega-chart-container {
width: 100%;
height: auto;
overflow: auto;
padding: 20px;
position: relative;
background-color: white;
line-height: 0%;
}
.vega-chart-container:not(.no-box-shadow) {
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.5);
}
.expanded {
width: 100vw;
height: 100vh;
position: fixed !important;
top: 0;
left: 0;
z-index: 100;
padding: 1%;
}
.expand-btn {
position: absolute;
top: 10px;
right: 10px;
outline: none !important;
width: 30px;
height: 30px;
border-radius: 5px;
border: 1px solid #aaa;
display: flex;
justify-content: center;
align-items: center;
}
.bi {
font-size: 20px;
}
/* Form container */
.vega-bindings {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px;
}
.vega-bind-name {
flex-basis: 20%;
text-align: left;
font-weight: bold;
margin-right: 5px;
}
.vega-bindings select {
border: 1px solid #ccc;
border-radius: 4px;
background-color: #fff;
text-align: center;
}
.vega-bindings input[type="select"] option {
text-align: center;
font-size: 14px;
}
.vega-bindings input[type="radio"] {
margin: 5px 5px 0px 10px;
}
</style>
31 changes: 31 additions & 0 deletions homepage/.vitepress/theme/Figure.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script>
export default {
props: {
imageSrc: {
type: String,
default: ''
},
caption: {
type: String,
required: true
}
},
};
</script>

<template>
<figure>
<img v-if="imageSrc" :src="imageSrc" :alt="caption" />
<slot v-else></slot>
<figcaption>{{ caption }}</figcaption>
</figure>
</template>

<style scoped>
figcaption {
color: grey;
padding-top: 0.5em;
font-size: 0.8em;
text-align: center;
}
</style>
19 changes: 19 additions & 0 deletions homepage/.vitepress/theme/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { h } from "vue";
import DefaultTheme from "vitepress/theme";
import Altair from "./Altair.vue";
import Figure from "./Figure.vue";
import "bootstrap-icons/font/bootstrap-icons.css";
import "./style.css";

export default {
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {

});
},
enhanceApp({ app, router, siteData }) {
app.component("Altair", Altair);
app.component("Figure", Figure);
},
};
38 changes: 38 additions & 0 deletions homepage/.vitepress/theme/parseVegaSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export async function parseVegaSpecFromHTML(htmlContent) {
// Use the browser's DOMParser to parse the HTML content
const parser = new DOMParser();
const doc = parser.parseFromString(htmlContent, "text/html");

// Locate the script tag containing the Vega spec
const scripts = doc.querySelectorAll("script");
let vegaSpecString = "";

scripts.forEach((script) => {
if (script.textContent.includes("var spec =")) {
vegaSpecString = script.textContent;
}
});

if (!vegaSpecString) {
throw new Error("Vega spec not found in the HTML document.");
}

// Locate the JSON part after "var spec ="
const specStart = vegaSpecString.indexOf("var spec =") + "var spec =".length;
const specEnd = vegaSpecString.indexOf("};", specStart) + 1; // +1 to include the closing brace
const jsonSubstring = vegaSpecString.substring(specStart, specEnd);

// Trim any leading or trailing non-JSON characters (like semicolons or whitespace)
const trimmedJsonString = jsonSubstring.trim();

// Parse the JSON string into an object
let vegaSpecObject;
try {
vegaSpecObject = JSON.parse(trimmedJsonString);
} catch (error) {
console.log(trimmedJsonString);
throw new Error("Error parsing Vega spec JSON: " + error.message);
}

return vegaSpecObject;
}
Loading

0 comments on commit 3babf4f

Please sign in to comment.