Skip to content

Commit

Permalink
i
Browse files Browse the repository at this point in the history
  • Loading branch information
holtzy committed Nov 7, 2024
1 parent 0bb9768 commit 3985829
Show file tree
Hide file tree
Showing 26 changed files with 2,053 additions and 84 deletions.
196 changes: 196 additions & 0 deletions pages/course/canvas/combining-svg-and-canvas.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import React from 'react';
import TitleAndDescription from '@/component/TitleAndDescription';
import { LayoutCourse } from '@/component/LayoutCourse';
import { lessonList } from '@/util/lessonList';
import Link from 'next/link';
import { ExerciseAccordion } from '@/component/ExerciseAccordion';
import {
Exercise,
ExerciseDoubleSandbox,
} from '@/component/ExerciseDoubleSandbox';
import { ChartOrSandbox } from '@/component/ChartOrSandbox';
import { ScatterplotCanvasBasicDemo } from '@/viz/ScatterplotCanvas/ScatterplotCanvasBasicDemo';
import { BubblePlotCanvasDemo } from '@/viz/BubblePlotCanvas/BubblePlotCanvasDemo';
import { Caption } from '@/component/UI/Caption';
import { CodeBlock } from '@/component/UI/CodeBlock';

const previousURL = '/course/canvas/drawing-shapes-with-canvas';
const currentURL = '/course/canvas/combining-svg-and-canvas';
const nextURL = '/course/canvas/svg-path-in-canvas';
const seoDescription = '';

export default function Home() {
const currentLesson = lessonList.find((l) => l.link === currentURL);

if (!currentLesson) {
return null;
}

return (
<LayoutCourse
title={currentLesson.name}
seoDescription={seoDescription}
nextTocItem={lessonList.find((l) => l.link === nextURL)}
previousTocItem={lessonList.find((l) => l.link === previousURL)}
>
<TitleAndDescription
title={currentLesson.name}
lessonStatus={currentLesson.status}
readTime={currentLesson.readTime}
selectedLesson={currentLesson}
description={
<>
<p>
When displaying 100,000 circles in a scatterplot, using canvas is{' '}
<b>essential for performance</b>.
</p>
<p>
However, SVG is still ideal for axes and lighter graphical
elements. Let’s explore how to <b>combine</b> SVG and canvas
effectively!
</p>
</>
}
/>
{/* -
-
-
-
-
-
-
*/}
<h2>🍔 Stacking Canvas and SVG</h2>
<p>
In the previous lesson, we learned how to loop through a dataset and
render a circle for each item. This is very close to creating a{' '}
<Link href="/bubble-plot">bubble chart</Link>! 🎉
</p>
<p>
In earlier modules, we explored how to{' '}
<Link href="/course/axis/margin-and-translation">add margins</Link>{' '}
around the chart area and created{' '}
<Link href="/course/axis/bottom-axis">reusable axis</Link> components to
define the x and y scales.
</p>
<p>
The great news is that we can seamlessly combine both{' '}
<code>canvas</code> and <code>SVG</code>, since, at the core, they're
both HTML elements!
</p>

<p>
<br />
</p>
<center>
<img src="/excalidraw/canvas-axis-layers.png" width={550} />
<Caption>
How to overlap SVG and Canvas layers to create a bubble plot.
</Caption>
</center>
<p>
<br />
</p>

<h2>🔎 How your DOM will look like</h2>
<p>
Below is some pseudocode demonstrating the JSX structure of the graph
component.
</p>
<p>
Essentially, your SVG and canvas elements need to be <b>absolutely</b>{' '}
positioned (using <code>position: absolute</code>) to stack correctly on
top of each other.
</p>
<p>
A key point to remember is that absolutely positioned elements are
positioned relative to the nearest positioned ancestor. So, make sure
the parent <code>div</code> is set to <code>position: relative</code>,
or the positioning won’t work as expected.
</p>

<CodeBlock
code={`
{/* Parent div */}
<div style={{ position: 'relative' }}>
{/* Bounds div */}
<div
style={{
transform: ...translate for margins,
width: boundsWidth,
height: boundsHeight,
}}
>
{/* Axes */}
<svg
width={boundsWidth}
height={boundsHeight}
style={{ position: 'absolute', top: 0, left: 0 }}
>
<AxisLeft yScale={yScale} pixelsPerTick={40} width={boundsWidth} />
<g transform={...translate to bottom}>
<AxisBottom xScale={xScale} pixelsPerTick={40} height={boundsHeight}/>
</g>
</svg>
{/* Canvas is for the circles */}
<canvas
style={{ position: 'relative' }}
ref={canvasRef}
width={boundsWidth}
height={boundsHeight}
/>
</div>
</div>
`}
/>

<h2>Application: bubble plot</h2>
<p>
Here's an example of a <Link href="/bubble-plot">bubble plot</Link>{' '}
created with SVG and React! Take a moment to review the code and ensure
you fully understand each part.
</p>

<ChartOrSandbox
vizName={'BubblePlotCanvas'}
VizComponent={BubblePlotCanvasDemo}
height={600}
maxWidth={600}
caption="Your fist bubble chart using canvas (for circles) and SVG (for axes)"
/>

<h2>Your turn!</h2>
<ExerciseAccordion
localStorageId={currentLesson.link}
exercises={[
{
title: <span>Your own bubble chart</span>,
content: (
<div>
<p>Here is a simple dataset:</p>
<CodeBlock
code={`
const data = [
{x: 12, y: 99},
{x: 22, y: 9},
{x: 2, y: 89},
{x: 98, y: 34},
{x: 76, y: 22},
]
`.trim()}
/>
<p>
Create a very simple scatterplot that represents this dataset,
with a <code>x</code> and and a <code>y</code> axis.
</p>
</div>
),
},
]}
/>
</LayoutCourse>
);
}
27 changes: 7 additions & 20 deletions pages/course/canvas/drawing-shapes-with-canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,10 @@ import {
Exercise,
ExerciseDoubleSandbox,
} from '@/component/ExerciseDoubleSandbox';
import { Graph1 } from '@/viz/exercise/CanvasBasicCircleSolution/Graph';
import { Graph2 } from '@/viz/exercise/CanvasRectOutlineSolution/Graph';
import { Graph3 } from '@/viz/exercise/CanvasClearRectSolution/Graph';
import { Graph4 } from '@/viz/exercise/CanvasBasicLineSolution/Graph';
import { Graph5 } from '@/viz/exercise/CanvasTenCirclesSolution/Graph';
import { Graph6 } from '@/viz/exercise/CanvasBasicTextSolution/Graph';

const previousURL = '/course/canvas/introduction';
const currentURL = '/course/canvas/drawing-shapes-with-canvas';
const nextURL = '';
const nextURL = '/course/canvas/combining-svg-and-canvas';
const seoDescription = '';

export default function Home() {
Expand Down Expand Up @@ -206,37 +200,30 @@ ctx.fill();
exercises={[
{
title: <span>Your first circle</span>,
content: <ExerciseDoubleSandbox exercise={exercices[0]} />,
content: <ExerciseDoubleSandbox exercise={exercises[0]} />,
},
{
title: <span>Outline of a rectangle with strokeRect()</span>,
content: <ExerciseDoubleSandbox exercise={exercices[1]} />,
content: <ExerciseDoubleSandbox exercise={exercises[1]} />,
},
{
title: <span>Clear a portion of the canvas with clearRect()</span>,
content: <ExerciseDoubleSandbox exercise={exercices[2]} />,
content: <ExerciseDoubleSandbox exercise={exercises[2]} />,
},
{
title: <span>Let's make a path</span>,
content: <ExerciseDoubleSandbox exercise={exercices[3]} />,
content: <ExerciseDoubleSandbox exercise={exercises[3]} />,
},
{
title: <span>Ten circles?</span>,
content: <ExerciseDoubleSandbox exercise={exercices[4]} />,
content: <ExerciseDoubleSandbox exercise={exercises[4]} />,
},
{
title: <span>What about text</span>,
content: <ExerciseDoubleSandbox exercise={exercices[5]} />,
content: <ExerciseDoubleSandbox exercise={exercises[5]} />,
},
]}
/>
<Graph1 />

<Graph2 />
<Graph3 />
<Graph4 />
<Graph5 />
<Graph6 />
</LayoutCourse>
);
}
Expand Down
Loading

0 comments on commit 3985829

Please sign in to comment.