-
Notifications
You must be signed in to change notification settings - Fork 91
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This PR exposes some basic custom components APIs: - `usePaneContext` (undocumented) - `Debug.TransformWidget` (half-undocumented) - `useTransformContext` - `var(--mafs-view-transform)` - `var(--mafs-user-transform)` More docs, and potentially some API changes, to come. Just wanted to get this out early for folks to use.
- Loading branch information
1 parent
b681c6c
commit ec4cb37
Showing
22 changed files
with
573 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
"use client" | ||
|
||
import PizzaSlice from "guide-examples/custom/pizza-slice" | ||
import PizzaSliceSource from "!raw-loader!guide-examples/custom/pizza-slice" | ||
|
||
import PointCloud from "guide-examples/custom/point-cloud" | ||
import PointCloudSource from "!raw-loader!guide-examples/custom/point-cloud" | ||
|
||
import CodeAndExample from "components/CodeAndExample" | ||
import Link from "next/link" | ||
|
||
export default function TransformContextsPage() { | ||
return ( | ||
<> | ||
<p> | ||
At its core, Mafs is just SVG with two contextual transforms. Those transforms correspond to | ||
two things: | ||
</p> | ||
|
||
<ul> | ||
<li> | ||
The <strong>view transform</strong>, which maps from world space to pixel space. | ||
</li> | ||
<li> | ||
The <strong>user transform</strong>, which is imposed by the{" "} | ||
<Link href="/guides/display/transform">Transform</Link> component. | ||
</li> | ||
</ul> | ||
|
||
<p> | ||
The general approach is that, to render a point <code>(x, y)</code>, you must first apply | ||
the user transform (because, well, the user is trying to move your component in some way), | ||
and <em>then</em> the view transform (so that it gets rendered by the SVG renderer in the | ||
right spot). | ||
</p> | ||
|
||
<p>Mafs provides these transforms through two means:</p> | ||
|
||
<ul> | ||
<li> | ||
The <code>--mafs-view-transform</code> and <code>--mafs-user-transform</code> CSS custom | ||
properties, which can be applied to an SVG element's <code>style</code> attribute. | ||
</li> | ||
<li> | ||
The <code>useTransformContext</code> hook, which returns an object containing the{" "} | ||
<code>viewTransform</code> matrix and the <code>userTransform</code> matrix. | ||
</li> | ||
</ul> | ||
|
||
<p> | ||
Components can mix and match these two approaches depending on needs. For example, the{" "} | ||
<Link href="/guides/display/text">Text</Link> component transforms its <em>anchor point</em>{" "} | ||
in JavaScript, and doesn't apply any CSS transforms, because that would distort the text | ||
itself. On the other hand, the <Link href="/guides/display/ellipse">Ellipse</Link> component | ||
almost entirely relies on CSS transforms internally. | ||
</p> | ||
|
||
<h2>Accessing transforms in CSS</h2> | ||
|
||
<p> | ||
Here's an example of a custom component that uses the CSS transforms approach to render a | ||
delicious little <code>PizzaSlice</code>. The slice is wrapped in{" "} | ||
<code>Debug.TransformWidget</code> component so that you can try applying some user | ||
transforms it. | ||
</p> | ||
|
||
<CodeAndExample component={<PizzaSlice />} source={PizzaSliceSource} /> | ||
|
||
<p> | ||
This is an example of a component that gets entirely transformed by the user and view | ||
transforms. The circle can end up totally distorted. For cases where you want to preserve | ||
the aspect ratio or pixel size of your component, you likely need to use the hooks approach. | ||
</p> | ||
|
||
<h2>Accessing transforms in JavaScript</h2> | ||
|
||
<p> | ||
Here's an example of a custom component that uses the hooks approach to render a grid of | ||
points. Because we want the grid's points to have a radius of 3 <em>pixels</em> (regardless | ||
of the viewport or any transforms), we use the <code>useTransformContext</code> hook to get | ||
the user and view transforms and apply them to the circles' <code>x</code> and{" "} | ||
<code>y</code> coordinates, but not to their radius (which is in pixels). We also cannot use | ||
the CSS transforms approach here, because that would distort each circle. | ||
</p> | ||
|
||
<CodeAndExample component={<PointCloud />} source={PointCloudSource} /> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
"use client" | ||
|
||
import PizzaMarch from "guide-examples/custom/pizza-march" | ||
|
||
export default function CustomPage() { | ||
return ( | ||
<> | ||
<p> | ||
Sometimes, Mafs simply won't have the component you need. When that happens, Mafs provides | ||
APIs to drop one level deeper, letting you render any SVG elements you want. All it takes is | ||
some work to ensure things render correctly. | ||
</p> | ||
|
||
<p> | ||
In learning this, we'll make a <code>PizzaSlice</code> component that behaves just like a | ||
built-in Mafs component. | ||
</p> | ||
|
||
<PizzaMarch /> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-nocheck | ||
import { range } from "lodash" | ||
import { | ||
Mafs, | ||
CartesianCoordinates, | ||
Plot, | ||
useMovablePoint, | ||
Transform, | ||
useTransformContext, | ||
vec, | ||
} from "mafs" | ||
|
||
let inc = 0 | ||
|
||
function PizzaSlice({ at, radius: r }) { | ||
const [x, y] = at | ||
inc++ | ||
|
||
const { userTransform } = useTransformContext() | ||
|
||
const userTransformCSS = vec.toCSS(userTransform) | ||
|
||
return ( | ||
<g | ||
style={{ | ||
transform: `var(--mafs-view-transform)`, | ||
}} | ||
> | ||
<defs> | ||
<mask | ||
id={`pizza-slice-mask-${inc}`} | ||
maskUnits="userSpaceOnUse" | ||
> | ||
<polyline | ||
id="pizza-slice" | ||
points={`0,0 ${r},${r / 2} ${r},${-r / 2}`} | ||
fill="white" | ||
stroke="none" | ||
/> | ||
</mask> | ||
</defs> | ||
|
||
<g | ||
mask={`url(#pizza-slice-mask-${inc})`} | ||
transform={`translate(${x}, ${y}) ${userTransformCSS}`} | ||
maskContentUnits="userSpaceOnUse" | ||
> | ||
<circle cx={0} cy={0} r={r} fill="brown" /> | ||
<circle cx={0} cy={0} r={r * 0.85} fill="yellow" /> | ||
{/* prettier-ignore */} | ||
<circle cx={0 + r * 0.4} cy={0 + r*0.1} r={r * 0.11} fill="red" /> | ||
{/* prettier-ignore */} | ||
<circle cx={0 + r * 0.2} cy={0 - r*0.1} r={r * 0.09} fill="red" /> | ||
{/* prettier-ignore */} | ||
<circle cx={0 + r * 0.5} cy={0 - r*0.15} r={r * 0.1} fill="red" /> | ||
{/* prettier-ignore */} | ||
<circle cx={0 + r * 0.70} cy={0 + r*0.05} r={r * 0.11} fill="red" /> | ||
{/* prettier-ignore */} | ||
<circle cx={0 + r * 0.65} cy={0 + r*0.35} r={r * 0.1} fill="red" /> | ||
{/* prettier-ignore */} | ||
<circle cx={0 + r * 0.65} cy={0 - r*0.37} r={r * 0.08} fill="red" /> | ||
</g> | ||
</g> | ||
) | ||
} | ||
|
||
export default function Example() { | ||
const fn = (x) => Math.sin(x * 2) | ||
const deriv = (x) => 2 * Math.cos(x * 2) | ||
|
||
const offset = useMovablePoint([2, fn(2)], { | ||
constrain: ([x, y]) => [x, fn(x)], | ||
}) | ||
|
||
const points = range(-4, 0.1, 1 / 2).map( | ||
(x) => [x + offset.x, fn(x + offset.x)] as const | ||
) | ||
|
||
return ( | ||
<Mafs height={300} viewBox={{ y: [-1, 1] }}> | ||
<CartesianCoordinates /> | ||
<Plot.OfX y={fn} /> | ||
{points.map((p, index) => ( | ||
<Transform | ||
rotate={Math.PI + Math.atan(deriv(p[0]))} | ||
key={index} | ||
> | ||
<PizzaSlice at={p} radius={0.5} /> | ||
</Transform> | ||
))} | ||
|
||
{offset.element} | ||
</Mafs> | ||
) | ||
} |
Oops, something went wrong.
ec4cb37
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
mafs – ./
mafs-docs.vercel.app
mafs-stevenpetryk.vercel.app
mafs-git-main-stevenpetryk.vercel.app
mafs.dev
www.mafs.dev