├── .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 | ![App Screenshot](https://raw.githubusercontent.com/afternoon2/svg-matrix-visual-guide/media/screenshot.png?token=AEGO7ADWVS4ACBM6ERDLTKK6IHEBK) -------------------------------------------------------------------------------- /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 | 10 | 11 | 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 | 10 | 11 | 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 | 29 | 30 | 38 | 43 | 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 | 85 | 92 | 99 | 102 | 103 | 104 | 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 |