npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@diamovi/react-native-word-cloud

v0.1.0

Published

A WordCloud component for React Native, rendered with react-native-svg.

Readme

@diamovi/react-native-word-cloud

A word cloud component for React Native. Pure JavaScript spiral layout, rendered with react-native-svg. Works with Expo (including Expo Go) and bare React Native, on iOS and Android.

  • Deterministic layouts (seeded random, or your own random)
  • Weight to font size mapping: linear, sqrt or log
  • Rotation ranges with discrete steps, or no rotation at all
  • Colors by palette, by category field, or by function
  • Press and long-press handlers with the original data row
  • Layout engine exported separately for tests and custom renderers
  • No native code of its own: only react-native-svg is required

Installation

bun add @diamovi/react-native-word-cloud react-native-svg
# or
npm install @diamovi/react-native-word-cloud react-native-svg
# or
yarn add @diamovi/react-native-word-cloud react-native-svg

react-native-svg is a peer dependency.

  • Expo: npx expo install react-native-svg. It is included in Expo Go.
  • Bare React Native: run cd ios && pod install after installing it.

Quick start

import { WordCloud } from '@diamovi/react-native-word-cloud'

const data = [
  { text: 'React Native', value: 120 },
  { text: 'TypeScript', value: 95 },
  { text: 'JavaScript', value: 90 },
  { text: 'Expo', value: 80 },
  { text: 'SVG', value: 70 },
  { text: 'Hooks', value: 65 },
]

export function Example() {
  return <WordCloud data={data} height={260} />
}

data is any array of objects. By default the word is read from text and the weight from value; change that with wordField and weightField. When width or height are omitted the component measures its container with onLayout, so it can be given a style with a height or placed inside a flex parent.

Examples

All screenshots below come from the app in example/.

Default

Built-in palette, sizes from 12 to 60 px, half of the words rotated 90°.

<WordCloud
  data={data}
  height={260}
  seed={1}
  onWordPress={(word) => console.log(word.text, word.value)}
/>

Change seed to get a different arrangement of the same data. The same seed and data always produce the same layout.

Color by category

Each distinct value of colorField gets the next color of the palette. Rotation is turned off and the font is bold.

const data = [
  { text: 'TypeScript', value: 95, category: 'language' },
  { text: 'Expo', value: 80, category: 'framework' },
  { text: 'SVG', value: 70, category: 'graphics' },
  { text: 'Metro', value: 55, category: 'tooling' },
  // ...
]

<WordCloud
  data={data}
  height={240}
  colorField="category"
  color={['#1F77B4', '#FF7F0E', '#2CA02C', '#D62728', '#9467BD']}
  wordStyle={{ rotation: 0, fontWeight: 'bold', padding: 2 }}
/>

Report style: custom fields, one color per word, condensed font

Rows shaped as { word, count }, a fixed random so the layout never changes between renders, and a condensed display font. Impact ships with iOS; on Android the example bundles Anton (SIL OFL) as a look-alike.

import { Platform } from 'react-native'

const words = [
  { word: 'coffee', count: 143 },
  { word: 'morning', count: 98 },
  { word: 'espresso', count: 87 },
  // ...
]

<WordCloud
  data={words}
  height={300}
  wordField="word"
  weightField="count"
  colorField="word"
  color={['#07c0cb', '#1e92c4', '#6aa72a', '#8945a3', '#c04891', '#e96d3c', '#eebc01', '#e5484d']}
  wordStyle={{
    rotation: 0,
    fontFamily: Platform.select({ ios: 'Impact', default: 'Anton' }),
    fontSize: [12, 60],
  }}
  random={() => 0.5}
  onWordPress={(word) => console.log(word.item)} // { word: 'coffee', count: 143 }
/>

Emojis only

Any string works as a word, including emoji. Emoji sequences (skin tones, flags, ZWJ combinations) are measured as a single square glyph.

const emojis = [
  { text: '🔥', value: 120 },
  { text: '❤️', value: 110 },
  { text: '😂', value: 95 },
  { text: '🚀', value: 80 },
  // ...
]

<WordCloud
  data={emojis}
  height={240}
  seed={3}
  wordStyle={{ rotation: 0, fontSize: [18, 64], padding: 3 }}
/>

Free rotation, rectangular spiral, log scale

Five rotation steps between -60° and 60°, a rectangular spiral instead of the archimedean one, a logarithmic size scale that flattens big differences in weight, and a color function.

<WordCloud
  data={data}
  height={240}
  spiral="rectangular"
  scale="log"
  wordStyle={{ rotation: [-60, 60], rotationSteps: 5, fontSize: [10, 44] }}
  color={(item) => (item.value > 50 ? '#0F172A' : '#64748B')}
/>

Filling the parent

Omit width and height and let the container decide.

<View style={{ flex: 1 }}>
  <WordCloud data={data} style={{ flex: 1 }} />
</View>

Custom word renderer

renderWord receives the placed word and must return react-native-svg elements. x and y are the center of the word and dy is the baseline offset that keeps the glyphs vertically centered.

import { Text as SvgText } from 'react-native-svg'

<WordCloud
  data={data}
  height={260}
  renderWord={(word) => (
    <SvgText
      x={0}
      y={0}
      dy={word.dy}
      fontSize={word.size}
      fill={word.color}
      textAnchor="middle"
      opacity={0.85}
      transform={`translate(${word.x}, ${word.y}) rotate(${word.rotate})`}
    >
      {word.text.toUpperCase()}
    </SvgText>
  )}
/>

Reacting to the computed layout

<WordCloud
  data={data}
  height={260}
  onLayoutComplete={(words) => {
    const dropped = data.length - words.length
    if (dropped > 0) console.log(`${dropped} words did not fit`)
  }}
/>

Props

| Prop | Type | Default | Description | | --- | --- | --- | --- | | data | Record<string, any>[] | required | Data rows. | | wordField | string | 'text' | Field holding the word. | | weightField | string | 'value' | Field holding the numeric weight. | | colorField | string | – | Field used to pick a palette color per category. | | color | string \| string[] \| (item, index) => string | built-in palette | Single color, palette, or resolver function. | | width / height | number | measured | Canvas size in px. | | wordStyle | WordStyle | see below | Font and rotation options. | | spiral | 'archimedean' \| 'rectangular' | 'archimedean' | Placement spiral. | | scale | 'linear' \| 'sqrt' \| 'log' | 'sqrt' | Weight to font size mapping. | | seed | number | 42 | Seed for the built-in deterministic PRNG. | | random | () => number | – | Custom random generator in [0, 1). Overrides seed. | | measureText | (text, fontSize, fontFamily?, fontWeight?) => number | built-in estimator | Custom text width measurement. | | dropOverflow | boolean | true | Drop words that do not fit instead of forcing them at the edge. | | backgroundColor | string | – | Fill behind the words. | | style | ViewStyle | – | Container style. | | onWordPress / onWordLongPress | (word: Word, index: number) => void | – | Press handlers. | | onLayoutComplete | (words: Word[]) => void | – | Called after every layout computation. | | renderWord | (word: Word, index: number) => ReactElement \| null | – | Custom renderer (must return react-native-svg elements). | | testID | string | – | Forwarded to the container view. |

wordStyle

| Option | Type | Default | Description | | --- | --- | --- | --- | | fontFamily | string | system | Any font available to the app. | | fontWeight | 'normal' \| 'bold' \| '100'…'900' | 'normal' | | | fontSize | number \| [min, max] | [12, 60] | Fixed size or range mapped from the weight. | | rotation | number \| [min, max] | [0, 90] | Degrees. A single number disables random rotation. | | rotationSteps | number | 2 | Discrete angles inside the rotation range. | | rotateRatio | number | 0.5 | Share of words (0–1) that get rotated at all. | | padding | number | 1 | Space in px around each word. |

Word

Every placed word carries:

| Field | Description | | --- | --- | | item | The original row from data. | | text, value | Resolved word and weight. | | x, y | Center of the word, relative to the canvas. | | dy | Baseline offset from y that vertically centers the glyphs. | | size | Font size in px. | | rotate | Rotation in degrees. | | color | Resolved color. | | width, height | Estimated glyph box before rotation. | | fontFamily, fontWeight | Copied from wordStyle. |

Layout without rendering

The layout engine is exported on its own. It is handy for tests, custom renderers, or computing a layout ahead of time.

import { layoutWordCloud, createRandom } from '@diamovi/react-native-word-cloud'

const words = layoutWordCloud({
  width: 400,
  height: 300,
  data,
  wordField: 'text',
  weightField: 'value',
  random: createRandom(7),
})
// words[0] -> { text: 'React Native', x: 200.8, y: 150.4, size: 60, rotate: 0, color: '#5B8FF9', ... }

How the layout works

  1. Words are sorted by weight, largest first.
  2. Each weight is mapped to a font size with the chosen scale.
  3. Starting from the center, each word walks along the spiral until it finds a spot that does not overlap an already placed word and stays inside the canvas.
  4. A rotated word that does not fit is retried upright. If it still does not fit, it is dropped (or pinned to the edge with dropOverflow={false}).

Collision is done with the rotated bounding box of each word's glyphs, which keeps the layout fast enough to run on every render.

Text measurement and fonts

React Native has no synchronous text measurement API, so word widths are estimated from a per-character width table (Latin, accented Latin, CJK and emoji). Heights use the real glyph extent of each word, so lines pack tightly. Estimates are usually within a few percent of the rendered glyphs.

  • If a custom font renders wider than expected, increase wordStyle.padding.
  • For exact results, pass your own measureText.
  • Load custom fonts the same way you would for any Text: with expo-font or by linking them in the native projects. The example app shows both useFonts (works in Expo Go) and the expo-font config plugin.

Development

This repo uses bun. The example/ folder is an Expo app that bundles the library source directly (see example/metro.config.js), so edits under src/ reload live.

bun install          # installs the library and the example (bun workspaces)
bun run typecheck
bun test
bun run build        # emits lib/ (CommonJS + .d.ts)

bun run start        # Metro for the example (dev client)
cd example && bun run start:go   # Expo Go instead of a dev build
cd example && npx expo prebuild --platform ios --clean && bun run ios

Releasing

CI runs typecheck, tests and build on every push to main and on pull requests. Publishing is done by the Publish to npm workflow, either by pushing a version tag or by running it manually with a version input:

git tag v0.1.0
git push origin v0.1.0

The workflow sets the package version from the tag, publishes to npm and creates a GitHub release with generated notes. It needs an NPM_TOKEN repository secret with publish rights on the @diamovi scope.

License

MIT