内容简介:本文是“JavaScript 线性代数”教程的一部分。最近我撰写了这个线性代数系列的开篇之作。在新篇开始动笔前,我有了一个想法:使用 React 开发一个项目,来为这个系列的所有示例提供可视化功能一定很好玩!本系列的所有代码都存放于在本系列刚开始写作时,只有一个章节涉及了向量的基本运算。所以,目前实现一个能渲染二维坐标网格以及能将向量可视化为箭头的组件就够用了。本文最后做出的效果如下图所示,你也可以在此处进行体验。
本文是“JavaScript 线性代数”教程的一部分。
最近我撰写了这个线性代数系列的开篇之作。在新篇开始动笔前,我有了一个想法:使用 React 开发一个项目,来为这个系列的所有示例提供可视化功能一定很好玩!本系列的所有代码都存放于 此 GitHub 仓库 ,本文相关代码的提交记录位于 此处 。
目标
在本系列刚开始写作时,只有一个章节涉及了向量的基本运算。所以,目前实现一个能渲染二维坐标网格以及能将向量可视化为箭头的组件就够用了。本文最后做出的效果如下图所示,你也可以在此处进行体验。
创建 React 项目
其实已经有关于创建 React 项目的最佳实践指南文章可供参考,不过在本文中,我们将尽可能减少依赖的库,并简化对项目的配置。
create-react-app linear-algebra-demo cd linear-algebra-demo npm install --save react-sizeme styled-components 复制代码
上面的脚本安装了两个库。第一个库 react-sizeme
可以实现当窗体大小发生变化时,重新渲染网格组件。第二个库 styled-components
则能让我们更轻松地编写组件的样式。此外,要用到我们正在开发的 linear-algebra 库,需要在 package.json 中进行如下引用:
"dependencies": { "linear-algebra": "file:../library", ... } 复制代码
项目结构
本系列为每个示例都在 views
目录中创建了各自的组件。我们在 index.js 中导出一个以示例名称为键、以对应组件为值的对象。
import { default as VectorLength } from './vector-length' import { default as VectorScale } from './vector-scale' import { default as VectorsAddition } from './vectors-addition' import { default as VectorsSubtraction } from './vectors-subtraction' import { default as VectorsDotProduct } from './vectors-dot-product' export default { 'vectors: addition': VectorsAddition, 'vectors: subtraction': VectorsSubtraction, 'vectors: length': VectorLength, 'vectors: scale': VectorScale, 'vectors: dot product': VectorsDotProduct } 复制代码
接着在 Main
组件中导入该对象,并在菜单中展示出所有的键。当用户通过菜单选择示例后,更新组件状态,并渲染新的 view
。
import React from 'react' import styled from 'styled-components' import views from './views' import MenuItem from './menu-item' const Container = styled.div` ... ` const Menu = styled.div` ... ` class Main extends React.Component { constructor(props) { super(props) this.state = { view: Object.keys(views)[0] } } render() { const { view } = this.state const View = views[view] const viewsNames = Object.keys(views) const MenuItems = () => viewsNames.map(name => ( <MenuItem key={name} selected={name === view} text={name} onClick={() => this.setState({ view: name })} /> )) return ( <Container> <View /> <Menu> <MenuItems /> </Menu> </Container> ) } } export default Main 复制代码
网格组件
为了在之后的示例中渲染向量和其它内容,我们设计了一个功能强大的组件,这个组件需要有这么一种投影功能:将我们熟知的直角坐标系(原点在中间,y 轴正向朝上)投影到 SVG 坐标系(原点在左上角,y 轴正向朝下)中。
this.props.updateProject(vector => { // 在 vector 类中没有任何用于缩放的方法,因此在这里进行计算: const scaled = vector.scaleBy(step) const withNegatedY = new Vector( scaled.components[0], -scaled.components[1] ) const middle = getSide(size) / 2 return withNegatedY.add(new Vector(middle, middle)) }) 复制代码
为了捕获到网格组件容器的大小变动,我们使用 react-size 库提供的函数将这个组件包装起来:
... import { withSize } from 'react-sizeme' ... class Grid extends React.Component { updateProject = (size, cells) => { const step = getStepLen(size, cells) this.props.updateProject(() => /...) } componentWillReceiveProps({ size, cells }) { if (this.props.updateProject) { const newStepLen = getStepLen(size, cells) const oldStepLen = getStepLen(this.props.size, cells) if (newStepLen !== oldStepLen) { this.updateProject(size, cells) } } } componentDidMount() { if (this.props.updateProject) { this.updateProject(this.props.size, this.props.cells) } } } export default withSize({ monitorHeight: true })(Grid) 复制代码
为了便于在不同的示例中使用这个网格组件,我们编写了一个 GridExample 组件,它可以接收两个参数:一个用于渲染信息(例如向量的名称)的函数 renderInformation
,以及一个用于在网格上呈现内容(如后面的箭头组件)的函数 renderGridContent
。
... import Grid from './grid' ... class Main extends React.Component { constructor(props) { super(props) this.state = { project: undefined } } render() { const { project } = this.state const { renderInformation, renderGridContent } = this.props const Content = () => { if (project && renderGridContent) { return renderGridContent({ project }) } return null } const Information = () => { if (renderInformation) { return renderInformation() } return null } return ( <Container> <Grid cells={10} updateProject={project => this.setState({ project })}> <Content /> </Grid> <InfoContainer> <Information /> </InfoContainer> </Container> ) } } export default Main 复制代码
这样就能在 view 中使用这个组件了。下面以向量的加法为例测试一下:
import React from 'react' import { withTheme } from 'styled-components' import { Vector } from 'linear-algebra/vector' import GridExample from '../grid-example' import Arrow from '../arrow' import VectorView from '../vector' const VectorsAddition = ({ theme }) => { const one = new Vector(0, 5) const other = new Vector(6, 2) const oneName = 'v⃗' const otherName = 'w⃗' const oneColor = theme.color.green const otherColor = theme.color.red const sum = one.add(other) const sumColor = theme.color.blue const sumText = `${oneName} + ${otherName}` const renderInformation = () => ( <> <VectorView components={one.components} name={oneName} color={oneColor} /> <VectorView components={other.components} name={otherName} color={otherColor} /> <VectorView components={sum.components} name={sumText} color={sumColor} /> </> ) const renderGridContent = ({ project }) => ( <> <Arrow project={project} vector={one} text={oneName} color={oneColor} /> <Arrow project={project} vector={other} text={otherName} color={otherColor} /> <Arrow project={project} vector={sum} text={sumText} color={sumColor} /> </> ) const props = { renderInformation, renderGridContent } return <GridExample {...props} /> } export default withTheme(VectorsAddition) 复制代码
箭头组件
箭头组件由 3 个 SVG 元素组成: line 用于显示箭头的线、 polygon 用于显示箭头的头、 text 用于显示向量名称。此外,我们需要接收 project 函数,用于将箭头放在网格中正确的位置上。
import React from 'react' import styled from 'styled-components' import { Vector } from 'linear-algebra/vector' const Arrow = styled.line` stroke-width: 2px; stroke: ${p => p.color}; ` const Head = styled.polygon` fill: ${p => p.color}; ` const Text = styled.text` font-size: 24px; fill: ${p => p.color}; ` export default ({ vector, text, color, project }) => { const direction = vector.normalize() const headStart = direction.scaleBy(vector.length() - 0.6) const headSide = new Vector( direction.components[1], -direction.components[0] ).scaleBy(0.2) const headPoints = [ headStart.add(headSide), headStart.subtract(headSide), vector ] .map(project) .map(v => v.components) const projectedStart = project(new Vector(0, 0)) const projectedEnd = project(vector) const PositionedText = () => { if (!text) return null const { components } = project(vector.withLength(vector.length() + 0.2)) return ( <Text color={color} x={components[0]} y={components[1]}> {text} </Text> ) } return ( <g> <Arrow color={color} x1={projectedStart.components[0]} y1={projectedStart.components[1]} x2={projectedEnd.components[0]} y2={projectedEnd.components[1]} /> <Head color={color} points={headPoints} /> <PositionedText /> </g> ) } 复制代码
通过结合 React 与 SVG 可以做更多有意思的事。在本系列的后面章节中,我们会给这个可视化示例添加更多的功能。最后推荐另一篇类似的文章: 使用 React 与 SVG 制作复杂的条形图 。
如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为掘金 上的英文分享文章。内容覆盖 Android 、 iOS 、 前端 、 后端 、 区块链 、 产品 、 设计 、 人工智能 等领域,想要查看更多优质译文请持续关注 掘金翻译计划 、官方微博、 知乎专栏 。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 强化学习的线性代数
- [译] JavaScript 线性代数:向量
- [译] JavaScript 线性代数:使用 ThreeJS 制作线性变换动画
- 线性代数 Cheat Sheet 7-4:奇异值分解
- 深度学习必备数学知识之线性代数篇(附代码实现)
- 应用Python的SymPy库解决高等数学及线性代数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
BSD Hacks
Dru Lavigne / O'Reilly Media, Inc. / 2004-05-24 / USD 24.95
If you want more than your average BSD user--you want to explore and experiment, unearth shortcuts, create useful tools, and come up with fun things to try on your own--BSD Hacks is a must-have. This ......一起来看看 《BSD Hacks》 这本书的介绍吧!
XML 在线格式化
在线 XML 格式化压缩工具
Markdown 在线编辑器
Markdown 在线编辑器