• React for Linear Algebra Examples: Grid and Arrows
  • Rodion Chachura
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: lsvih
  • Proofread by: Baddyo, ZavierTang

This article is part of the “JavaScript Linear Algebra” tutorial.

I recently wrote the first book in this series on linear algebra. Before I started writing this new article, I had an idea: It would be fun to use React to develop a project that would provide visualizations for all the examples in this series! All code for this series is stored in this GitHub repository, where the submission record for this article is located.

The target

When this series began, only one chapter dealt with the basic operations of vectors. So, for now, implementing a component that can render a two-dimensional coordinate grid and visualize vectors as arrows is enough. The final result of this article is shown below, which you can also experience here.

Create the React project

There are already best practice guidelines for creating React projects, but in this article, we’ll minimize the number of libraries we rely on and simplify the configuration of the project.

create-react-app linear-algebra-demo
cd linear-algebra-demo
npm install --save react-sizeme styled-components
Copy the code

The script above installs two libraries. The first library, react-Sizeme, allows you to rerender grid components when the form size changes. The second library, styled- Components, makes it easier to style components. In addition, to use the Linear-Algebra library we are developing, we need to make the following reference in package.json:

"dependencies": {
    "linear-algebra": "file:.. /library". }Copy the code

The project structure

This series has created a separate component in the Views directory for each example. In index.js, we export an object with the example name as the key and the corresponding component as the value.

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
}
Copy the code

Then import the object in the Main component and display all the keys in the menu. When the user selects the sample from the menu, the component state is updated and the new View is rendered.

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 = (a)= >
      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
Copy the code

Grid component

To render vectors and other content in the following examples, we designed a powerful component that needs to have the ability to project the familiar rectangular coordinate system (origin in the middle, y-up) to an SVG coordinate system (origin in the upper left corner, y-down).

this.props.updateProject(vector= > {
  // There are no methods for scaling in the vector class, so we do the calculation here:
  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))
})
Copy the code

To capture the size changes of the grid component container, we wrap the component using functions provided by the React-Size library:

. import { withSize }from 'react-sizeme'. class Grid extends React.Component { updateProject =(size, cells) = > {
    const step = getStepLen(size, cells)
    this.props.updateProject((a)= >/...). } 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)
Copy the code

To facilitate the use of this grid component in different examples, we wrote a GridExample component that accepts two parameters: One function, renderInformation, is used to renderInformation, such as the names of vectors, and one function, renderGridContent, is used to render content on the grid, such as the arrow component below.

. import Gridfrom './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 = (a)= > {
      if (project && renderGridContent) {
        return renderGridContent({ project })
      }
      return null
    }
    const Information = (a)= > {
      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
Copy the code

This allows you to use this component in your View. Let’s take vector addition as an example:

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 = (a)= >( <> <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)Copy the code

Arrow components

The arrow component consists of three SVG elements: line for displaying the arrow line, Polygon for displaying the arrow head, and Text for displaying the vector name. In addition, we need to receive the project function to place the arrows in the correct position in the grid.

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 = (a)= > {
    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>
  )
}
Copy the code

You can do more interesting things with React and SVG. We will add more functionality to this visual example in later sections of this series. Another similar article: Making complex bar charts with React and SVG.

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.