Convert your favorite whitewater river into an interactive guide.
This project is a front-end React App, hosted using Jamstack on Netlify. Figma is an essential application for the the design component of the project, but only a limited design skillset is necessary.
- React - The web framework used
- Node.js - Used for CLI Tools
- Sass - CSS extension language
- Figma - The design tool used
- Netlify - Back-end using Jamstack
The project is rendered from information stored in an array (data
) and an object (global
) that will contain the entirety of the river data. Monitor changes made in the code editor by running a live server.
Upon initialization of the river (explained below), these are located in src\river-data\[your-river]\[YourRiverData].js
.
An array of objects, each object representing one distinct rapid section.
const data = [ // An array of rapids
{
// All information about a single rapid
},
...
]
An object that holds information relevant to the entire river.
export const global = {
// All information that pertains to the entire river
}
Use the following commands in your terminal to run a live server of the project.
git clone https://github.com/JeffThorslund/Ottawa-River-Paddling-Guide.git
cd [local repository]
npm install
npm start
If you see the website running in a live server, continue to the next step.
The first step is to initialize a river.
In the terminal, run the following CLI tool in your root directory:
addRiver
Follow prompts.
Expected Result: Template directory created in src/river-data/your-river
. River should render in the list of availible rivers on live server.
In the live server, click on your river, then the "Let's Paddle" button on the "Welcome" page. This will bring you to a template rapid, which is an element of the data
array. Try clicking on the waves, lines, eddies and icons. Adjust the water level slider. Check out the overview map.
Let's manipulate the template element to create our first rapid.
Open the following in your code editor:
src\river-data\[your-river]\data.js
Going forward, let x represent a rapid being rendered in the array.
The most common name of the rapid.
const data = [
{
name: "Fish Hook Rapid", //ex. "Dragon's Tooth", "Zoar Gap" etc.
},
]
A short description of the rapid or just the class.
const data = [
{
name: "Fish Hook Rapid",
desc: "Class III", //ex. "Class 2", "Class IV+", "Class Five"
},
]
Expected Result: Title and description of rapid will be updated.
Golden Rule: Always design on a 1600x900 frame in Figma.
In the Overview frame, place a screenshot of Bing satellite image so that the entire river fits in the frame and set the layer to 60% opacity. This will be your tracing outline.
Use the pen tool to draw a trace of the river, ending up with a long thin outline of the river. Set fill to blue.
Set the satellite image to hidden.
Draw a 1600x900 rectangle behind the river trace. Set fill to green.
Select the entire frame in Figma in the Layers panel on the right. Right click on Overview. Select Copy As SVG.
Go to src/river-data/[your-river]/basemaps/OverviewMap.js
Delete the existing SVG element.
Paste your copied SVG element.
Save.
Import this map into the main data file by adding the following to the imports.
import OverviewMap from "./basemaps/OverviewMaps.js";
Set global.overviewMap
property to your newly added map.
global.overviewMap = OverviewMap
Expected Result: Clicking the "Show Map" toggle on the live server should create a pop-up with your map.
We will add the first rapid of the river.
Create a 1600x900 Figma frame and paste a screenshot of a satellite image of the rapid of choice. Remeber to leave room at the top for the title, and space for the Display.js box.
Use the pen tool to draw a trace of the river, ending up with an outline of the river. Set fill to blue.
Set the satellite image to hidden.
Draw a 1600x900 rectangle behind the river trace. Set fill to green.
Select the entire frame in Figma in the Layers panel on the right. Right click on the the frame layer that you built your rapid in. Select Copy As SVG.
Create a copy of src/river-data/[your-river]/basemaps/TemplateMap.js
and name the new file.
Paste your copied <svg>
element between the <g> </g>
tags.
Delete the <svg>
wrapper element.
Your file should have a similar structure to the following.
import React from "react";
const RapidName = (
<g>
<g>
<rect /> //rectangle elements
<path />
<mask />
</g>
<defs>
<linearGradient>
<stop/>
</defs>
</g>
);
export default RapidName;
Import this map into the main data file by adding the following to the imports.
import RapidName from "./basemaps/RapidName.js";
Set data.riverMap.path
property to your newly added map.
data.riverMap.path = RapidName
Expected Result: The map of your first rapid should reflect your new map.
This object indicates where the display panel (Display.js
) it located. As with most positionable elements, the units for vertical distances are viewport height (vh
) and horizontal distances are viewport width(vw
). The object has the following keys: top, left, width
. The height
auto adjusts based on its contents.
const Data = [
{
...
desc: "Class III",
displayPosition: {
top: "70vh", // the position from the top of the viewport
left: "22vw", // the position from the left side of the viewport
width: "35vw", //the width of Display.js
},
},
]
This object holds the rapid base map and information on how it is viewed and scaled.
As all features are rendered into a single SVG element, we have control over the viewBox attribute. This can be used to make small adjustments to the view of the rapid. All elements will scale and tranpose proportionally to viewBox changes. This is a resource for learning viewBox.
The base map is vector map drawn in at a px ratio of 1600:900. The default <svg>
element is removed and everything is wrapped into a <g>
element, and saved as a JSX element. It will look similar to this.
import React from "react";
const AngelsKissRapid = (
<g>
//contents of your basemap
<g/>
)
export default AngelsKissRapid
This, and all other basemaps is saved in ./components/VectorAssets/Basemaps/FishHookRapid
. Import the JSX element into Data.js
.
import React from 'react'
import FishHookRapid from "./components/VectorAssets/Basemaps/FishHookRapid"
const Data = [
{
...
displayPosition: {...},
riverMap: {
viewBox: "0 0 1600 900", //default value
path: FishHookRapid
}
},
]
This is an array of objects, each representing a wave/hole (now called hydraulic) in the rapid.
Let y represent a wave/hole in a rapid. The properties in the Data[x].hydraulics[y]
object allow you manipulate the name, description, position, size, rotation, and water level of the hydraulic.
Key | Value |
---|---|
Data[x].hydraulics[y].name (string) |
Name of the hydraulic |
Data[x].hydraulics[y].desc (string) |
Description |
Data[x].hydraulics[y].top (string) |
Distance from top of viewport |
Data[x].hydraulics[y].left (string) |
Distance from left of viewport |
Data[x].hydraulics[y].height (string) |
Size of wave from river left to river right |
Data[x].hydraulics[y].width (string) |
Size of wave from upstream to downstream |
Data[x].hydraulics[y].rotation (integer) |
Rotation in degrees. |
Data[x].hydraulics[y].range (array) |
Lowest level and highest level that feature is present |
const Data = [
{
...
riverMap: {...},
hydraulics: [
{
name: "Phil's Hole",
desc:
"Phil's Hole is the first hole on the Ottawa River at the top of McCoys.,
top: "463.55",
left: "589.11",
height: "58.73",
width: "13.44",
rotation: 1,
range: [-10, 13],
},
{...}, // another wave in the rapid
]
},
]
This is an array of objects, each representing a eddy in the rapid.
Let y represent a eddy in a rapid. The properties in the Data[x].eddys[y]
object allow you manipulate the name, description, vector path, position, and water level of the eddy.
Key | Value |
---|---|
Data[x].eddys[y].name (string) |
Name of the eddy |
Data[x].eddys[y].desc (string) |
Description |
Data[x].eddys[y].vector (string) |
These shapes are best drawn in an external vector editing software, but can also be drawn manually with using SVG path notation. |
Data[x].eddys[y].x (string) |
Distance from left of viewport |
Data[x].eddys[y].y (string) |
Distance from bottom of viewport |
Data[x].eddys[y].range (array) |
Lowest level and highest level that eddy is present |
const Data = [
{
...
hydraulics: [
{...},
],
eddys: [
{
name: "Football Eddy",
desc:
"A large eddy that can sometimes collect gear and swimmmers. This can sometimes be hard to get out of.",
vector:
"M57.7367 100.472C112.332 100.472 150.868 97.0028 157.209 77.01C164.801 53.0702 161.941 37.64 106.045 30.2237C36.1735 20.9535 8.56346 41.3981 12.9053 63.0286C17.2471 84.6591 30.113 100.472 57.7367 100.472Z",
x: "725",
y: "250",
range: [-10, 10],
},
{...}, // another wave in the rapid
],
}
]
This is an array of objects, each representing a line in the rapid.
Let y represent a line in a rapid. The properties in the Data[x].lines[y]
object allow you manipulate the name, description, vector path, and water level of the eddy.
Key | Value |
---|---|
Data[x].lines[y].name (string) |
Name of the eddy |
Data[x].lines[y].desc (string) |
Description |
Data[x].lines[y].vector (string) |
These shapes are best drawn in an external vector editing software, but can also be drawn manually with using SVG path notation. |
Data[x].lines[y].x (string) |
Distance from left of viewport |
Data[x].lines[y].y (string) |
Distance from bottom of viewport |
Data[x].lines[y].range (array) |
Lowest level and highest level that eddy is present |
const Data = [
{
...
eddys: [
{...},
],
lines: [
{
name: "Thread The Needle",
desc:
"A commonly taken line through McCoys. Start center-right coming into the rapid with your boat pointed slightly left. When approaching the Sattlers, paddle towards river left, clip Sattlers and paddle for your life away from Phils",
vector: (
<path
d="
M 150,455
q 300,58 500,-10
Q 1000,340 1180,800
"
/>
),
range: [-100, 100],
},
]
}
]
This is an array of objects, each representing a symbol in the rapid. There are several symbols to choose from
Symbol Name | Image |
---|---|
"Caution" | ---- |
"Portage" | ----- |
Let y represent a line in a rapid.
Key | Value |
---|---|
Data[x].symbols[y].name (string) |
Symbol name, as described above. |
Data[x].symbols[y].desc (string) |
Add information about why the symbol is there. |
Data[x].symbols[y].top (string) |
Distance from top of viewport |
Data[x].symbols[y].left (string) |
Distance from left of viewport |
const Data = [
{
...
lines: [
{...},
],
symbols: [
{
type: "Caution",
desc:
"There is no safe way to navigate this rapid at this water level.",
top: "200",
left: "200",
},
{...},
]
}
]
This is an array of objects, each representing an "arrow" pointing to the next rapid or the previous rapid in the river.
Let y represent a line in a rapid.
Key | Value |
---|---|
Data[x].arrows[y].name (string) |
The name of the target rapid, as written in its Data[x].name . |
Data[x].arrows[y].bottom (string) |
Distance from bottom of viewport, in vh |
Data[x].arrows[y].right (string) |
Distance from right of viewport, in vw |
const Data = [
{
...
symbols: [
{...},
],
arrows: [
{
name: "Iron Ring",
rotation: "160deg",
bottom: "4vh",
right: "5vw",
}
{...},
]
}
]
This is an array of objects, each representing a label that appears on the pop up river map.
Let y represent a line in a rapid.
Key | Value |
---|---|
Data[x].mapLabel[y].titleTop (string) |
Distance of label from top of viewport vh |
Data[x].mapLabel[y].titleLeft (string) |
Distance of label from left of viewport vw |
Data[x].mapLabel[y].pointerDirection (string) |
Pointer direction (Options: top , bottom , left or right ) |
Data[x].mapLabel[y].pointerCoordinates (string) |
Location of the tip of the pointer. Based on a 100x100 SVG box. |
const Data = [
{
...
symbols: [
{...},
],
mapLabel: [
{
name: "Iron Ring",
rotation: "160deg",
bottom: "4vh",
right: "5vw",
}
{...},
]
}
]