Skip to content

Commit 551b0fa

Browse files
committed
Add Image component
1 parent 3501d8d commit 551b0fa

File tree

7 files changed

+162
-0
lines changed

7 files changed

+162
-0
lines changed
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { PropTable } from "components/PropTable"
2+
3+
import CodeAndExample from "components/CodeAndExample"
4+
import ImageExample from "guide-examples/display/images/ImageExample"
5+
import type { Metadata } from "next"
6+
7+
export const metadata: Metadata = {
8+
title: "Vectors",
9+
}
10+
11+
function Vectors() {
12+
return (
13+
<>
14+
<p>
15+
Images in Mafs are just wrappers around the SVG <code>image</code> element, with some
16+
improvements. For exa
17+
</p>
18+
19+
<CodeAndExample example={ImageExample} />
20+
21+
<PropTable of={"Vector"} />
22+
</>
23+
)
24+
}
25+
26+
export default Vectors

docs/app/guides/guides.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
TextIcon,
1111
CursorArrowIcon,
1212
PlayIcon,
13+
ImageIcon,
1314
} from "@radix-ui/react-icons"
1415

1516
import {
@@ -62,6 +63,7 @@ export const Guides: Section[] = [
6263
{ title: "Plots", icon: FunctionIcon, slug: "plots" },
6364
{ title: "Text", icon: TextIcon, slug: "text" },
6465
{ title: "Vectors", icon: ArrowTopRightIcon, slug: "vectors" },
66+
{ title: "Images", icon: ImageIcon, slug: "images" },
6567
{ separator: true },
6668
{ title: "Transform", icon: RotateCounterClockwiseIcon, slug: "transform" },
6769
{ title: "Debug", icon: DebugIcon, slug: "debug" },
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"use client"
2+
3+
import {
4+
Coordinates,
5+
Debug,
6+
Image,
7+
Mafs,
8+
useMovablePoint,
9+
} from "mafs"
10+
11+
import mafs from "./mafs.png"
12+
13+
export default function VectorExample() {
14+
return (
15+
<Mafs viewBox={{ x: [-1, 7], y: [-1, 5] }}>
16+
<Coordinates.Cartesian />
17+
<Image
18+
src={mafs.src}
19+
anchor="cc"
20+
x={2}
21+
y={2}
22+
width={2}
23+
height={2}
24+
/>
25+
</Mafs>
26+
)
27+
}
Loading

src/display/Image.tsx

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Anchor, computeAnchor } from "../math"
2+
3+
export interface ImageProps {
4+
src: string
5+
x: number
6+
y: number
7+
width: number
8+
height: number
9+
anchor?: Anchor
10+
preserveAspectRatio?: string
11+
svgImageProps?: React.SVGProps<SVGImageElement>
12+
}
13+
14+
export function Image({
15+
src,
16+
x,
17+
y,
18+
width,
19+
height,
20+
anchor = "bl",
21+
preserveAspectRatio,
22+
svgImageProps,
23+
}: ImageProps) {
24+
const [actualX, actualY] = computeAnchor(anchor, x, y, width, height)
25+
26+
const scaleX = width < 0 ? -1 : 1
27+
const scaleY = height < 0 ? -1 : 1
28+
29+
console.log(actualX - (width < 0 ? -width : 0))
30+
31+
return (
32+
<image
33+
href={src}
34+
x={actualX - (width < 0 ? -width : 0)}
35+
y={actualY - (height < 0 ? -height : 0)}
36+
width={Math.abs(width)}
37+
height={Math.abs(height)}
38+
preserveAspectRatio={preserveAspectRatio}
39+
{...svgImageProps}
40+
style={{
41+
transform: [`var(--mafs-view-transform)`, `var(--mafs-user-transform)`, `scaleY(-1) `].join(
42+
" ",
43+
),
44+
}}
45+
/>
46+
)
47+
}

src/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export type { PointProps } from "./display/Point"
3535
export { Vector } from "./display/Vector"
3636
export type { VectorProps } from "./display/Vector"
3737

38+
export { Image } from "./display/Image"
39+
export type { ImageProps } from "./display/Image"
40+
3841
export { Text } from "./display/Text"
3942
export type { TextProps, CardinalDirection } from "./display/Text"
4043

src/math.ts

+57
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export type Interval = [min: number, max: number]
2+
export type Anchor = "tl" | "tc" | "tr" | "cl" | "cc" | "cr" | "bl" | "bc" | "br"
23

34
export function round(value: number, precision = 0): number {
45
const multiplier = Math.pow(10, precision || 0)
@@ -24,3 +25,59 @@ export function range(min: number, max: number, step = 1): number[] {
2425
export function clamp(number: number, min: number, max: number): number {
2526
return Math.min(Math.max(number, min), max)
2627
}
28+
29+
/**
30+
* Given an anchor and a bounding box (x, y, width, height), compute the x and y coordinates of the
31+
* anchor such that rendering an element at those coordinates will align the element with the anchor.
32+
*/
33+
export function computeAnchor(
34+
anchor: Anchor,
35+
x: number,
36+
y: number,
37+
width: number,
38+
height: number,
39+
): [number, number] {
40+
let actualX = x
41+
let actualY = y
42+
43+
switch (anchor) {
44+
case "tl":
45+
actualX = x
46+
actualY = -y
47+
break
48+
case "tc":
49+
actualX = x - width / 2
50+
actualY = -y
51+
break
52+
case "tr":
53+
actualX = x - width
54+
actualY = -y
55+
break
56+
case "cl":
57+
actualX = x
58+
actualY = -y - height / 2
59+
break
60+
case "cc":
61+
actualX = x - width / 2
62+
actualY = -y - height / 2
63+
break
64+
case "cr":
65+
actualX = x - width
66+
actualY = -y - height / 2
67+
break
68+
case "bl":
69+
actualX = x
70+
actualY = -y - height
71+
break
72+
case "bc":
73+
actualX = x - width / 2
74+
actualY = -y - height
75+
break
76+
case "br":
77+
actualX = x - width
78+
actualY = -y - height
79+
break
80+
}
81+
82+
return [actualX, actualY]
83+
}

0 commit comments

Comments
 (0)