├── .eslintrc.js
├── .gitignore
├── README.md
├── commitlint.config.js
├── components
├── common
│ ├── bracket_left
│ │ ├── BracketLeft.tsx
│ │ └── index.ts
│ ├── bracket_right
│ │ ├── BracketRight.tsx
│ │ └── index.ts
│ ├── font_face
│ │ ├── FontFace.tsx
│ │ └── index.ts
│ ├── octocat
│ │ ├── Octocat.tsx
│ │ └── index.ts
│ ├── outside_click_handler
│ │ ├── OutsideClickHandler.tsx
│ │ └── index.ts
│ ├── rounded_input
│ │ ├── RoundedInput.tsx
│ │ └── index.ts
│ └── segment
│ │ ├── Segment.tsx
│ │ └── index.ts
├── parts
│ ├── code
│ │ ├── Code.tsx
│ │ └── index.ts
│ ├── coord_system
│ │ ├── CoordSystem.tsx
│ │ ├── axis
│ │ │ ├── Axis.tsx
│ │ │ └── index.ts
│ │ ├── circle
│ │ │ ├── Circle.tsx
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── moved_rectangle
│ │ │ ├── MovedRectangle.tsx
│ │ │ └── index.ts
│ │ ├── rectangle
│ │ │ ├── Rectangle.tsx
│ │ │ └── index.ts
│ │ ├── steps
│ │ │ ├── Steps.tsx
│ │ │ └── index.ts
│ │ ├── trail
│ │ │ ├── Trail.tsx
│ │ │ └── index.ts
│ │ ├── transformation
│ │ │ ├── Transformation.tsx
│ │ │ └── index.ts
│ │ └── transition
│ │ │ ├── Transition.tsx
│ │ │ └── index.ts
│ ├── equation
│ │ ├── Equation.tsx
│ │ ├── bond
│ │ │ ├── Bond.tsx
│ │ │ └── index.ts
│ │ ├── bracket_pair
│ │ │ ├── BracketPair.tsx
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── faq
│ │ ├── Faq.tsx
│ │ └── index.ts
│ ├── footer
│ │ ├── Footer.tsx
│ │ └── index.ts
│ ├── function
│ │ ├── Function.tsx
│ │ └── index.ts
│ ├── layout
│ │ ├── Layout.tsx
│ │ └── index.ts
│ ├── tour
│ │ ├── Tour.tsx
│ │ ├── overlay
│ │ │ ├── Overlay.tsx
│ │ │ └── index.ts
│ │ └── step
│ │ │ └── Step.tsx
│ ├── transform_slider
│ │ ├── TransformSlider.tsx
│ │ └── index.ts
│ ├── transform_switch
│ │ ├── TransformSwitch.tsx
│ │ └── index.ts
│ └── transformation
│ │ ├── Transformation.tsx
│ │ └── index.ts
└── states
│ ├── figure
│ ├── FigureState.tsx
│ └── index.ts
│ ├── matrix
│ ├── MatrixState.tsx
│ └── index.ts
│ ├── points
│ ├── PointsState.tsx
│ └── index.ts
│ ├── tour
│ ├── TourState.tsx
│ └── index.ts
│ ├── transformation
│ ├── TransformationState.tsx
│ └── index.ts
│ └── translation
│ ├── TranslationState.tsx
│ └── index.ts
├── content
├── coord_system.mdx
├── equation.mdx
├── function.mdx
├── intro.mdx
└── summary.mdx
├── context
├── figure
│ ├── context.ts
│ ├── reducer.ts
│ └── types.ts
├── matrix
│ ├── context.ts
│ ├── reducer.ts
│ └── types.ts
├── points
│ ├── context.ts
│ ├── reducer.ts
│ └── types.ts
├── tour
│ ├── context.ts
│ ├── reducer.ts
│ └── types.ts
├── transformation
│ ├── context.ts
│ ├── reducer.ts
│ └── types.ts
└── translation
│ ├── context.ts
│ ├── reducer.ts
│ └── types.ts
├── hooks
├── useDrag.ts
├── useMouseMove.ts
└── useOutsideClick.ts
├── next-env.d.ts
├── next.config.js
├── package.json
├── pages
└── index.tsx
├── public
└── fonts
│ ├── crimson-text-v10-latin-italic.woff
│ ├── crimson-text-v10-latin-italic.woff2
│ ├── crimson-text-v10-latin-regular.woff
│ ├── crimson-text-v10-latin-regular.woff2
│ ├── fira-code-v7-latin-600.woff
│ ├── fira-code-v7-latin-600.woff2
│ ├── fira-code-v7-latin-regular.woff
│ └── fira-code-v7-latin-regular.woff2
├── theme
└── theme.ts
├── tsconfig.json
├── types
└── svg
│ └── index.ts
├── typings
├── @mdx-js
│ └── react
│ │ └── index.d.ts
└── mdx.d.ts
├── yarn-error.log
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | jest: true,
4 | browser: true
5 | },
6 | parser: '@typescript-eslint/parser',
7 | extends: [
8 | 'airbnb',
9 | 'plugin:react/recommended',
10 | 'plugin:@typescript-eslint/recommended',
11 | ],
12 | plugins: [
13 | 'react-hooks'
14 | ],
15 | parserOptions: {
16 | ecmaVersion: 2019,
17 | sourceType: 'module',
18 | ecmaFeatures: {
19 | jsx: true
20 | }
21 | },
22 | rules: {
23 | '@typescript-eslint/ban-ts-ignore': 0,
24 | '@typescript-eslint/no-explicit-any': 0,
25 | 'react/jsx-filename-extension': [
26 | 1,
27 | { extensions: ['.js', '.jsx', '.tsx', '.ts'] }
28 | ],
29 | 'import/extensions': 0,
30 | 'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
31 | 'no-plusplus': 0,
32 | 'react/jsx-max-props-per-line': 1,
33 | 'react/jsx-props-no-spreading': 0,
34 | 'react/destructuring-assignment': 0,
35 | },
36 | settings: {
37 | react: {
38 | version: 'detect'
39 | },
40 | 'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
41 | 'import/parsers': {
42 | '@typescript-eslint/parser': ['.ts', '.tsx']
43 | },
44 | 'import/resolver': {
45 | node: {
46 | paths: ['src'],
47 | extensions: ['.js', '.jsx', '.ts', '.tsx']
48 | }
49 | }
50 | },
51 | overrides: [
52 | {
53 | files: ['**/*.tsx'],
54 | rules: {
55 | 'react/prop-types': 'off',
56 | }
57 | }
58 | ]
59 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | out
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Visual Guide To SVG Matrix
2 |
3 | This app aims to visually explain SVG's `matrix` transformation function
4 |
5 | 
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["@commitlint/config-conventional"]
3 | };
--------------------------------------------------------------------------------
/components/common/bracket_left/BracketLeft.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { SvgComponent } from '../../../types/svg';
3 |
4 | const BracketLeft: SvgComponent = (props) => (
5 |
12 | );
13 |
14 | export default BracketLeft;
15 |
--------------------------------------------------------------------------------
/components/common/bracket_left/index.ts:
--------------------------------------------------------------------------------
1 | import BracketLeft from './BracketLeft';
2 |
3 | export default BracketLeft;
4 |
--------------------------------------------------------------------------------
/components/common/bracket_right/BracketRight.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { SvgComponent } from '../../../types/svg';
3 |
4 | const BracketRight: SvgComponent = (props) => (
5 |
12 | );
13 |
14 | export default BracketRight;
15 |
--------------------------------------------------------------------------------
/components/common/bracket_right/index.ts:
--------------------------------------------------------------------------------
1 | import BracketRight from './BracketRight';
2 |
3 | export default BracketRight;
4 |
--------------------------------------------------------------------------------
/components/common/font_face/FontFace.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const FontFace: React.FC = () => (
4 | <>
5 |
53 | >
54 | );
55 |
56 | export default FontFace;
57 |
--------------------------------------------------------------------------------
/components/common/font_face/index.ts:
--------------------------------------------------------------------------------
1 | import FontFace from './FontFace';
2 |
3 | export default FontFace;
4 |
--------------------------------------------------------------------------------
/components/common/octocat/Octocat.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, useThemeUI } from 'theme-ui';
3 |
4 |
5 | const Octocat: React.FC = () => {
6 | const { theme } = useThemeUI();
7 | return (
8 |
21 |
44 |
45 | );
46 | };
47 |
48 | export default Octocat;
49 |
--------------------------------------------------------------------------------
/components/common/octocat/index.ts:
--------------------------------------------------------------------------------
1 | import Octocat from './Octocat';
2 |
3 | export default Octocat;
4 |
--------------------------------------------------------------------------------
/components/common/outside_click_handler/OutsideClickHandler.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 | import { Flex } from 'theme-ui';
3 | import useOutsideClick from '../../../hooks/useOutsideClick';
4 |
5 | interface Props {
6 | onOutsideClick: () => void;
7 | sx?: any;
8 | }
9 |
10 | const OutsideClickHandler: React.FC = ({ children, onOutsideClick, sx }) => {
11 | const wrapperRef = useRef(null);
12 |
13 | useOutsideClick(wrapperRef, onOutsideClick);
14 |
15 | return (
16 |
17 | {children}
18 |
19 | );
20 | };
21 |
22 | export default OutsideClickHandler;
23 |
--------------------------------------------------------------------------------
/components/common/outside_click_handler/index.ts:
--------------------------------------------------------------------------------
1 | import OutsideClickHandler from './OutsideClickHandler';
2 |
3 | export default OutsideClickHandler;
4 |
--------------------------------------------------------------------------------
/components/common/rounded_input/RoundedInput.tsx:
--------------------------------------------------------------------------------
1 | import React, { InputHTMLAttributes } from 'react';
2 | import { Flex, Input, Text } from 'theme-ui';
3 | import OutsideClickHandler from '../outside_click_handler';
4 | import useMouseMove from '../../../hooks/useMouseMove';
5 |
6 | interface Props extends InputHTMLAttributes {
7 | label: string;
8 | mode: 'x' | 'y';
9 | onUpdate: (label: string, value: number) => void;
10 | value: number;
11 | }
12 |
13 | const RoundedInput: React.FC = ({
14 | label, mode, step, value, onUpdate,
15 | }) => {
16 | const [editMode, setEditMode] = React.useState<'drag' | 'manual'>('drag');
17 | const [currentValue, setCurrentValue] = React.useState(value);
18 |
19 | const [onMouseDown, onTouchStart] = useMouseMove(setCurrentValue, value);
20 |
21 | const handleChange = (event: React.ChangeEvent): void => {
22 | event.stopPropagation();
23 | const parsed: number = parseInt(
24 | (event.target as HTMLInputElement).value,
25 | 10,
26 | );
27 | setCurrentValue(parsed);
28 | };
29 |
30 | const handleDoubleClick = (): void => setEditMode('manual');
31 |
32 | const handleOutsideClick = React.useCallback(() => {
33 | setEditMode('drag');
34 | }, [editMode]);
35 |
36 | React.useEffect(() => {
37 | onUpdate(label, currentValue);
38 | }, [label, currentValue]);
39 |
40 | return (
41 |
47 |
48 | {editMode === 'drag' ? (
49 |
65 |
74 | {currentValue}
75 |
76 |
77 | ) : (
78 |
91 |
102 |
103 | )}
104 |
115 | {label}
116 |
117 |
118 | );
119 | };
120 |
121 | export default RoundedInput;
122 |
--------------------------------------------------------------------------------
/components/common/rounded_input/index.ts:
--------------------------------------------------------------------------------
1 | import RoundedInput from './RoundedInput';
2 |
3 | export default RoundedInput;
4 |
--------------------------------------------------------------------------------
/components/common/segment/Segment.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex } from 'theme-ui';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 | import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
5 | import TourContext from '../../../context/tour/context';
6 |
7 | interface Props {
8 | id?: string;
9 | styles?: React.CSSProperties;
10 | title: string;
11 | hideHelp?: boolean;
12 | }
13 |
14 | const Segment: React.FC = ({
15 | id, children, title, styles, hideHelp,
16 | }) => {
17 | const {
18 | state: { step },
19 | dispatch,
20 | } = React.useContext(TourContext);
21 | const handleClick = React.useCallback(() => {
22 | let newStep: number;
23 | switch (title) {
24 | case 'Function':
25 | newStep = 2;
26 | break;
27 | case 'Equation':
28 | newStep = 3;
29 | break;
30 | case 'CoordSystem':
31 | newStep = 4;
32 | break;
33 | case 'Code':
34 | case 'Current Point':
35 | newStep = 5;
36 | break;
37 | default:
38 | newStep = 1;
39 | }
40 | dispatch({
41 | type: 'setStep',
42 | payload: newStep,
43 | });
44 | dispatch({
45 | type: 'toggle',
46 | payload: true,
47 | });
48 | }, [title, step]);
49 | return (
50 |
63 |
73 |
78 | {title}
79 | {!hideHelp && (
80 |
86 | )}
87 |
88 |
89 | {children}
90 |
91 | );
92 | };
93 |
94 | export default Segment;
95 |
--------------------------------------------------------------------------------
/components/common/segment/index.ts:
--------------------------------------------------------------------------------
1 | import Segment from './Segment';
2 |
3 | export default Segment;
4 |
--------------------------------------------------------------------------------
/components/parts/code/Code.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Alert, Text } from 'theme-ui';
3 | import { Transition } from 'react-spring/renderprops.cjs';
4 | import Segment from '../../common/segment';
5 | import MatrixContext from '../../../context/matrix/context';
6 |
7 | const Code: React.FC = () => {
8 | const {
9 | state: {
10 | a, b, c, d, e, f,
11 | },
12 | } = React.useContext(MatrixContext);
13 | const [copied, setCopied] = React.useState(false);
14 | const handleClick = React.useCallback((event: React.MouseEvent) => {
15 | const str: string = (event.target as HTMLElement).textContent;
16 | const el = document.createElement('textarea');
17 | el.value = str;
18 | el.setAttribute('readonly', '');
19 | el.style.position = 'absolute';
20 | el.style.left = '-9999px';
21 | document.body.appendChild(el);
22 | el.select();
23 | document.execCommand('copy');
24 | document.body.removeChild(el);
25 | setCopied(true);
26 | setTimeout(() => setCopied(false), 2000);
27 | }, [setCopied]);
28 | return (
29 |
38 |
53 | {(isCopied): any => isCopied && ((props): React.ReactElement => (
54 |
64 |
65 | Matrix copied to the clipboard!
66 |
67 |
68 | ))}
69 |
70 |
84 | transform="matrix(
85 | {a}
86 | {' '}
87 | {b}
88 | {' '}
89 | {c}
90 | {' '}
91 | {d}
92 | {' '}
93 | {e}
94 | {' '}
95 | {f}
96 | )"
97 |
98 |
99 | );
100 | };
101 |
102 | export default Code;
103 |
--------------------------------------------------------------------------------
/components/parts/code/index.ts:
--------------------------------------------------------------------------------
1 | import Code from './Code';
2 |
3 | export default Code;
4 |
--------------------------------------------------------------------------------
/components/parts/coord_system/CoordSystem.tsx:
--------------------------------------------------------------------------------
1 | import React, { MutableRefObject, createRef } from 'react';
2 | import { Flex, useThemeUI } from 'theme-ui';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 | import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
5 | import Axis from './axis';
6 | import TourContext from '../../../context/tour/context';
7 | import Transformation from './transformation';
8 |
9 | const margin: { [key: string]: number } = {
10 | top: 30,
11 | left: 30,
12 | right: 30,
13 | bottom: 30,
14 | };
15 |
16 | const CoordSystem: React.FC = () => {
17 | const {
18 | dispatch,
19 | } = React.useContext(TourContext);
20 | const [currentWidth, setCurrentWidth] = React.useState(0);
21 | const [currentHeight, setCurrentHeight] = React.useState(0);
22 | const ref: MutableRefObject = createRef();
23 | const { theme } = useThemeUI();
24 |
25 | const handleResize = React.useCallback(() => {
26 | if (ref.current) {
27 | const { width, height } = ref.current.parentElement.getBoundingClientRect();
28 | setCurrentWidth(Math.round(width));
29 | setCurrentHeight(Math.round(height));
30 | }
31 | }, [ref, setCurrentWidth, setCurrentHeight]);
32 |
33 | const handleIconClick = React.useCallback(() => {
34 | dispatch({
35 | type: 'setStep',
36 | payload: 4,
37 | });
38 | dispatch({
39 | type: 'toggle',
40 | payload: true,
41 | });
42 | }, [dispatch]);
43 |
44 | React.useEffect(() => {
45 | if (currentHeight === 0 && currentWidth === 0) {
46 | handleResize();
47 | }
48 | window.addEventListener('resize', handleResize);
49 | return (): void => {
50 | window.removeEventListener('resize', handleResize);
51 | };
52 | }, [handleResize]);
53 |
54 | return (
55 |
67 |
80 |
105 |
106 | );
107 | };
108 |
109 | export default CoordSystem;
110 |
--------------------------------------------------------------------------------
/components/parts/coord_system/axis/Axis.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { scaleLinear } from 'd3-scale';
3 | import { useThemeUI } from 'theme-ui';
4 |
5 | interface Tick {
6 | value: number;
7 | offset: number;
8 | }
9 |
10 | interface Props {
11 | axis: 'x' | 'y';
12 | marginTop: number;
13 | marginLeft: number;
14 | ticks?: number;
15 | viewBoxWidth: number;
16 | viewBoxHeight: number;
17 | }
18 |
19 | const Axis: React.FC = ({
20 | axis, marginTop, marginLeft, ticks = 10, viewBoxWidth, viewBoxHeight,
21 | }) => {
22 | const { theme } = useThemeUI();
23 | const getTicks = React.useCallback((): Tick[] => {
24 | const endPoint: number = axis === 'x'
25 | ? viewBoxWidth
26 | : viewBoxHeight;
27 | const scale = scaleLinear()
28 | .domain([0, endPoint])
29 | .range([0, endPoint]);
30 |
31 | return scale.ticks(Math.round((axis === 'x' ? viewBoxWidth : viewBoxHeight) / 35))
32 | .map((value) => ({
33 | value,
34 | offset: scale(value),
35 | }));
36 | }, [axis, ticks, viewBoxHeight, viewBoxWidth]);
37 |
38 | return (
39 |
40 | {getTicks().map((t: Tick, index: number) => {
41 | const translateX: number = axis === 'x'
42 | ? t.offset + marginLeft
43 | : 0;
44 | const translateY: number = axis === 'x'
45 | ? 0
46 | : t.offset + marginTop;
47 | const lineTransformX: number = axis === 'x'
48 | ? 0
49 | : marginLeft;
50 | const lineTransformY: number = axis === 'x'
51 | ? marginTop
52 | : 0;
53 | return index > 0 && (
54 |
55 |
61 |
70 | {t.value}
71 |
72 |
73 | );
74 | })}
75 |
76 | );
77 | };
78 |
79 | export default Axis;
80 |
--------------------------------------------------------------------------------
/components/parts/coord_system/axis/index.ts:
--------------------------------------------------------------------------------
1 | import Axis from './Axis';
2 |
3 | export default Axis;
4 |
--------------------------------------------------------------------------------
/components/parts/coord_system/circle/Circle.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useThemeUI } from 'theme-ui';
3 | import { Spring } from 'react-spring/renderprops.cjs';
4 |
5 | interface Props {
6 | id: string;
7 | cx: number;
8 | cy: number;
9 | name: string;
10 | isSelected: boolean;
11 | isTranslated?: boolean;
12 | onClick?: (event: React.MouseEvent) => void;
13 | }
14 |
15 | const Circle: React.FC = ({
16 | cx, cy, isSelected, isTranslated, onClick, name, id,
17 | }) => {
18 | const [hover, setHover] = React.useState(false);
19 | const { theme } = useThemeUI();
20 |
21 | const handleMouseEnter = (): void => setHover(true);
22 | const handleMouseOut = (): void => setHover(false);
23 | return (
24 | <>
25 |
42 | {(hover || isSelected) && (
43 |
47 | {(styles): JSX.Element => (
48 |
51 |
60 |
68 | {isSelected && (
69 |
77 | {isTranslated ? 'New Current Point:' : 'Current point:'}
78 |
79 | )}
80 |
88 | {name}
89 |
90 |
94 | x:
95 |
98 | {cx}
99 |
100 |
101 |
102 | y:
103 | {cy}
104 |
105 |
106 |
107 | )}
108 |
109 | )}
110 | >
111 | );
112 | };
113 |
114 | export default Circle;
115 |
--------------------------------------------------------------------------------
/components/parts/coord_system/circle/index.ts:
--------------------------------------------------------------------------------
1 | import Circle from './Circle';
2 |
3 | export default Circle;
4 |
--------------------------------------------------------------------------------
/components/parts/coord_system/index.ts:
--------------------------------------------------------------------------------
1 | import CoordSystem from './CoordSystem';
2 |
3 | export default CoordSystem;
4 |
--------------------------------------------------------------------------------
/components/parts/coord_system/moved_rectangle/MovedRectangle.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useThemeUI } from 'theme-ui';
3 | import FigureContext from '../../../../context/figure/context';
4 | import MatrixContext from '../../../../context/matrix/context';
5 | import TranslationContext from '../../../../context/translation/context';
6 | import Circle from '../circle';
7 | import PointsContext from '../../../../context/points/context';
8 |
9 | const MovedRectangle: React.FC = () => {
10 | const {
11 | state: {
12 | x, y, width, height,
13 | },
14 | } = React.useContext(FigureContext);
15 | const {
16 | state: {
17 | a, b, c, d, e, f,
18 | },
19 | } = React.useContext(MatrixContext);
20 | const {
21 | state,
22 | } = React.useContext(TranslationContext);
23 | const {
24 | state: {
25 | current,
26 | },
27 | } = React.useContext(PointsContext);
28 | const { theme } = useThemeUI();
29 | const handlePointClick = (): null => null;
30 | return (
31 | <>
32 |
40 |
49 |
58 |
67 |
76 | >
77 | );
78 | };
79 |
80 | export default MovedRectangle;
81 |
--------------------------------------------------------------------------------
/components/parts/coord_system/moved_rectangle/index.ts:
--------------------------------------------------------------------------------
1 | import MovedRectangle from './MovedRectangle';
2 |
3 | export default MovedRectangle;
4 |
--------------------------------------------------------------------------------
/components/parts/coord_system/rectangle/Rectangle.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useThemeUI } from 'theme-ui';
3 | import { Vec2, CurrentPoint } from '../../../../context/points/types';
4 | import PointsContext from '../../../../context/points/context';
5 | import Circle from '../circle';
6 | import FigureContext from '../../../../context/figure/context';
7 | import useDrag from '../../../../hooks/useDrag';
8 |
9 | const Rectangle: React.FC = () => {
10 | const {
11 | state: {
12 | x, y, width, height,
13 | },
14 | dispatch: figureDispatch,
15 | } = React.useContext(FigureContext);
16 | const {
17 | state,
18 | dispatch,
19 | } = React.useContext(PointsContext);
20 | const { theme } = useThemeUI();
21 |
22 | const currentPointDiff = React.useRef({ x: 0, y: 0 });
23 | const handleRectDown = React.useCallback(
24 | (event: React.MouseEvent | React.TouchEvent) => {
25 | const { type: eventType } = event;
26 | const pointerX: number = eventType === 'touchstart'
27 | ? (event as React.TouchEvent).touches[0].clientX
28 | : (event as React.MouseEvent).clientX;
29 | const pointerY: number = eventType === 'touchstart'
30 | ? (event as React.TouchEvent).touches[0].clientY
31 | : (event as React.MouseEvent).clientY;
32 | currentPointDiff.current = {
33 | x: pointerX - x,
34 | y: pointerY - y,
35 | };
36 | },
37 | [currentPointDiff, x, y],
38 | );
39 | const handleRectDrag = React.useCallback((newPos: Vec2) => {
40 | figureDispatch({
41 | type: 'set',
42 | payload: {
43 | x: newPos.x - currentPointDiff.current.x,
44 | y: newPos.y - currentPointDiff.current.y,
45 | },
46 | });
47 | }, [figureDispatch, currentPointDiff]);
48 |
49 | const [onMouseDown, onTouchStart] = useDrag(handleRectDrag, handleRectDown);
50 |
51 | const commonRectProps: { [key: string]: any } = {
52 | x, y, width, height,
53 | };
54 |
55 | const handlePointClick = React.useCallback(
56 | (event: React.MouseEvent) => {
57 | dispatch({
58 | type: 'set',
59 | payload: {
60 | current: (event.target as SVGCircleElement).id as CurrentPoint,
61 | },
62 | });
63 | },
64 | [dispatch],
65 | );
66 |
67 | return (
68 | <>
69 |
80 |
88 |
96 |
104 |
112 | >
113 | );
114 | };
115 |
116 | export default Rectangle;
117 |
--------------------------------------------------------------------------------
/components/parts/coord_system/rectangle/index.ts:
--------------------------------------------------------------------------------
1 | import Rectangle from './Rectangle';
2 |
3 | export default Rectangle;
--------------------------------------------------------------------------------
/components/parts/coord_system/steps/Steps.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useThemeUI } from 'theme-ui';
3 | import { Spring } from 'react-spring/renderprops.cjs';
4 | import MatrixContext from '../../../../context/matrix/context';
5 | import PointsContext from '../../../../context/points/context';
6 | import TranslationContext from '../../../../context/translation/context';
7 | import TransformationContext from '../../../../context/transformation/context';
8 |
9 | const Steps: React.FC = () => {
10 | const {
11 | state: {
12 | topLeft, topRight,
13 | bottomLeft, bottomRight,
14 | },
15 | } = React.useContext(PointsContext);
16 | const {
17 | state: {
18 | a, b, c, d,
19 | },
20 | } = React.useContext(MatrixContext);
21 | const {
22 | state: translate,
23 | } = React.useContext(TranslationContext);
24 | const { theme } = useThemeUI();
25 | const {
26 | state: { step, visible },
27 | } = React.useContext(TransformationContext);
28 | const transitions: { [key: string]: any } = {
29 | 1: {
30 | from: {
31 | m: `${topLeft.x},${topLeft.y}`,
32 | l1: `${topRight.x},${topRight.y}`,
33 | l2: `${bottomRight.x},${bottomRight.y}`,
34 | l3: `${bottomLeft.x},${bottomLeft.y}`,
35 | },
36 | to: {
37 | m: `${topLeft.x},${topLeft.y}`,
38 | l1: `${topRight.x},${topRight.y}`,
39 | l2: `${bottomRight.x},${bottomRight.y}`,
40 | l3: `${bottomLeft.x},${bottomLeft.y}`,
41 | },
42 | },
43 | 2: {
44 | from: {
45 | m: `${topLeft.x},${topLeft.y}`,
46 | l1: `${topRight.x},${topRight.y}`,
47 | l2: `${bottomRight.x},${bottomRight.y}`,
48 | l3: `${bottomLeft.x},${bottomLeft.y}`,
49 | },
50 | to: {
51 | m: `${topLeft.x * a},${topLeft.x * b}`,
52 | l1: `${topRight.x * a},${topRight.x * b}`,
53 | l2: `${bottomRight.x * a},${bottomRight.x * b}`,
54 | l3: `${bottomLeft.x * a},${bottomLeft.x * b}`,
55 | },
56 | },
57 | 3: {
58 | from: {
59 | m: `${topLeft.x * a},${topLeft.x * b}`,
60 | l1: `${topRight.x * a},${topRight.x * b}`,
61 | l2: `${bottomRight.x * a},${bottomRight.x * b}`,
62 | l3: `${bottomLeft.x * a},${bottomLeft.x * b}`,
63 | },
64 | to: {
65 | m: `${topLeft.x * a + topLeft.y * c},${topLeft.x * b + topLeft.y * d}`,
66 | l1: `${topRight.x * a + topRight.y * c},${topRight.x * b + topRight.y * d}`,
67 | l2: `${bottomRight.x * a + bottomRight.y * c},${bottomRight.x * b + bottomRight.y * d}`,
68 | l3: `${bottomLeft.x * a + bottomLeft.y * c},${bottomLeft.x * b + bottomLeft.y * d}`,
69 | },
70 | },
71 | 4: {
72 | from: {
73 | m: `${topLeft.x * a + topLeft.y * c},${topLeft.x * b + topLeft.y * d}`,
74 | l1: `${topRight.x * a + topRight.y * c},${topRight.x * b + topRight.y * d}`,
75 | l2: `${bottomRight.x * a + bottomRight.y * c},${bottomRight.x * b + bottomRight.y * d}`,
76 | l3: `${bottomLeft.x * a + bottomLeft.y * c},${bottomLeft.x * b + bottomLeft.y * d}`,
77 | },
78 | to: {
79 | m: `${translate.topLeft.x},${translate.topLeft.y}`,
80 | l1: `${translate.topRight.x},${translate.topRight.y}`,
81 | l2: `${translate.bottomRight.x},${translate.bottomRight.y}`,
82 | l3: `${translate.bottomLeft.x},${translate.bottomLeft.y}`,
83 | },
84 | },
85 | };
86 | return (
87 |
91 | {(styles): JSX.Element => (
92 | <>
93 | {visible && (
94 |
99 | )}
100 | >
101 | )}
102 |
103 | );
104 | };
105 |
106 | export default Steps;
107 |
--------------------------------------------------------------------------------
/components/parts/coord_system/steps/index.ts:
--------------------------------------------------------------------------------
1 | import Steps from './Steps';
2 |
3 | export default Steps;
4 |
--------------------------------------------------------------------------------
/components/parts/coord_system/trail/Trail.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useThemeUI } from 'theme-ui';
3 | import TransformationContext from '../../../../context/transformation/context';
4 | import PointsContext from '../../../../context/points/context';
5 | import MatrixContext from '../../../../context/matrix/context';
6 | import { Vec2 } from '../../../../context/points/types';
7 |
8 | interface StepProps {
9 | color: string;
10 | point: Vec2;
11 | step: number;
12 | }
13 |
14 | const Step: React.FC = ({ color, point, step }) => {
15 | const {
16 | state: {
17 | a, b, c,
18 | d, e, f,
19 | },
20 | } = React.useContext(MatrixContext);
21 | const getPoints = React.useCallback(() => {
22 | const step1 = `M ${Object.values(point)}`;
23 | const step2 = `${step1} L${point.x * a},${point.x * b} `;
24 | const step3 = `${step2} L${point.x * a + point.y * c},${point.x * b + point.y * d} `;
25 | const step4 = `${step3} L${point.x * a + point.y * c + e},${point.x * b + point.y * d + f}`;
26 | const steps: { [key: string]: string } = {
27 | step1, step2, step3, step4,
28 | };
29 | return steps[`step${step}`];
30 | }, [step, point, a, b, c, d, e, f]);
31 | return (
32 |
41 | );
42 | };
43 |
44 | const Trail: React.FC = () => {
45 | const {
46 | state: { step },
47 | } = React.useContext(TransformationContext);
48 | const {
49 | state: {
50 | topLeft, topRight,
51 | bottomLeft, bottomRight,
52 | },
53 | } = React.useContext(PointsContext);
54 | const { theme } = useThemeUI();
55 | return (
56 |
57 |
58 |
59 |
60 |
61 |
62 | );
63 | };
64 |
65 | export default Trail;
66 |
--------------------------------------------------------------------------------
/components/parts/coord_system/trail/index.ts:
--------------------------------------------------------------------------------
1 | import Trail from './Trail';
2 |
3 | export default Trail;
4 |
--------------------------------------------------------------------------------
/components/parts/coord_system/transformation/Transformation.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Rectangle from '../rectangle';
3 | import MovedRectangle from '../moved_rectangle';
4 | import Steps from '../steps';
5 | import TransformationContext from '../../../../context/transformation/context';
6 | import Trail from '../trail';
7 |
8 | const Transformation: React.FC = () => {
9 | const {
10 | state: {
11 | trail,
12 | },
13 | } = React.useContext(TransformationContext);
14 | return (
15 | <>
16 | {trail && }
17 |
18 |
19 |
20 | >
21 | );
22 | };
23 |
24 | export default Transformation;
25 |
--------------------------------------------------------------------------------
/components/parts/coord_system/transformation/index.ts:
--------------------------------------------------------------------------------
1 | import Transformation from './Transformation';
2 |
3 | export default Transformation;
4 |
--------------------------------------------------------------------------------
/components/parts/coord_system/transition/Transition.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useThemeUI } from 'theme-ui';
3 | import MatrixContext from '../../../../context/matrix/context';
4 | import PointsContext from '../../../../context/points/context';
5 | import TranslationContext from '../../../../context/translation/context';
6 | import { Vec2 } from '../../../../context/points/types';
7 | import Circle from '../circle';
8 |
9 | const Transition: React.FC = () => {
10 | const {
11 | state: {
12 | a, c,
13 | b, d,
14 | },
15 | } = React.useContext(MatrixContext);
16 | const { state: pointsState } = React.useContext(PointsContext);
17 | const { state: translationState } = React.useContext(TranslationContext);
18 | const { theme } = useThemeUI();
19 |
20 | const currentStart: Vec2 = pointsState[pointsState.current];
21 | const currentEnd: Vec2 = translationState[pointsState.current];
22 | const firstStep: Vec2 = {
23 | x: currentStart.x * a,
24 | y: currentStart.x * b,
25 | };
26 | const secondStep: Vec2 = {
27 | x: currentStart.y * c,
28 | y: currentStart.y * d,
29 | };
30 |
31 | return (
32 |
33 |
41 |
48 |
56 |
63 |
71 |
72 | );
73 | };
74 |
75 | export default Transition;
76 |
--------------------------------------------------------------------------------
/components/parts/coord_system/transition/index.ts:
--------------------------------------------------------------------------------
1 | import Transition from './Transition';
2 |
3 | export default Transition;
4 |
--------------------------------------------------------------------------------
/components/parts/equation/Equation.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex } from 'theme-ui';
3 | import Segment from '../../common/segment';
4 | import MatrixContext from '../../../context/matrix/context';
5 | import BracketPair from './bracket_pair';
6 | import Bond from './bond';
7 | import PointsContext from '../../../context/points/context';
8 | import { Vec2 } from '../../../context/points/types';
9 | import TranslationContext from '../../../context/translation/context';
10 |
11 | const Equation: React.FC = () => {
12 | const {
13 | state: {
14 | a, c, e,
15 | b, d, f,
16 | },
17 | } = React.useContext(MatrixContext);
18 | const {
19 | state,
20 | } = React.useContext(PointsContext);
21 | const {
22 | state: translationState,
23 | } = React.useContext(TranslationContext);
24 | const point: Vec2 = state[state.current];
25 | return (
26 |
35 |
44 |
51 |
56 |
63 |
68 |
75 |
78 |
85 |
86 |
87 | );
88 | };
89 |
90 | export default Equation;
91 |
--------------------------------------------------------------------------------
/components/parts/equation/bond/Bond.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex, Text } from 'theme-ui';
3 |
4 | interface Props {
5 | mode: 'equation' | 'result';
6 | propName?: 'x' | 'y';
7 | propValue?: number;
8 | }
9 |
10 | const Bond: React.FC = ({
11 | mode, propName, propValue,
12 | }) => {
13 | const color = propName === 'x' ? 'tertiary' : 'secondary';
14 | return (
15 |
25 | {mode === 'equation' && propName ? (
26 | <>
27 |
36 | {propName.toUpperCase()}
37 | old
38 |
39 |
48 |
54 | ×
55 |
56 |
64 | {propValue}
65 |
66 |
72 | +
73 |
74 |
75 | >
76 | ) : (
77 |
83 | ⟹
84 |
85 | )}
86 |
87 | );
88 | };
89 |
90 | export default Bond;
91 |
--------------------------------------------------------------------------------
/components/parts/equation/bond/index.ts:
--------------------------------------------------------------------------------
1 | import Bond from './Bond';
2 |
3 | export default Bond;
4 |
--------------------------------------------------------------------------------
/components/parts/equation/bracket_pair/BracketPair.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex, Text, useThemeUI } from 'theme-ui';
3 | import BracketLeft from '../../../common/bracket_left';
4 | import BracketRight from '../../../common/bracket_right';
5 |
6 | interface Props {
7 | mode: 'equation' | 'result';
8 | modifierTop: string;
9 | modifierBottom: string;
10 | modifierTopValue: number;
11 | modifierBottomValue: number;
12 | }
13 |
14 | const BracketPair: React.FC = ({
15 | mode, modifierTop, modifierBottom, modifierTopValue, modifierBottomValue,
16 | }) => {
17 | const { theme } = useThemeUI();
18 | return (
19 |
27 |
28 |
36 |
45 | {mode === 'equation' ? modifierTop : (
46 | <>
47 | {modifierTop.toUpperCase()}
48 | new
49 | >
50 | )}
51 |
52 |
61 |
70 | {modifierTopValue}
71 |
72 |
81 | {modifierBottomValue}
82 |
83 |
84 |
94 | {mode === 'equation' ? modifierBottom : (
95 | <>
96 | {modifierBottom.toUpperCase()}
97 | new
98 | >
99 | )}
100 |
101 |
102 |
103 |
104 | );
105 | };
106 |
107 | export default BracketPair;
108 |
--------------------------------------------------------------------------------
/components/parts/equation/bracket_pair/index.ts:
--------------------------------------------------------------------------------
1 | import BracketPair from './BracketPair';
2 |
3 | export default BracketPair;
4 |
--------------------------------------------------------------------------------
/components/parts/equation/index.ts:
--------------------------------------------------------------------------------
1 | import Equation from './Equation';
2 |
3 | export default Equation;
4 |
--------------------------------------------------------------------------------
/components/parts/faq/Faq.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex, Button } from 'theme-ui';
3 | import TourContext from '../../../context/tour/context';
4 |
5 | const Faq: React.FC = () => {
6 | const {
7 | dispatch,
8 | } = React.useContext(TourContext);
9 | const handleClick = React.useCallback(() => {
10 | dispatch({
11 | type: 'setStep',
12 | payload: 1,
13 | });
14 | dispatch({
15 | type: 'toggle',
16 | payload: true,
17 | });
18 | }, [dispatch]);
19 | return (
20 |
29 |
44 |
45 | );
46 | };
47 |
48 | export default Faq;
49 |
--------------------------------------------------------------------------------
/components/parts/faq/index.ts:
--------------------------------------------------------------------------------
1 | import Faq from './Faq';
2 |
3 | export default Faq;
4 |
--------------------------------------------------------------------------------
/components/parts/footer/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex, Heading, Link } from 'theme-ui';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 | import { faTwitter } from '@fortawesome/free-brands-svg-icons';
5 | import { faBlog } from '@fortawesome/free-solid-svg-icons';
6 |
7 | const iconLinkProps: { [key: string]: any } = {
8 | target: '_blank',
9 | mr: 40,
10 | };
11 |
12 | const Footer: React.FC = () => (
13 |
20 |
29 | Visual Guide To SVG Matrix
30 |
31 |
32 |
37 | made by afternoon2
38 |
39 |
40 |
47 |
53 |
54 |
55 |
62 |
63 |
64 |
65 |
66 | );
67 |
68 | export default Footer;
69 |
--------------------------------------------------------------------------------
/components/parts/footer/index.ts:
--------------------------------------------------------------------------------
1 | import Footer from './Footer';
2 |
3 | export default Footer;
4 |
--------------------------------------------------------------------------------
/components/parts/function/Function.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex } from 'theme-ui';
3 | import MatrixContext from '../../../context/matrix/context';
4 | import Segment from '../../common/segment';
5 | import RoundedInput from '../../common/rounded_input';
6 |
7 | const Function: React.FC = () => {
8 | const {
9 | state: {
10 | a, b, c, d, e, f,
11 | },
12 | dispatch,
13 | } = React.useContext(MatrixContext);
14 |
15 | const handleUpdate = React.useCallback((label, value) => {
16 | dispatch({
17 | type: 'set',
18 | payload: {
19 | [label]: value,
20 | },
21 | });
22 | }, [dispatch]);
23 |
24 | return (
25 |
33 |
43 |
50 |
57 |
64 |
71 |
78 |
85 |
86 |
87 | );
88 | };
89 |
90 | export default Function;
91 |
--------------------------------------------------------------------------------
/components/parts/function/index.ts:
--------------------------------------------------------------------------------
1 | import Function from './Function';
2 |
3 | export default Function;
4 |
--------------------------------------------------------------------------------
/components/parts/layout/Layout.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSpring, animated } from 'react-spring';
3 | import { Flex } from 'theme-ui';
4 | import TourContext from '../../../context/tour/context';
5 | import Code from '../code';
6 | import Footer from '../footer';
7 | import Function from '../function';
8 | import Equation from '../equation';
9 | import CoordSystem from '../coord_system';
10 | import Faq from '../faq';
11 | import Tour from '../tour/Tour';
12 | import Transformation from '../transformation';
13 |
14 | const TOOLBAR_WIDTH = 700;
15 |
16 | const Layout: React.FC = animated(() => {
17 | const { state: { open } } = React.useContext(TourContext);
18 | const props = useSpring({
19 | opacity: 1,
20 | from: {
21 | opacity: 0,
22 | },
23 | });
24 | return (
25 |
32 |
41 |
42 |
43 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | {open && }
66 |
67 | );
68 | });
69 |
70 | export default Layout;
71 |
--------------------------------------------------------------------------------
/components/parts/layout/index.ts:
--------------------------------------------------------------------------------
1 | import Layout from './Layout';
2 |
3 | export default Layout;
4 |
--------------------------------------------------------------------------------
/components/parts/tour/Tour.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Step from './step/Step';
3 | import Intro from '../../../content/intro.mdx';
4 | import TourContext from '../../../context/tour/context';
5 | import Function from '../../../content/function.mdx';
6 | import Equation from '../../../content/equation.mdx';
7 | import CoordSystem from '../../../content/coord_system.mdx';
8 | import Summary from '../../../content/summary.mdx';
9 | import Overlay from './overlay';
10 |
11 | interface TourStep {
12 | target: string;
13 | element: JSX.Element;
14 | }
15 |
16 | const steps: { [key: string]: JSX.Element } = {
17 | 1: (
18 |
19 |
20 |
21 | ),
22 | 2: (
23 |
24 |
25 |
26 | ),
27 | 3: (
28 |
29 |
30 |
31 | ),
32 | 4: (
33 |
34 |
35 |
36 | ),
37 | 5: (
38 |
39 |
40 |
41 | ),
42 | };
43 |
44 | const Tour: React.FC = () => {
45 | const {
46 | state: {
47 | step,
48 | },
49 | } = React.useContext(TourContext);
50 |
51 | return (
52 | <>
53 | {step ? (
54 |
55 | {steps[step]}
56 |
57 | ) : null}
58 | >
59 | );
60 | };
61 | export default Tour;
62 |
--------------------------------------------------------------------------------
/components/parts/tour/overlay/Overlay.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex } from 'theme-ui';
3 |
4 | const Overlay: React.FC = ({ children }) => (
5 |
18 | {children}
19 |
20 | );
21 |
22 | export default Overlay;
23 |
--------------------------------------------------------------------------------
/components/parts/tour/overlay/index.ts:
--------------------------------------------------------------------------------
1 | import Overlay from './Overlay';
2 |
3 | export default Overlay;
4 |
--------------------------------------------------------------------------------
/components/parts/tour/step/Step.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Button, Flex, Heading, Text, useThemeUI,
4 | } from 'theme-ui';
5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
6 | import { faTimes } from '@fortawesome/free-solid-svg-icons';
7 | import TourContext from '../../../../context/tour/context';
8 | import OutsideClickHandler from '../../../common/outside_click_handler';
9 |
10 | interface Props {
11 | title: string;
12 | }
13 |
14 | const MAX_STEPS = 5;
15 |
16 | const Step: React.FC = ({ title, children }) => {
17 | const {
18 | state: {
19 | step,
20 | },
21 | dispatch,
22 | } = React.useContext(TourContext);
23 |
24 | const handleClickPrev = React.useCallback(() => {
25 | dispatch({
26 | type: 'setStep',
27 | payload: step - 1,
28 | });
29 | }, [dispatch, step]);
30 |
31 | const handleClickNext = React.useCallback(() => {
32 | dispatch({
33 | type: 'setStep',
34 | payload: step + 1,
35 | });
36 | }, [dispatch, step]);
37 |
38 | const handleClose = React.useCallback(() => {
39 | dispatch({
40 | type: 'setStep',
41 | payload: 0,
42 | });
43 | dispatch({
44 | type: 'toggle',
45 | payload: false,
46 | });
47 | }, [dispatch]);
48 |
49 | const { theme } = useThemeUI();
50 | return (
51 |
60 |
72 |
80 |
81 |
82 | {title}
83 |
84 |
85 |
86 |
87 |
88 |
95 | {children}
96 |
97 |
98 |
105 |
119 |
120 |
133 |
134 |
135 |
136 | );
137 | };
138 |
139 | export default Step;
140 |
--------------------------------------------------------------------------------
/components/parts/transform_slider/TransformSlider.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex, Button, Input } from 'theme-ui';
3 | import TransformationContext from '../../../context/transformation/context';
4 |
5 | const TransformSlider: React.FC = () => {
6 | const {
7 | state: { step },
8 | dispatch,
9 | } = React.useContext(TransformationContext);
10 | const handleChange = React.useCallback((event: React.ChangeEvent) => {
11 | const { value } = event.target;
12 | const parsed = parseInt(value, 10);
13 | dispatch({
14 | type: parsed > step ? 'increment' : 'decrement',
15 | });
16 | }, [dispatch, step]);
17 | const handleIncrement = React.useCallback(() => {
18 | dispatch({ type: 'increment' });
19 | }, [dispatch]);
20 | const handleDecrement = React.useCallback(() => {
21 | dispatch({ type: 'decrement' });
22 | }, [dispatch]);
23 | return (
24 |
31 |
38 |
49 |
56 |
57 | );
58 | };
59 |
60 | export default TransformSlider;
61 |
--------------------------------------------------------------------------------
/components/parts/transform_slider/index.ts:
--------------------------------------------------------------------------------
1 | import TransformSlider from './TransformSlider';
2 |
3 | export default TransformSlider;
4 |
--------------------------------------------------------------------------------
/components/parts/transform_switch/TransformSwitch.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Checkbox, Label, Text } from 'theme-ui';
3 |
4 | interface Props {
5 | label: string;
6 | checked: boolean;
7 | onChange: (event: React.ChangeEvent) => void;
8 | }
9 |
10 | const TransformSwitch: React.FC = ({ label, checked, onChange }) => (
11 |
34 | );
35 |
36 | export default TransformSwitch;
37 |
--------------------------------------------------------------------------------
/components/parts/transform_switch/index.ts:
--------------------------------------------------------------------------------
1 | import TransformSwitch from './TransformSwitch';
2 |
3 | export default TransformSwitch;
4 |
--------------------------------------------------------------------------------
/components/parts/transformation/Transformation.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex } from 'theme-ui';
3 | import Segment from '../../common/segment';
4 | import TransformationContext from '../../../context/transformation/context';
5 | import TransformSlider from '../transform_slider';
6 | import TransformSwitch from '../transform_switch';
7 |
8 | const Transformation: React.FC = () => {
9 | const {
10 | state: { visible, trail },
11 | dispatch,
12 | } = React.useContext(TransformationContext);
13 |
14 | const handleChange = React.useCallback(() => {
15 | dispatch({
16 | type: 'toggle',
17 | });
18 | }, [dispatch]);
19 | const handleStepsChange = React.useCallback(() => {
20 | dispatch({
21 | type: 'toggleSteps',
22 | });
23 | }, [dispatch]);
24 | return (
25 |
34 |
41 |
46 |
51 |
52 |
53 |
54 | );
55 | };
56 |
57 | export default Transformation;
58 |
--------------------------------------------------------------------------------
/components/parts/transformation/index.ts:
--------------------------------------------------------------------------------
1 | import TransformSwitch from './Transformation';
2 |
3 | export default TransformSwitch;
4 |
--------------------------------------------------------------------------------
/components/states/figure/FigureState.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FigureContext from '../../../context/figure/context';
3 | import figureReducer, { initialState } from '../../../context/figure/reducer';
4 | import PointsContext from '../../../context/points/context';
5 |
6 | const FigureState: React.FC = ({ children }) => {
7 | const { dispatch: pointDispatch } = React.useContext(PointsContext);
8 | const [state, dispatch] = React.useReducer(figureReducer, initialState);
9 |
10 | React.useEffect(() => {
11 | pointDispatch({
12 | type: 'set',
13 | payload: {
14 | topLeft: {
15 | x: state.x,
16 | y: state.y,
17 | },
18 | topRight: {
19 | x: state.x + state.width,
20 | y: state.y,
21 | },
22 | bottomRight: {
23 | x: state.x + state.width,
24 | y: state.y + state.height,
25 | },
26 | bottomLeft: {
27 | x: state.x,
28 | y: state.y + state.height,
29 | },
30 | },
31 | });
32 | }, [pointDispatch, state]);
33 |
34 | return (
35 |
38 | {children}
39 |
40 | );
41 | };
42 |
43 | export default FigureState;
44 |
--------------------------------------------------------------------------------
/components/states/figure/index.ts:
--------------------------------------------------------------------------------
1 | import FigureState from './FigureState';
2 |
3 | export default FigureState;
4 |
--------------------------------------------------------------------------------
/components/states/matrix/MatrixState.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MatrixContext from '../../../context/matrix/context';
3 | import matrixReducer, { initialState } from '../../../context/matrix/reducer';
4 |
5 | const MatrixState: React.FC = ({ children }) => {
6 | const [state, dispatch] = React.useReducer(matrixReducer, initialState);
7 |
8 | return (
9 |
10 | {children}
11 |
12 | );
13 | };
14 |
15 | export default MatrixState;
16 |
--------------------------------------------------------------------------------
/components/states/matrix/index.ts:
--------------------------------------------------------------------------------
1 | import MatrixState from './MatrixState';
2 |
3 | export default MatrixState;
4 |
--------------------------------------------------------------------------------
/components/states/points/PointsState.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PointsContext from '../../../context/points/context';
3 | import pointsReducer, { initialState } from '../../../context/points/reducer';
4 |
5 | const PointsState: React.FC = ({ children }) => {
6 | const [state, dispatch] = React.useReducer(
7 | pointsReducer,
8 | initialState,
9 | );
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | };
17 |
18 | export default PointsState;
19 |
--------------------------------------------------------------------------------
/components/states/points/index.ts:
--------------------------------------------------------------------------------
1 | import PointsState from './PointsState';
2 |
3 | export default PointsState;
4 |
--------------------------------------------------------------------------------
/components/states/tour/TourState.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TourContext from '../../../context/tour/context';
3 | import tourReducer, { initialTourState } from '../../../context/tour/reducer';
4 |
5 | const TourState: React.FC = ({ children }) => {
6 | const [state, dispatch] = React.useReducer(tourReducer, initialTourState);
7 | return (
8 |
9 | {children}
10 |
11 | );
12 | };
13 |
14 | export default TourState;
15 |
--------------------------------------------------------------------------------
/components/states/tour/index.ts:
--------------------------------------------------------------------------------
1 | import TourState from './TourState';
2 |
3 | export default TourState;
4 |
--------------------------------------------------------------------------------
/components/states/transformation/TransformationState.tsx:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from 'react';
2 | import transformationReducer, { initialTransformationState } from '../../../context/transformation/reducer';
3 | import TransformationContext from '../../../context/transformation/context';
4 |
5 | const TransformationState: React.FC = ({ children }) => {
6 | const [state, dispatch] = useReducer(
7 | transformationReducer,
8 | initialTransformationState,
9 | );
10 | return (
11 |
12 | {children}
13 |
14 | );
15 | };
16 |
17 | export default TransformationState;
18 |
--------------------------------------------------------------------------------
/components/states/transformation/index.ts:
--------------------------------------------------------------------------------
1 | import TransformationState from './TransformationState';
2 |
3 | export default TransformationState;
4 |
--------------------------------------------------------------------------------
/components/states/translation/TranslationState.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TranslationContext from '../../../context/translation/context';
3 | import translationReducer, { initialState } from '../../../context/translation/reducer';
4 | import MatrixContext from '../../../context/matrix/context';
5 | import PointsContext from '../../../context/points/context';
6 | import { CurrentPoint, Vec2 } from '../../../context/points/types';
7 |
8 | const TranslationState: React.FC = ({ children }) => {
9 | const [state, dispatch] = React.useReducer(
10 | translationReducer,
11 | initialState,
12 | );
13 |
14 | const {
15 | state: {
16 | a, c, e,
17 | b, d, f,
18 | },
19 | } = React.useContext(MatrixContext);
20 |
21 | const {
22 | state: pointsState,
23 | } = React.useContext(PointsContext);
24 |
25 | const getNewCoords = React.useCallback(
26 | (point: CurrentPoint): Vec2 => {
27 | const pX: number = pointsState[point].x;
28 | const pY: number = pointsState[point].y;
29 | return {
30 | x: Math.round(a * pX + c * pY + e),
31 | y: Math.round(b * pX + d * pY + f),
32 | };
33 | }, [a, b, c, d, e, f, pointsState],
34 | );
35 |
36 | React.useEffect(() => {
37 | dispatch({
38 | type: 'set',
39 | payload: {
40 | topLeft: getNewCoords('topLeft'),
41 | topRight: getNewCoords('topRight'),
42 | bottomLeft: getNewCoords('bottomLeft'),
43 | bottomRight: getNewCoords('bottomRight'),
44 | },
45 | });
46 | }, [dispatch, getNewCoords]);
47 |
48 | return (
49 |
50 | {children}
51 |
52 | );
53 | };
54 |
55 | export default TranslationState;
56 |
--------------------------------------------------------------------------------
/components/states/translation/index.ts:
--------------------------------------------------------------------------------
1 | import TranslationState from './TranslationState';
2 |
3 | export default TranslationState;
4 |
--------------------------------------------------------------------------------
/content/coord_system.mdx:
--------------------------------------------------------------------------------
1 | Here you can see transformation in action.
2 |
3 | - Drag the root rectangle (orange stroke) and observe changes in the transformed figure (opaque orange background)
4 | - Click on the root rectangle's corners to see their coordinates.
5 | - Enable transformation steps display to see the road of the transformation (you'll be guided by the step controller in `Transformation Steps` section).
6 | - You can also enable the trail visibility.
7 |
--------------------------------------------------------------------------------
/content/equation.mdx:
--------------------------------------------------------------------------------
1 | Here we can see the process in a little bit mathy way.
2 |
3 | Horizontal axis modifiers are placed on top, while vertical on the bottom. `a` is multiplied by the current point's `X` value. `b` on the bottom does the same. That's how the first transformation step is made.
4 |
5 | Then we add `c × Y` and `d × Y` to them (second step). The transformation is done after adding `e` and `f` respectively.
6 |
--------------------------------------------------------------------------------
/content/function.mdx:
--------------------------------------------------------------------------------
1 | Drag the cursor inside the circle area to update each modifier's value.
2 |
3 | You can also double click it to update the value using an input.
4 |
5 | `X` modifying values are hightlighted with pink-ish color. `Y` modifiers are marked with dark orange.
--------------------------------------------------------------------------------
/content/intro.mdx:
--------------------------------------------------------------------------------
1 | SVG `matrix` is a transformation function that [maps coordinates from a previous coordinate system into a new coordinate system](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform).
2 |
3 | It takes six parameters - `a`, `b`, `c`, `d`, `e` and `f`, where odd elements (`a`, `c` and `e`) are modifying the next `X` coordinate, while the even ones (`b`, `d` and `f`) do the same with the `Y` coordinate.
4 |
5 | The matrix function is applied to every point that creates a shape. In case of a rectangle (like in this app), it transforms the `topLeft`, `topRight`, `botttomRight` and `bottomLeft` corners.
6 |
7 | You can check this process using every part of the UI.
8 |
--------------------------------------------------------------------------------
/content/summary.mdx:
--------------------------------------------------------------------------------
1 | Click on the `Code` pane to copy the matrix value to the clipboard.
2 |
3 | Feel free to fork this project on GitHub or raise an issue and/or PR if you want to contribute!
4 |
--------------------------------------------------------------------------------
/context/figure/context.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FigureContextValue } from './types';
3 | import { initialState } from './reducer';
4 |
5 | const FigureContext = React.createContext({
6 | state: initialState,
7 | dispatch: () => null,
8 | });
9 |
10 | export default FigureContext;
11 |
--------------------------------------------------------------------------------
/context/figure/reducer.ts:
--------------------------------------------------------------------------------
1 | import { FigureState, FigureAction } from './types';
2 |
3 | export const initialState: FigureState = {
4 | x: 100,
5 | y: 100,
6 | width: 250,
7 | height: 250,
8 | };
9 |
10 | const figureReducer = (
11 | state: FigureState,
12 | { type, payload }: FigureAction,
13 | ): FigureState => {
14 | switch (type) {
15 | case 'set':
16 | return {
17 | ...state,
18 | ...payload,
19 | };
20 | default:
21 | return initialState;
22 | }
23 | };
24 |
25 | export default figureReducer;
26 |
--------------------------------------------------------------------------------
/context/figure/types.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch } from "react";
2 |
3 | export interface FigureState {
4 | x: number;
5 | y: number;
6 | width: number;
7 | height: number;
8 | }
9 |
10 | export interface FigureAction {
11 | type: 'set';
12 | payload: Partial;
13 | }
14 |
15 | export interface FigureContextValue {
16 | state: FigureState;
17 | dispatch: Dispatch;
18 | }
19 |
--------------------------------------------------------------------------------
/context/matrix/context.ts:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { MatrixContextValue } from './types'
3 | import { initialState } from './reducer'
4 |
5 | const MatrixContext = React.createContext({
6 | state: initialState,
7 | dispatch: () => null,
8 | });
9 |
10 | export default MatrixContext;
11 |
--------------------------------------------------------------------------------
/context/matrix/reducer.ts:
--------------------------------------------------------------------------------
1 | import { MatrixState, MatrixAction } from "./types";
2 |
3 | export const initialState: MatrixState = {
4 | a: 1,
5 | b: 1,
6 | c: 1,
7 | d: 0,
8 | e: 250,
9 | f: -50,
10 | };
11 |
12 | const matrixReducer = (
13 | state: MatrixState,
14 | { type, payload }: MatrixAction
15 | ): MatrixState => {
16 | switch (type) {
17 | case 'set':
18 | return {
19 | ...state,
20 | ...payload,
21 | };
22 | default:
23 | return initialState;
24 | }
25 | };
26 |
27 | export default matrixReducer;
28 |
--------------------------------------------------------------------------------
/context/matrix/types.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch } from "react";
2 |
3 | export interface MatrixState {
4 | a: number;
5 | b: number;
6 | c: number;
7 | d: number;
8 | e: number;
9 | f: number;
10 | }
11 |
12 | export interface MatrixAction {
13 | type: 'set';
14 | payload: Partial;
15 | }
16 |
17 | export interface MatrixContextValue {
18 | state: MatrixState;
19 | dispatch: Dispatch;
20 | }
21 |
--------------------------------------------------------------------------------
/context/points/context.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PointsContextValue } from './types';
3 | import { initialState } from './reducer';
4 |
5 | const PointsContext = React.createContext({
6 | state: initialState,
7 | dispatch: () => null,
8 | });
9 |
10 | export default PointsContext;
11 |
--------------------------------------------------------------------------------
/context/points/reducer.ts:
--------------------------------------------------------------------------------
1 | import { PointsState, PointsAction } from './types';
2 |
3 | export const initialState: PointsState = {
4 | current: 'topLeft',
5 | topLeft: {
6 | x: 100,
7 | y: 100,
8 | },
9 | topRight: {
10 | x: 5,
11 | y: 1,
12 | },
13 | bottomRight: {
14 | x: 250,
15 | y: 250,
16 | },
17 | bottomLeft: {
18 | x: 1,
19 | y: 5,
20 | },
21 | };
22 |
23 | const pointsReducer = (
24 | state: PointsState,
25 | { type, payload }: PointsAction,
26 | ): PointsState => {
27 | switch (type) {
28 | case 'set':
29 | return {
30 | ...state,
31 | ...payload,
32 | };
33 | default:
34 | return initialState;
35 | }
36 | };
37 |
38 | export default pointsReducer;
39 |
--------------------------------------------------------------------------------
/context/points/types.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch } from "react";
2 |
3 | export interface Vec2 {
4 | x: number;
5 | y: number;
6 | }
7 |
8 | export type CurrentPoint = 'topLeft'
9 | | 'topRight'
10 | | 'bottomRight'
11 | | 'bottomLeft';
12 |
13 | export interface PointsState {
14 | current: CurrentPoint;
15 | topLeft: Vec2;
16 | topRight: Vec2;
17 | bottomRight: Vec2;
18 | bottomLeft: Vec2;
19 | }
20 |
21 | export interface PointsAction {
22 | type: 'set';
23 | payload: Partial;
24 | }
25 |
26 | export interface PointsContextValue {
27 | state: PointsState;
28 | dispatch: Dispatch;
29 | }
30 |
--------------------------------------------------------------------------------
/context/tour/context.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 | import { TourContextValue } from './types';
3 | import { initialTourState } from './reducer';
4 | ;
5 |
6 | export default createContext({
7 | state: initialTourState,
8 | dispatch: () => null,
9 | });
10 |
--------------------------------------------------------------------------------
/context/tour/reducer.ts:
--------------------------------------------------------------------------------
1 | import { TourState, TourAction } from "./types"
2 |
3 | export const initialTourState: TourState = {
4 | step: 1,
5 | open: false,
6 | }
7 |
8 | const tourReducer = (
9 | state: TourState,
10 | { type, payload }: TourAction,
11 | ): TourState => {
12 | switch (type) {
13 | case 'setStep':
14 | return {
15 | ...state,
16 | step: payload as number,
17 | };
18 | case 'toggle':
19 | return {
20 | ...state,
21 | open: payload as boolean,
22 | }
23 | default:
24 | return initialTourState;
25 | }
26 | };
27 |
28 | export default tourReducer;
29 |
--------------------------------------------------------------------------------
/context/tour/types.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch } from "react";
2 |
3 | export interface TourState {
4 | step: number;
5 | open: boolean;
6 | }
7 |
8 | export interface TourAction {
9 | type: 'setStep' | 'toggle';
10 | payload: number | boolean;
11 | }
12 |
13 | export interface TourContextValue {
14 | state: TourState;
15 | dispatch: Dispatch;
16 | }
17 |
--------------------------------------------------------------------------------
/context/transformation/context.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TransformationContextValue } from './types';
3 | import { initialTransformationState } from './reducer';
4 |
5 | const TransformationContext = React.createContext({
6 | state: initialTransformationState,
7 | dispatch: () => null,
8 | });
9 |
10 | export default TransformationContext;
11 |
--------------------------------------------------------------------------------
/context/transformation/reducer.ts:
--------------------------------------------------------------------------------
1 | import { TransformationState, TransformationAction } from './types';
2 |
3 | export const initialTransformationState: TransformationState = {
4 | visible: true,
5 | step: 1,
6 | trail: true,
7 | }
8 |
9 | const transformationReducer = (
10 | state: TransformationState,
11 | { type }: TransformationAction
12 | ): TransformationState => {
13 | switch (type) {
14 | case 'decrement':
15 | return {
16 | ...state,
17 | step: state.step === 1 ? 4 : state.step - 1,
18 | };
19 | case 'increment':
20 | return {
21 | ...state,
22 | step: state.step === 4 ? 1 : state.step + 1,
23 | };
24 | case 'toggle':
25 | return {
26 | ...state,
27 | visible: !state.visible,
28 | };
29 | case 'toggleSteps':
30 | return {
31 | ...state,
32 | trail: !state.trail,
33 | };
34 | default:
35 | return initialTransformationState;
36 | }
37 | };
38 |
39 | export default transformationReducer
--------------------------------------------------------------------------------
/context/transformation/types.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch } from 'react';
2 |
3 | export interface TransformationState {
4 | visible: boolean;
5 | step: number;
6 | trail: boolean;
7 | }
8 |
9 | export interface TransformationAction {
10 | type: 'increment' | 'decrement' | 'toggle' | 'toggleSteps';
11 | }
12 |
13 | export interface TransformationContextValue {
14 | state: TransformationState;
15 | dispatch: Dispatch;
16 | }
--------------------------------------------------------------------------------
/context/translation/context.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TranslationContextValue } from './types';
3 | import { initialState } from './reducer';
4 |
5 | const TranslationContext = React.createContext({
6 | state: initialState,
7 | dispatch: () => null,
8 | });
9 |
10 | export default TranslationContext;
11 |
--------------------------------------------------------------------------------
/context/translation/reducer.ts:
--------------------------------------------------------------------------------
1 | import { TranslationState, TranslationAction } from './types';
2 |
3 | export const initialState: TranslationState = {
4 | topLeft: {
5 | x: 0,
6 | y: 0,
7 | },
8 | topRight: {
9 | x: 0,
10 | y: 0,
11 | },
12 | bottomRight: {
13 | x: 0,
14 | y: 0,
15 | },
16 | bottomLeft: {
17 | x: 0,
18 | y: 0,
19 | },
20 | };
21 |
22 | const translationReducer = (
23 | state: TranslationState,
24 | { type, payload }: TranslationAction,
25 | ): TranslationState => {
26 | switch (type) {
27 | case 'set':
28 | return {
29 | ...state,
30 | ...payload,
31 | };
32 | default:
33 | return initialState;
34 | }
35 | };
36 |
37 | export default translationReducer;
38 |
--------------------------------------------------------------------------------
/context/translation/types.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch } from 'react';
2 | import { Vec2 } from '../points/types';
3 |
4 | export interface TranslationState {
5 | topLeft: Vec2,
6 | topRight: Vec2,
7 | bottomRight: Vec2,
8 | bottomLeft: Vec2,
9 | }
10 |
11 | export interface TranslationAction {
12 | type: 'set';
13 | payload: Partial;
14 | }
15 |
16 | export interface TranslationContextValue {
17 | state: TranslationState;
18 | dispatch: Dispatch;
19 | }
20 |
--------------------------------------------------------------------------------
/hooks/useDrag.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Vec2 } from '../context/points/types';
3 |
4 | type UseDragHook = [
5 | (event: React.MouseEvent) => void,
6 | (event: React.TouchEvent) => void,
7 | ];
8 |
9 | export default (
10 | onUpdate: (newValue: Vec2) => void,
11 | onDown: (event: React.MouseEvent | React.TouchEvent) => void,
12 | ): UseDragHook => {
13 | const handleMouseUp = (event: MouseEvent) => {
14 | document.removeEventListener('mousemove', handleMouseMove);
15 | document.removeEventListener('touchmove', handleMouseMove);
16 | document.removeEventListener('mouseup', handleMouseUp);
17 | event.preventDefault();
18 | return false;
19 | };
20 |
21 | const handleMouseMove = React.useCallback((event: MouseEvent | TouchEvent) => {
22 | const x: number = event.type === 'touchmove'
23 | ? (event as TouchEvent).touches[0].clientX
24 | : (event as MouseEvent).clientX;
25 | const y: number = event.type === 'touchmove'
26 | ? (event as TouchEvent).touches[0].clientY
27 | : (event as MouseEvent).clientY;
28 | onUpdate({ x, y });
29 | }, [onUpdate]);
30 |
31 | const onMouseDown = React.useCallback((event: React.MouseEvent): boolean => {
32 | onDown(event);
33 | document.addEventListener('mousemove', handleMouseMove);
34 | document.addEventListener('mouseup', handleMouseUp);
35 | event.preventDefault();
36 | return false;
37 | }, [onDown]);
38 |
39 | const onTouchStart = React.useCallback((event: React.TouchEvent): boolean => {
40 | onDown(event);
41 | document.addEventListener('touchmove', handleMouseMove);
42 | document.addEventListener('touchend', handleMouseUp);
43 | event.preventDefault();
44 | return false;
45 | }, [onDown]);
46 |
47 | return [
48 | onMouseDown,
49 | onTouchStart,
50 | ];
51 | };
52 |
--------------------------------------------------------------------------------
/hooks/useMouseMove.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default (
4 | onUpdate: (newValue: number) => void,
5 | currentValue: number,
6 | ) => {
7 | const startY = React.useRef(0);
8 | const handleMouseUp = (event: MouseEvent) => {
9 | document.removeEventListener('mousemove', handleMouseMove);
10 | document.removeEventListener('touchmove', handleMouseMove);
11 | document.removeEventListener('mouseup', handleMouseUp);
12 | event.preventDefault();
13 | return false;
14 | };
15 |
16 | const handleMouseMove = React.useCallback((event: MouseEvent | TouchEvent) => {
17 | const clientY: number = event.type === 'touchmove'
18 | ? (event as TouchEvent).touches[0].clientY
19 | : (event as MouseEvent).clientY;
20 | onUpdate(
21 | clientY > startY.current
22 | ? Math.round(currentValue - (clientY - startY.current) / 10)
23 | : Math.round(currentValue + (startY.current - clientY) / 10),
24 | );
25 | event.preventDefault();
26 | return false;
27 | }, [startY, currentValue, onUpdate]);
28 |
29 | const onMouseDown = (event: React.MouseEvent) => {
30 | startY.current = event.clientY;
31 | document.addEventListener('mousemove', handleMouseMove);
32 | document.addEventListener('mouseup', handleMouseUp);
33 | event.preventDefault();
34 | return false;
35 | };
36 |
37 | const onTouchStart = (event: React.TouchEvent) => {
38 | startY.current = event.touches[0].clientY;
39 | document.addEventListener('touchmove', handleMouseMove);
40 | document.addEventListener('touchend', handleMouseUp);
41 | event.preventDefault();
42 | return false;
43 | }
44 |
45 | return [
46 | onMouseDown,
47 | onTouchStart,
48 | ]
49 | };
50 |
--------------------------------------------------------------------------------
/hooks/useOutsideClick.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default (
4 | ref: React.MutableRefObject,
5 | onClickOutside: () => void,
6 | ) => {
7 | const handleClickOutside = React.useCallback((event: React.MouseEvent) => {
8 | if (ref.current && !ref.current.contains(event.target)) {
9 | onClickOutside();
10 | }
11 | }, [ref, onClickOutside]);
12 |
13 | React.useEffect(() => {
14 | document.addEventListener('mousedown', handleClickOutside as any);
15 | return () => {
16 | document.removeEventListener('mousedown', handleClickOutside as any);
17 | }
18 | });
19 | };
20 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const withFonts = require('next-fonts');
2 | const withMDX = require('@next/mdx')();
3 |
4 | module.exports = withFonts(
5 | withMDX({
6 | pageExtensions: ['mdx', 'jsx', 'js', 'ts', 'tsx']
7 | })
8 | )
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svg-matrix-visual-guide",
3 | "version": "1.0.0",
4 | "description": "Visual Guide To SVG Matrix Function",
5 | "main": "index.js",
6 | "author": "Jakub Antolak ",
7 | "license": "MIT",
8 | "dependencies": {
9 | "@fortawesome/fontawesome-svg-core": "^1.2.26",
10 | "@fortawesome/free-brands-svg-icons": "^5.12.0",
11 | "@fortawesome/free-solid-svg-icons": "^5.12.0",
12 | "@fortawesome/react-fontawesome": "^0.1.8",
13 | "@mdx-js/loader": "^1.5.5",
14 | "@next/mdx": "^9.2.1",
15 | "@theme-ui/presets": "^0.3.0",
16 | "d3-scale": "^3.2.1",
17 | "next": "^9.2.1",
18 | "next-fonts": "^1.0.3",
19 | "polished": "^3.4.4",
20 | "react": "^16.12.0",
21 | "react-dom": "^16.12.0",
22 | "react-spring": "^8.0.27",
23 | "theme-ui": "^0.3.1"
24 | },
25 | "scripts": {
26 | "dev": "next -p 8080",
27 | "build": "next build",
28 | "start": "next start",
29 | "export": "next export",
30 | "commit": "git-cz",
31 | "deploy": "yarn build && yarn export",
32 | "lint": "eslint -c .eslintrc.js pages/*.tsx components/**/*.tsx",
33 | "lint:fix": "eslint -c .eslintrc.js pages/*.tsx components/**/*.tsx --fix"
34 | },
35 | "devDependencies": {
36 | "@commitlint/config-conventional": "^8.3.4",
37 | "@types/d3-scale": "^2.1.1",
38 | "@types/node": "^13.5.0",
39 | "@types/react": "^16.9.19",
40 | "@types/react-dom": "^16.9.5",
41 | "@typescript-eslint/eslint-plugin": "^2.17.0",
42 | "@typescript-eslint/parser": "^2.17.0",
43 | "cz-conventional-changelog": "3.0.2",
44 | "eslint": "^6.8.0",
45 | "eslint-config-airbnb": "^18.0.1",
46 | "eslint-plugin-import": "^2.20.0",
47 | "eslint-plugin-jsx-a11y": "^6.2.3",
48 | "eslint-plugin-react": "^7.18.0",
49 | "eslint-plugin-react-hooks": "^2.3.0",
50 | "husky": "^4.2.1",
51 | "typescript": "^3.7.5"
52 | },
53 | "config": {
54 | "commitizen": {
55 | "path": "./node_modules/cz-conventional-changelog"
56 | }
57 | },
58 | "husky": {
59 | "hooks": {
60 | "pre-commit": "yarn lint:fix",
61 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | ThemeProvider,
4 | } from 'theme-ui';
5 | import theme from '../theme/theme';
6 | import MatrixState from '../components/states/matrix';
7 | import FontFace from '../components/common/font_face';
8 | import PointsState from '../components/states/points';
9 | import Layout from '../components/parts/layout';
10 | import FigureState from '../components/states/figure';
11 | import TranslationState from '../components/states/translation';
12 | import Octocat from '../components/common/octocat';
13 | import TourState from '../components/states/tour/TourState';
14 | import TransformationState from '../components/states/transformation';
15 |
16 | const App: React.FC = () => (
17 | <>
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | >
36 | );
37 |
38 | export default App;
39 |
--------------------------------------------------------------------------------
/public/fonts/crimson-text-v10-latin-italic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afternoon2/svg-matrix-visual-guide/53c75ab0f2d7d3ccf56b379cbed61d6a2e227d78/public/fonts/crimson-text-v10-latin-italic.woff
--------------------------------------------------------------------------------
/public/fonts/crimson-text-v10-latin-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afternoon2/svg-matrix-visual-guide/53c75ab0f2d7d3ccf56b379cbed61d6a2e227d78/public/fonts/crimson-text-v10-latin-italic.woff2
--------------------------------------------------------------------------------
/public/fonts/crimson-text-v10-latin-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afternoon2/svg-matrix-visual-guide/53c75ab0f2d7d3ccf56b379cbed61d6a2e227d78/public/fonts/crimson-text-v10-latin-regular.woff
--------------------------------------------------------------------------------
/public/fonts/crimson-text-v10-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afternoon2/svg-matrix-visual-guide/53c75ab0f2d7d3ccf56b379cbed61d6a2e227d78/public/fonts/crimson-text-v10-latin-regular.woff2
--------------------------------------------------------------------------------
/public/fonts/fira-code-v7-latin-600.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afternoon2/svg-matrix-visual-guide/53c75ab0f2d7d3ccf56b379cbed61d6a2e227d78/public/fonts/fira-code-v7-latin-600.woff
--------------------------------------------------------------------------------
/public/fonts/fira-code-v7-latin-600.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afternoon2/svg-matrix-visual-guide/53c75ab0f2d7d3ccf56b379cbed61d6a2e227d78/public/fonts/fira-code-v7-latin-600.woff2
--------------------------------------------------------------------------------
/public/fonts/fira-code-v7-latin-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afternoon2/svg-matrix-visual-guide/53c75ab0f2d7d3ccf56b379cbed61d6a2e227d78/public/fonts/fira-code-v7-latin-regular.woff
--------------------------------------------------------------------------------
/public/fonts/fira-code-v7-latin-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/afternoon2/svg-matrix-visual-guide/53c75ab0f2d7d3ccf56b379cbed61d6a2e227d78/public/fonts/fira-code-v7-latin-regular.woff2
--------------------------------------------------------------------------------
/theme/theme.ts:
--------------------------------------------------------------------------------
1 | import { roboto } from '@theme-ui/presets';
2 | import { rgba, setLightness } from 'polished';
3 |
4 | const THEME = roboto;
5 |
6 | export default {
7 | ...THEME,
8 | colors: {
9 | /**
10 | * Taken from Happy Hues site by Mackenzie Child
11 | * https://www.happyhues.co/palettes/13
12 | */
13 | text: '#fffffe',
14 | gray: '#a7a9be',
15 | grayAlpha: rgba('#a7a9be', 0.25),
16 | background: '#0f0e17',
17 | backgroundLight: setLightness(0.15, '#0f0e17'),
18 | backgroundAlpha: rgba('#0f0e17', 0.25),
19 | backgroundAlphaDark: rgba('#0f0e17', 0.85),
20 | primary: '#ff8906',
21 | secondary: '#f25f4c',
22 | secondaryAlpha: rgba('#f25f4c', 0.5),
23 | tertiary: '#e53170',
24 | tertiaryAlpha: rgba('#e53170', 0.5),
25 | },
26 | styles: {
27 | ...THEME.styles,
28 | p: {
29 | fontSize: 21,
30 | fontFamily: 'body',
31 | color: 'gray',
32 | },
33 | a: {
34 | textDecoration: 'none',
35 | color: 'primary',
36 | },
37 | ul: {
38 | color: 'gray',
39 | fontSize: 21,
40 | },
41 | },
42 | breakpoints: [
43 | '1500px', '1800px', '1920px',
44 | ],
45 | fonts: {
46 | body: '"Crimson Text", serif',
47 | heading: '"Crimson Text", serif',
48 | legend: '"Crimson Text", serif',
49 | monospace: '"Fira Code", monospace',
50 | },
51 | fontSizes: [
52 | 9, 14, 18, 24,
53 | ],
54 | forms: {
55 | input: {
56 | borderColor: 'gray'
57 | },
58 | select: {
59 | borderColor: 'gray'
60 | }
61 | },
62 | fontWeights: {
63 | body: 400,
64 | heading: 400,
65 | legend: 600,
66 | monospace: 400,
67 | },
68 | buttons: {
69 | secondary: {
70 | bg: 'background',
71 | borderColor: 'primary',
72 | color: 'primary',
73 | borderWidth: '2px',
74 | borderStyle: 'solid',
75 | cursor: 'pointer',
76 | '&:disabled': {
77 | cursor: 'not-allowed',
78 | borderColor: 'gray',
79 | color: 'gray'
80 | }
81 | }
82 | },
83 | links: {
84 | transition: {
85 | color: 'gray',
86 | transition: '120ms',
87 | '&:hover': {
88 | color: 'primary',
89 | }
90 | },
91 | transitionText: {
92 | color: 'text',
93 | transition: '120ms',
94 | '&:hover': {
95 | color: 'primary',
96 | }
97 | }
98 | }
99 | };
100 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": false,
12 | "forceConsistentCasingInFileNames": true,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve"
20 | },
21 | "exclude": [
22 | "node_modules"
23 | ],
24 | "include": [
25 | "next-env.d.ts",
26 | "typings/mdx.d.ts",
27 | "**/*.ts",
28 | "**/*.tsx"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/types/svg/index.ts:
--------------------------------------------------------------------------------
1 | export type SvgComponent = React.FC>;
2 |
--------------------------------------------------------------------------------
/typings/@mdx-js/react/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@mdx-js/react' {
2 | import * as React from 'react'
3 | type ComponentType =
4 | | 'a'
5 | | 'blockquote'
6 | | 'code'
7 | | 'delete'
8 | | 'em'
9 | | 'h1'
10 | | 'h2'
11 | | 'h3'
12 | | 'h4'
13 | | 'h5'
14 | | 'h6'
15 | | 'hr'
16 | | 'img'
17 | | 'inlineCode'
18 | | 'li'
19 | | 'ol'
20 | | 'p'
21 | | 'pre'
22 | | 'strong'
23 | | 'sup'
24 | | 'table'
25 | | 'td'
26 | | 'thematicBreak'
27 | | 'tr'
28 | | 'ul'
29 | export type Components = {
30 | [key in ComponentType]?: React.ComponentType<{ children: React.ReactNode }>
31 | }
32 | export interface MDXProviderProps {
33 | children: React.ReactNode
34 | components: Components
35 | }
36 | export class MDXProvider extends React.Component { }
37 | }
--------------------------------------------------------------------------------
/typings/mdx.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.mdx' {
2 | let MDXComponent: (props: any) => JSX.Element
3 | export default MDXComponent
4 | }
--------------------------------------------------------------------------------