clubmate.fi

A good[ish] website

Web development blog, loads of UI and JavaScript topics

React data visualizations | No dependencies, just some CSS

Filed under: JavaScript— Tagged with: css, react

How to make simple data plotting with pure CSS. The point I had here was not to mathematically verify anything, but just to map out the curve and visually inspect it for any anomalies.

In a previous post about getting a random item from an array I used a helper that churns out random integers. And I got to thinking, how random it actually is? Is there a way for me to visualize it?

The graph

There is a way to visualize it, and it’s this random:

That’s simply 1000 flexboxed divs, not using any graph libs. The divs have a height of: occurrence count * 10 pixels.

The random number generators

The MDN article on the random method has an example piece of code to get random ints in a range:

const getRandomIntInclusive = (min, max) => {
  min = Math.ceil(min)
  max = Math.floor(max)

  return Math.floor(Math.random() * (max - min + 1) + min)
}

And it says:

It might be tempting to use Math.round() to accomplish that, but doing so would cause your random numbers to follow a non-uniform distribution, which may not be acceptable for your needs.

So I made a random number generator that uses the Math.round(), you can choose it from the dropdown on the above interactive graph. The distribution of the values is not shockingly different (at least on this magnitude), the visual difference isn’t striking.

const getRandomIntRound = (min, max) => {
  min = Math.round(min)
  max = Math.round(max)

  return Math.round(Math.random() * (max - min + 1) + min)
}

Build the random data

The random number functions used here are listed above. But if you don’t need the min value, it can be made a bit simpler looking:

const getRandomInt = max => Math.floor(Math.random() * max)

I’m making 10 000 random numbers with a range from 0 to 1000, that should be enough. Create an array and fill it with randomness:

const randomNumbers = Array.from({ length: 10000 }, () => getRandomInt(1000))

Then let’s find out how many times any given random number appears in the array. The occurrences can be dug out with a simple reduce:

const getOccurrences = array =>
  array.reduce((acc, randomNumber) => {
    acc[randomNumber] = ++acc[randomNumber] || 1
    return acc
  }, {})

That returns a object like this one below (truncated), where key is the random number, and the value is how many times it occurs in the array:

{
  "0": 8,
  "1": 13,
  "2": 12,
  "3": 9,
  "4": 7,
  "5": 9,
  "6": 9,
  "7": 11,
  "8": 13,
  "9": 11,
  "10": 14,
  "11": 11,
  "12": 10,
  "13": 7,
  "14": 9,
  "15": 3,
  "16": 12,
  "17": 9,
  ...
}

From it, you can already see that the distribution looks pretty random.

Make the sorting UI

Then we need a helper function to sort the data, let’s use getOccurrences() helper here, and remember the generate the random data inside the function, or the refresh won’t actually get fresh data:

const getRandomData = (isSorted, randomMethod) => {
  // Generate these inside this function so the refresh works.
  const randomNumbers = Array.from({ length: 10000 }, () =>
    randomMethod === 'round'
      ? getRandomIntRound(0, 1000)
      : getRandomIntInclusive(0, 1000)
  )
  const occurrences = getOccurrences(randomNumbers)

  const arr = Object.keys(occurrences).map(x => occurrences[x])

  return isSorted
    ? arr.sort((a, b) => {
        if (a < b) return -1
        if (a > b) return 1
        return 0
      })
    : arr
}

Render the graph

I’m using styled-components here, but you-do-you.

The refreshing is kind of interesting: React re-renders a component if you update its state, but in this case there’s not really a change to be made, so I’m updating a state that uses itself. Also, the random data needs to be generated inside the getRandomData helper.

The other click listeners is for the sort and the select:

const Bar = styled.div(props => ({
  alignItems: 'flex-end',
  backgroundColor: 'Gray',
  height: props.elementHeight * 10 + 'px',
  width: '0.1%'
}))

const Wrap = styled.div({
  border: '1px solid var(--gray)',
  marginBottom: '20px',
  padding: '10px'
})

const Container = styled.div({
  alignItems: 'flex-end',
  display: 'flex',
  height: '240px',
  marginBottom: '10px',
  width: '100%'
})

const Button = styled.button({
  marginRight: '10px'
})

const Chart = ({ shouldSort }) => {
  const [isSorted, setIsSorted] = React.useState(shouldSort)
  const [remountCount, setRemountCount] = React.useState(0)
  const [randomMethod, setRandomMethod] = React.useState('normal')  const refresh = () => setRemountCount(remountCount + 1)
  const handleSort = () => {
    setIsSorted(prev => !prev)
  }

  const handleChange = event => {
    setRandomMethod(event.target.value)
  }

  return (
    <Wrap>
      <Container>
        {getRandomData(isSorted, randomMethod).map((key, index) => (
          <Bar key={index} elementHeight={key} />
        ))}
      </Container>
      <Button onClick={refresh}>Refresh</Button>      <Button onClick={handleSort}>{isSorted ? 'Unsort' : 'Sort'}</Button>      <select onChange={handleChange}>
        <option value="ceil-floor">With ceil/floor</option>
        <option value="round">With round</option>
      </select>
    </Wrap>
  )
}

That's it pretty much.

Conclusions

Could totally render the graph onto a canvas, would be faster and better.

The results here weren’t that interesting, the Math.round() randomness doesn’t look that different (or maybe I’m just mangling the data in a wrong way..). This was a bit pointless, but kind of fun.

Comments would go here, but the commenting system isn’t ready yet, sorry. Tweet me @hiljaa if you want to make a correction etc.

  • © 2021 Antti Hiljá
  • About
  • Follow me in Twatter → @hiljaa
  • All rights reserved yadda yadda.
  • I can put just about anything here, no one reads the footer anyways.
  • console.log('Smash the patriarchy!')
  • I love u!