├── .babelrc ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .ignore ├── .importjs.js ├── .prettierrc ├── LICENSE ├── README.md ├── __tests__ ├── .eslintrc ├── TagCloud-test.js ├── __snapshots__ │ ├── TagCloud-test.js.snap │ └── defaultRenderer-test.js.snap ├── defaultRenderer-test.js ├── helpers-test.js └── utils.js ├── demo-min.png ├── examples ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── public │ ├── index.html │ └── robots.txt ├── src │ ├── App.js │ ├── analytics.js │ ├── custom-color-options.js │ ├── custom-renderer.js │ ├── custom-styles.js │ ├── index.js │ ├── interactive-cloud.js │ ├── logo.svg │ ├── shuffle-with-seed.js │ ├── simple-cloud.js │ ├── styles │ │ └── index.css │ └── tag-props.js └── yarn.lock ├── lib ├── TagCloud.js ├── TagCloudOld.js ├── default-renderer.js ├── defaultRenderer.js ├── helpers.js ├── index.js └── tag-cloud.js ├── package.json ├── rn ├── defaultRenderer.js └── index.js ├── src ├── TagCloud.js ├── defaultRenderer.js ├── helpers.js └── index.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | coverage/* 3 | dist/* 4 | lib/* 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "quotes": ["error", "single", { "avoidEscape": true }], 6 | "comma-dangle": ["error", "always-multiline"], 7 | "semi": ["error", "never"], 8 | "prettier/prettier": "warn", 9 | "react-hooks/exhaustive-deps": "off", 10 | "jsx-a11y/anchor-is-valid": "off" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | name: Tests 3 | jobs: 4 | lint-and-test: 5 | name: Test 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: actions/setup-node@v2 10 | with: 11 | node-version: '16' 12 | - run: yarn install 13 | - run: yarn lint 14 | - run: yarn test 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.ignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | lib/ 3 | -------------------------------------------------------------------------------- /.importjs.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | importStatementFormatter({ importStatement }) { 3 | return importStatement.replace(/;$/, '') 4 | }, 5 | environments: ['browser'], 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 madox2 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-tagcloud 2 | 3 | ![](https://github.com/madox2/react-tagcloud/workflows/Tests/badge.svg) 4 | 5 | Simple and extensible tag/word cloud React component. 6 | 7 | See the [demo](https://madox2.github.io/react-tagcloud/). 8 | 9 | ![preview tag-cloud preview](./demo-min.png) 10 | 11 | ## Installation 12 | 13 | ``` 14 | npm install react-tagcloud 15 | 16 | # or with yarn 17 | yarn add react-tagcloud 18 | 19 | # react < 16.8.0 20 | npm install react-tagcloud@1.4 21 | ``` 22 | 23 | ## Basic usage 24 | 25 | ```javascript 26 | import { TagCloud } from 'react-tagcloud' 27 | 28 | const data = [ 29 | { value: 'JavaScript', count: 38 }, 30 | { value: 'React', count: 30 }, 31 | { value: 'Nodejs', count: 28 }, 32 | { value: 'Express.js', count: 25 }, 33 | { value: 'HTML5', count: 33 }, 34 | { value: 'MongoDB', count: 18 }, 35 | { value: 'CSS3', count: 20 }, 36 | ] 37 | 38 | const SimpleCloud = () => ( 39 | alert(`'${tag.value}' was selected!`)} 44 | /> 45 | ) 46 | ``` 47 | 48 | ### React Native 49 | 50 | In react native projects import tag cloud from the package relative path `react-tagcloud/rn`. 51 | 52 | ```javascript 53 | import React from 'react'; 54 | 55 | import { Alert } from 'react-native'; 56 | import { TagCloud } from 'react-tagcloud/rn' 57 | 58 | const data = [ 59 | // ... 60 | ] 61 | 62 | const SimpleCloud = () => ( 63 | Alert.alert(`'${tag.value}' was selected!`)} 68 | /> 69 | ) 70 | ``` 71 | 72 | ## API 73 | 74 | ### Options 75 | 76 | `` component has props listed below: 77 | 78 | | Option | Type | Required | Note | 79 | |-----------|----------|--------|---| 80 | |`tags` |`Array` |`true`|Array of objects that represent tags (see [Tag object](#tag-object))| 81 | |`maxSize` |`Number` |`true` |Maximal font size (in px) used in cloud| 82 | |`minSize` |`Number` |`true` |Minimal font size (in px) used in cloud| 83 | |`shuffle` |`Boolean` |`false`|If true, tags are shuffled. When `tags` are modified, cloud is re-shuffled. Default: `true`| 84 | |`colorOptions` |`Object` |`false`|Random color options (see [randomColor#options](https://github.com/davidmerfield/randomColor#options))| 85 | |`disableRandomColor` |`Boolean` |`false`|If `true`, random color is not used| 86 | |`randomSeed` |`Number` |`false`|Random seed used to shuffle tags and generate color| 87 | |`renderer` |`Function`|`false`|Function used to render tag| 88 | |`randomNumberGenerator`|`Function`|`false`|DEPRECATED, use `randomSeed` instead. Specifies a custom random number generator that is being used by shuffle algorithm. Default: `Math.random`| 89 | 90 | *Note:* Furthermore you can pass any other prop and it will be forwarded to the wrapping `
` component (e.g. `style`, `className`). 91 | 92 | ### Tag object 93 | 94 | Each tag is represented by object literal having following properties: 95 | 96 | | Property | Type | Required | Note | 97 | |----------|------|----------|------| 98 | |`value`|`String`|`true` |String value to be displayed| 99 | |`count`|`Number`|`true` |Represents frequency of the tag that is used to calculate tag size| 100 | |`key` |`String`|`false`|Tag element key. If it is not provided, `value` property will be used instead (however it can fail if you don't have unique tag values. It is highly recommeded to use `key` property)| 101 | |`color`|`String`|`false`|Represents color of the tag. If it is not provided, random color will be used instead| 102 | |`props`|`Object`|`false`|Props to be passed to a particular tag component| 103 | 104 | ### Events 105 | 106 | Event handlers can be passed to the `` props. 107 | Each handler has two arguments: the first is related tag object and the second is DOM event object. 108 | 109 | Currently supported events: `onClick`, `onDoubleClick`, `onMouseMove` 110 | 111 | *Note:* Feel free to open issue if any other event is needed. 112 | 113 | ### Styles 114 | 115 | Default class names are `tag-cloud` for the wrapping container, and `tag-cloud-tag` for a particular tag. 116 | Styles passed to `` props will be applied to the wrapping container. 117 | 118 | ### Renderer 119 | 120 | Rendering of tags can be fully customized by providing custom render function and passing it to the `renderer` prop. 121 | By default is used [defaultRenderer](https://github.com/madox2/react-tagcloud/blob/master/src/defaultRenderer.js). 122 | Render function has three arguments - `tag`, `size` and `color`. 123 | For example: 124 | 125 | ```javascript 126 | import { TagCloud } from 'react-tagcloud' 127 | 128 | const customRenderer = (tag, size, color) => { 129 | return ( 130 | 131 | {tag.value} 132 | 133 | ) 134 | } 135 | 136 | const CustomizedCloud = () => ( 137 | 138 | ) 139 | ``` 140 | 141 | ## More examples 142 | 143 | * [Simple tag cloud](https://github.com/madox2/react-tagcloud/blob/master/examples/src/simple-cloud.js) 144 | * [Custom color options](https://github.com/madox2/react-tagcloud/blob/master/examples/src/custom-color-options.js) 145 | * [Custom styles](https://github.com/madox2/react-tagcloud/blob/master/examples/src/custom-styles.js) 146 | * [Custom tag props](https://github.com/madox2/react-tagcloud/blob/master/examples/src/tag-props.js) 147 | * [Custom renderer](https://github.com/madox2/react-tagcloud/blob/master/examples/src/custom-renderer.js) 148 | 149 | ## Testing 150 | 151 | Install dev modules: 152 | 153 | ``` 154 | yarn install 155 | ``` 156 | 157 | ### Run unit tests 158 | 159 | ``` 160 | yarn test 161 | ``` 162 | 163 | ### Run examples 164 | 165 | ``` 166 | cd examples 167 | yarn install 168 | yarn start 169 | ``` 170 | 171 | and open browser at `http://localhost:3000` 172 | 173 | ## License 174 | 175 | [MIT License](https://github.com/madox2/react-tagcloud/blob/master/LICENSE) 176 | -------------------------------------------------------------------------------- /__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "jest": true, 4 | "it": true, 5 | "describe": true, 6 | "expect": true, 7 | "jasmine": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /__tests__/TagCloud-test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { create, act } from 'react-test-renderer' 4 | 5 | import { TagCloud } from '../src/TagCloud' 6 | import { expectToMatchSnapshot, render } from './utils' 7 | 8 | describe('TagCloud', () => { 9 | const data = [ 10 | { value: 'tag1', count: 25 }, 11 | { value: 'tag2', count: 15 }, 12 | { value: 'tag3', count: 20 }, 13 | { value: 'tag4', count: 3 }, 14 | ] 15 | 16 | it('should render simple cloud', () => { 17 | let cloud 18 | act(() => { 19 | cloud = create() 20 | }) 21 | const tags = cloud.root.findAllByProps({ className: 'tag-cloud-tag' }) 22 | expect(tags.length).toBe(data.length) 23 | }) 24 | 25 | it('should render tags with default renderer', () => { 26 | expectToMatchSnapshot( 27 | , 28 | ) 29 | }) 30 | 31 | it('should render not shuffled tags', () => { 32 | expectToMatchSnapshot( 33 | , 40 | ) 41 | }) 42 | 43 | it('should use custom renderer', () => { 44 | const customRenderer = (tag, size) => { 45 | return ( 46 | {`${tag.value}-${tag.count}`} 51 | ) 52 | } 53 | expectToMatchSnapshot( 54 | , 61 | ) 62 | }) 63 | 64 | it('should trigger onClick event', () => { 65 | const onClickSpy = jest.fn() 66 | let cloud 67 | act(() => { 68 | cloud = create( 69 | , 76 | ) 77 | }) 78 | const tag3 = cloud.root.findAllByProps({ className: 'tag-cloud-tag' })[2] 79 | expect(tag3).not.toBeUndefined() 80 | const clickEvent = new Event('click') 81 | tag3.props.onClick(clickEvent) 82 | expect(onClickSpy).toHaveBeenCalled() 83 | expect(onClickSpy).toHaveBeenCalledWith( 84 | { 85 | value: 'tag3', 86 | count: 20, 87 | }, 88 | clickEvent, 89 | ) 90 | }) 91 | 92 | it('should not re-shuffle tags', () => { 93 | const rng = jest.fn(() => 0.5) 94 | let cloud 95 | act(() => { 96 | cloud = create( 97 | , 103 | ) 104 | }) 105 | expect(rng).toHaveBeenCalled() 106 | rng.mockClear() 107 | act(() => { 108 | cloud.update( 109 | , 115 | ) 116 | }) 117 | expect(rng).not.toHaveBeenCalled() 118 | rng.mockClear() 119 | }) 120 | 121 | it('should re-shuffle tags when tags changes', () => { 122 | const rng = jest.fn(() => 0.5) 123 | let cloud 124 | act(() => { 125 | cloud = create( 126 | , 132 | ) 133 | }) 134 | expect(rng).toHaveBeenCalled() 135 | rng.mockClear() 136 | 137 | act(() => { 138 | cloud.update( 139 | , 145 | ) 146 | }) 147 | expect(rng).toHaveBeenCalled() 148 | rng.mockClear() 149 | 150 | act(() => { 151 | cloud.update( 152 | , 158 | ) 159 | }) 160 | expect(rng).toHaveBeenCalled() 161 | rng.mockClear() 162 | 163 | act(() => { 164 | cloud.update( 165 | , 171 | ) 172 | }) 173 | expect(rng).toHaveBeenCalled() 174 | rng.mockClear() 175 | }) 176 | 177 | it('should shuffle with seed', () => { 178 | const renderer = (tag, size, color) => ( 179 | 180 | {`${tag.value}:${color}`} 181 | 182 | ) 183 | let cloud 184 | act(() => { 185 | cloud = create( 186 | , 193 | ) 194 | }) 195 | 196 | const tagsSeed42 = cloud.root 197 | .findAllByProps({ className: 'tag-cloud-tag' }) 198 | .map((i) => i.props.children) 199 | 200 | act(() => { 201 | cloud.update( 202 | , 209 | ) 210 | }) 211 | 212 | const tagsSeed43 = cloud.root 213 | .findAllByProps({ className: 'tag-cloud-tag' }) 214 | .map((i) => i.props.children) 215 | 216 | expect(tagsSeed42).not.toEqual(tagsSeed43) 217 | 218 | act(() => { 219 | cloud.update( 220 | , 227 | ) 228 | }) 229 | 230 | const tagsSeed42updated = cloud.root 231 | .findAllByProps({ className: 'tag-cloud-tag' }) 232 | .map((i) => i.props.children) 233 | 234 | expect(tagsSeed42).toEqual(tagsSeed42updated) 235 | }) 236 | 237 | it('should use custom rng', () => { 238 | const rng = jest.fn(() => 0.5) 239 | render( 240 | , 247 | ) 248 | expect(rng).toHaveBeenCalled() 249 | }) 250 | }) 251 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/TagCloud-test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`TagCloud should render not shuffled tags 1`] = ` 4 |
5 |
8 | 12 | tag1 13 | 14 | 18 | tag2 19 | 20 | 24 | tag3 25 | 26 | 30 | tag4 31 | 32 |
33 |
34 | `; 35 | 36 | exports[`TagCloud should render tags with default renderer 1`] = ` 37 |
38 |
41 | 45 | tag4 46 | 47 | 51 | tag2 52 | 53 | 57 | tag3 58 | 59 | 63 | tag1 64 | 65 |
66 |
67 | `; 68 | 69 | exports[`TagCloud should use custom renderer 1`] = ` 70 | 100 | `; 101 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/defaultRenderer-test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`defaultRenderer should render tag 1`] = ` 4 |
5 | 9 | tag1 10 | 11 |
12 | `; 13 | -------------------------------------------------------------------------------- /__tests__/defaultRenderer-test.js: -------------------------------------------------------------------------------- 1 | import { defaultRenderer } from '../src/defaultRenderer' 2 | import { expectToMatchSnapshot } from './utils' 3 | 4 | describe('defaultRenderer', () => { 5 | it('should render tag', () => { 6 | const tag = defaultRenderer( 7 | { value: 'tag1', key: 'key1', count: 33 }, 8 | 18, 9 | 'red', 10 | ) 11 | expectToMatchSnapshot(tag) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /__tests__/helpers-test.js: -------------------------------------------------------------------------------- 1 | import { omit, pick, fontSizeConverter } from '../src/helpers' 2 | 3 | describe('helpers', () => { 4 | describe('omit', () => { 5 | it('should omit object properties', () => { 6 | const obj = omit({ a: 1, b: 2, c: 3, d: 4 }, ['a', 'c']) 7 | expect(obj).toEqual({ b: 2, d: 4 }) 8 | }) 9 | }) 10 | 11 | describe('pick', () => { 12 | it('should include all object properties', () => { 13 | const obj = pick({ a: 1, b: 2, c: 3, d: 4 }, ['a', 'c']) 14 | expect(obj).toEqual({ a: 1, c: 3 }) 15 | }) 16 | 17 | it('should include all available object properties', () => { 18 | const obj = pick({ a: 1, b: 2, c: 3, d: 4 }, ['b', 'e']) 19 | expect(obj).toEqual({ b: 2 }) 20 | }) 21 | }) 22 | 23 | describe('fontSizeConverter', () => { 24 | it('should minimal tag size', () => { 25 | const size = fontSizeConverter(25, 10, 1000, 12, 25) 26 | expect(size).toEqual(12) 27 | }) 28 | 29 | it('should maximal tag size', () => { 30 | const size = fontSizeConverter(980, 10, 1000, 12, 25) 31 | expect(size).toEqual(25) 32 | }) 33 | 34 | it('should middle tag size', () => { 35 | const size = fontSizeConverter(510, 10, 1000, 12, 25) 36 | expect(size).toEqual(19) 37 | }) 38 | 39 | it('should handle devision by zero', () => { 40 | const size = fontSizeConverter(450, 10, 10, 12, 25) 41 | expect(size).toEqual(19) 42 | }) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /__tests__/utils.js: -------------------------------------------------------------------------------- 1 | import { act } from 'react-dom/test-utils' 2 | import * as ReactDOM from 'react-dom' 3 | 4 | export function render(elem) { 5 | let container = document.createElement('div') 6 | act(() => { 7 | ReactDOM.render(elem, container) 8 | }) 9 | return container 10 | } 11 | 12 | export function expectToMatchSnapshot(elem) { 13 | const container = render(elem) 14 | expect(container).toMatchSnapshot() 15 | } 16 | -------------------------------------------------------------------------------- /demo-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madox2/react-tagcloud/43a706b36a606ddd8a39c4060510166aebdf445e/demo-min.png -------------------------------------------------------------------------------- /examples/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | coverage/* 3 | dist/* 4 | lib/* 5 | -------------------------------------------------------------------------------- /examples/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app", 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "quotes": ["error", "single", { "avoidEscape": true }], 6 | "comma-dangle": ["error", "always-multiline"], 7 | "semi": ["error", "never"], 8 | "import/no-anonymous-default-export": "off", 9 | "prettier/prettier": "warn" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": es5, 5 | } 6 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | To run local react-tagcloud sources in development, link the folowing packages: 16 | 17 | ``` 18 | # link react-tagcloud into examples 19 | cd .. 20 | yarn link 21 | cd examples 22 | yarn link react-tagcloud 23 | 24 | # link react and webpack to not colide with react-scripts 25 | cd node_modules/react 26 | yarn link 27 | cd ../webpack 28 | yarn link 29 | cd ../../../ 30 | yarn link react webpack 31 | cd examples 32 | ``` 33 | 34 | ### `yarn test` 35 | 36 | Launches the test runner in the interactive watch mode.
37 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 38 | 39 | ### `yarn build` 40 | 41 | Builds the app for production to the `build` folder.
42 | It correctly bundles React in production mode and optimizes the build for the best performance. 43 | 44 | The build is minified and the filenames include the hashes.
45 | Your app is ready to be deployed! 46 | 47 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 48 | 49 | ### `yarn eject` 50 | 51 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 52 | 53 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 54 | 55 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 56 | 57 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 58 | 59 | ## Learn More 60 | 61 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 62 | 63 | To learn React, check out the [React documentation](https://reactjs.org/). 64 | 65 | ### Code Splitting 66 | 67 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 68 | 69 | ### Analyzing the Bundle Size 70 | 71 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 72 | 73 | ### Making a Progressive Web App 74 | 75 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 76 | 77 | ### Advanced Configuration 78 | 79 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 80 | 81 | ### Deployment 82 | 83 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 84 | 85 | ### `yarn build` fails to minify 86 | 87 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 88 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "homepage": "https://madox2.github.io/react-tagcloud", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "react": "^17.0.2", 8 | "react-dom": "^17.0.2", 9 | "react-ga": "^3.3.0", 10 | "react-github-btn": "^1.2.1", 11 | "react-highlight": "^0.14.0", 12 | "react-scripts": "5.0.0", 13 | "react-tagcloud": "^2.3.3", 14 | "whatwg-fetch": "^3.6.2" 15 | }, 16 | "scripts": { 17 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 18 | "build": "react-scripts build", 19 | "predeploy": "SKIP_PREFLIGHT_CHECK=true yarn build", 20 | "deploy": "gh-pages -d build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject", 23 | "lint": "eslint src/**/*.js" 24 | }, 25 | "eslintConfig": { 26 | "extends": "react-app" 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | }, 40 | "devDependencies": { 41 | "gh-pages": "^3.2.3" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | 14 | 15 | 16 | React TagCloud examples 17 | 18 | 19 | 20 |
21 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /examples/src/App.js: -------------------------------------------------------------------------------- 1 | /* eslint jsx-a11y/anchor-is-valid: 0 */ 2 | import React from 'react' 3 | import Highlight from 'react-highlight' 4 | import analytics from './analytics' 5 | import GitHubButton from 'react-github-btn' 6 | 7 | const BASE_URL = 8 | 'https://raw.githubusercontent.com/madox2/react-tagcloud/master/examples/src/' 9 | 10 | const examples = [ 11 | { file: 'simple-cloud.js', title: 'Simple cloud' }, 12 | { file: 'interactive-cloud.js', title: 'Interactive cloud' }, 13 | { file: 'custom-color-options.js', title: 'Custom color options' }, 14 | { file: 'custom-styles.js', title: 'Custom styles' }, 15 | { file: 'tag-props.js', title: 'Custom tag props' }, 16 | { file: 'custom-renderer.js', title: 'Custom renderer' }, 17 | { file: 'shuffle-with-seed.js', title: 'Shuffle with seed' }, 18 | ].map((example, key) => ({ ...example, key })) 19 | 20 | class App extends React.Component { 21 | componentDidMount() { 22 | analytics.pageview() 23 | } 24 | render() { 25 | return ( 26 |
27 |
28 |

Tag cloud for React

29 |

30 | react-tagcloud examples 31 | 35 | 36 | 37 |

38 | 39 |

40 | 47 | Star 48 | 49 |

50 |
51 |
52 |
53 |
54 |

Quickstart

55 |
56 | 57 | {` 58 | npm install react-tagcloud 59 | `.trim()} 60 | 61 |
62 |
63 | 64 | {` 65 | const data = [ 66 | { value: 'JavaScript', count: 38 }, 67 | { value: 'React', count: 30 }, 68 | { value: 'Nodejs', count: 28 }, 69 | { value: 'Express.js', count: 25 }, 70 | { value: 'HTML5', count: 33 }, 71 | { value: 'MongoDB', count: 18 }, 72 | { value: 'CSS3', count: 20 }, 73 | ] 74 | 75 | const SimpleCloud = () => ( 76 | 81 | ) 82 | `.trim()} 83 | 84 |
85 |

86 | Documentation:{' '} 87 | 88 | https://github.com/madox2/react-tagcloud 89 | 90 |

91 |
92 |
93 |
94 |
95 | {examples.map((e) => ( 96 | 102 | ))} 103 |
104 |
105 |

106 | 2022 madox2 107 |

108 |
109 |
110 | ) 111 | } 112 | } 113 | 114 | class Example extends React.Component { 115 | constructor(props, ctx) { 116 | super(props, ctx) 117 | this.state = { expanded: false, fetched: false, detail: '' } 118 | this.toggleDetail = this.toggleDetail.bind(this) 119 | } 120 | 121 | toggleDetail(e) { 122 | this.setState({ expanded: !this.state.expanded }) 123 | if (!this.state.fetched) { 124 | analytics.codeExpanded() 125 | this.fetch() 126 | } 127 | e.preventDefault() 128 | } 129 | 130 | fetch() { 131 | fetch(BASE_URL + this.props.file) 132 | .then((response) => response.text()) 133 | .then((text) => { 134 | this.setState({ 135 | fetched: true, 136 | detail: text, 137 | }) 138 | }) 139 | } 140 | 141 | render() { 142 | return ( 143 | 163 | ) 164 | } 165 | } 166 | 167 | export default App 168 | -------------------------------------------------------------------------------- /examples/src/analytics.js: -------------------------------------------------------------------------------- 1 | import ReactGA from 'react-ga' 2 | 3 | if (process.env.NODE_ENV !== 'production') { 4 | ReactGA.initialize('UA-000000-01', { debug: true }) 5 | } else { 6 | ReactGA.initialize('UA-87163647-1') 7 | } 8 | 9 | const pageview = () => ReactGA.pageview('/') 10 | 11 | const codeExpanded = () => 12 | ReactGA.event({ 13 | category: 'User', 14 | action: 'Expanded Code Preview', 15 | }) 16 | 17 | const analytics = { pageview, codeExpanded } 18 | 19 | export default analytics 20 | -------------------------------------------------------------------------------- /examples/src/custom-color-options.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TagCloud } from 'react-tagcloud' 3 | 4 | const data = [ 5 | { value: 'jQuery', count: 25 }, 6 | { value: 'MongoDB', count: 18 }, 7 | { value: 'JavaScript', count: 38 }, 8 | { value: 'React', count: 30 }, 9 | { value: 'Nodejs', count: 28 }, 10 | { value: 'Express.js', count: 25 }, 11 | { value: 'HTML5', count: 33 }, 12 | { value: 'CSS3', count: 20 }, 13 | { value: 'Webpack', count: 22 }, 14 | { value: 'Babel.js', count: 7 }, 15 | { value: 'ECMAScript', count: 25 }, 16 | { value: 'Jest', count: 15 }, 17 | { value: 'Mocha', count: 17 }, 18 | { value: 'React Native', count: 27 }, 19 | { value: 'Angular.js', count: 30 }, 20 | { value: 'TypeScript', count: 15 }, 21 | { value: 'Flow', count: 30 }, 22 | { value: 'NPM', count: 11 }, 23 | ] 24 | 25 | // custom random color options 26 | // see randomColor package: https://github.com/davidmerfield/randomColor 27 | const options = { 28 | luminosity: 'light', 29 | hue: 'blue', 30 | } 31 | 32 | export default () => ( 33 | console.log('clicking on tag:', tag)} 39 | /> 40 | ) 41 | -------------------------------------------------------------------------------- /examples/src/custom-renderer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TagCloud } from 'react-tagcloud' 3 | 4 | const data = [ 5 | { value: 'jQuery', count: 25 }, 6 | { value: 'MongoDB', count: 18 }, 7 | { value: 'JavaScript', count: 38 }, 8 | { value: 'React', count: 30 }, 9 | { value: 'Nodejs', count: 28 }, 10 | { value: 'Express.js', count: 25 }, 11 | { value: 'HTML5', count: 33 }, 12 | { value: 'CSS3', count: 20 }, 13 | { value: 'Webpack', count: 22 }, 14 | { value: 'Babel.js', count: 7 }, 15 | { value: 'ECMAScript', count: 25 }, 16 | { value: 'Jest', count: 15 }, 17 | { value: 'Mocha', count: 17 }, 18 | { value: 'React Native', count: 27 }, 19 | { value: 'Angular.js', count: 30 }, 20 | { value: 'TypeScript', count: 15 }, 21 | { value: 'Flow', count: 30 }, 22 | { value: 'NPM', count: 11 }, 23 | ] 24 | 25 | /* CSS: 26 | @keyframes blinker { 27 | 50% { opacity: 0.0; } 28 | } 29 | */ 30 | 31 | // custom renderer is function which has tag, computed font size and 32 | // color as arguments, and returns react component which represents tag 33 | const customRenderer = (tag, size, color) => ( 34 | 47 | {tag.value} 48 | 49 | ) 50 | 51 | export default () => ( 52 | 53 | ) 54 | -------------------------------------------------------------------------------- /examples/src/custom-styles.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TagCloud } from 'react-tagcloud' 3 | 4 | const data = [ 5 | { value: 'jQuery', count: 25 }, 6 | { value: 'MongoDB', count: 18 }, 7 | { value: 'JavaScript', count: 38 }, 8 | { value: 'React', count: 30 }, 9 | { value: 'Nodejs', count: 28 }, 10 | { value: 'Express.js', count: 25 }, 11 | { value: 'HTML5', count: 33 }, 12 | { value: 'CSS3', count: 20 }, 13 | { value: 'Webpack', count: 22 }, 14 | { value: 'Babel.js', count: 7 }, 15 | { value: 'ECMAScript', count: 25 }, 16 | { value: 'Jest', count: 15 }, 17 | { value: 'Mocha', count: 17 }, 18 | { value: 'React Native', count: 27 }, 19 | { value: 'Angular.js', count: 30 }, 20 | { value: 'TypeScript', count: 15 }, 21 | { value: 'Flow', count: 30 }, 22 | { value: 'NPM', count: 11 }, 23 | ] 24 | 25 | /* CSS: 26 | .myTagCloud span { 27 | text-decoration: underline; 28 | } 29 | */ 30 | 31 | // default style class names are tag-cloud and tag-cloud-tag 32 | 33 | // class name of the wrapping component can be overriden 34 | // by passing `className` prop 35 | // it is also possible to pass inline styles 36 | export default () => ( 37 | 44 | ) 45 | -------------------------------------------------------------------------------- /examples/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | import './styles/index.css' 5 | import '../node_modules/highlight.js/styles/atom-one-dark.css' 6 | import 'whatwg-fetch' 7 | 8 | ReactDOM.render(, document.getElementById('root')) 9 | -------------------------------------------------------------------------------- /examples/src/interactive-cloud.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { TagCloud } from 'react-tagcloud' 3 | 4 | const defaultData = [ 5 | { value: 'jQuery', count: 25 }, 6 | { value: 'MongoDB', count: 18 }, 7 | { value: 'JavaScript', count: 38 }, 8 | { value: 'React', count: 30 }, 9 | { value: 'Nodejs', count: 28 }, 10 | { value: 'Express.js', count: 25 }, 11 | { value: 'HTML5', count: 33 }, 12 | { value: 'CSS3', count: 20 }, 13 | { value: 'Webpack', count: 22 }, 14 | { value: 'Babel.js', count: 7 }, 15 | { value: 'ECMAScript', count: 25 }, 16 | { value: 'Jest', count: 15 }, 17 | { value: 'Mocha', count: 17 }, 18 | { value: 'React Native', count: 27 }, 19 | { value: 'Angular.js', count: 30 }, 20 | { value: 'TypeScript', count: 15 }, 21 | { value: 'Flow', count: 30 }, 22 | { value: 'NPM', count: 11 }, 23 | ] 24 | 25 | export default () => { 26 | const [minSize, setMinSize] = useState(12) 27 | const [maxSize, setMaxSize] = useState(35) 28 | const [data, setData] = useState(defaultData) 29 | const [randomColor, setRandomColor] = useState(true) 30 | const [shuffle, setShuffle] = useState(true) 31 | return ( 32 |
33 |
34 |
35 | Min 36 | setMinSize(parseInt(e.target.value, 10))} 41 | /> 42 |
43 |
44 | Max 45 | setMaxSize(parseInt(e.target.value, 10))} 50 | /> 51 |
52 |
53 | Shuffle 54 | setShuffle(!shuffle)} 58 | /> 59 |
60 |
61 | Color 62 | setRandomColor(!randomColor)} 66 | /> 67 |
68 |
69 | 70 |
71 |
72 | { 80 | const value = prompt('Edit tag value', tag.value) 81 | if (value == null) { 82 | return 83 | } 84 | const newTag = { value, count: tag.count } 85 | const newData = data.map((t) => { 86 | if (t.value === tag.value) { 87 | return newTag 88 | } 89 | return t 90 | }) 91 | setData(newData) 92 | }} 93 | /> 94 |
95 | ) 96 | } 97 | -------------------------------------------------------------------------------- /examples/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/src/shuffle-with-seed.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TagCloud } from 'react-tagcloud' 3 | 4 | const data = [ 5 | { value: 'jQuery', count: 25 }, 6 | { value: 'MongoDB', count: 18 }, 7 | { value: 'JavaScript', count: 38 }, 8 | { value: 'React', count: 30 }, 9 | { value: 'Nodejs', count: 28 }, 10 | { value: 'Express.js', count: 25 }, 11 | { value: 'HTML5', count: 33 }, 12 | { value: 'CSS3', count: 20 }, 13 | { value: 'Webpack', count: 22 }, 14 | { value: 'Babel.js', count: 7 }, 15 | { value: 'ECMAScript', count: 25 }, 16 | { value: 'Jest', count: 15 }, 17 | { value: 'Mocha', count: 17 }, 18 | { value: 'React Native', count: 27 }, 19 | { value: 'Angular.js', count: 30 }, 20 | { value: 'TypeScript', count: 15 }, 21 | { value: 'Flow', count: 30 }, 22 | { value: 'NPM', count: 11 }, 23 | ] 24 | 25 | // randomSeed - seed passed to the seeded random number generator 26 | export default () => ( 27 | 28 | ) 29 | -------------------------------------------------------------------------------- /examples/src/simple-cloud.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TagCloud } from 'react-tagcloud' 3 | 4 | const data = [ 5 | { value: 'jQuery', count: 25 }, 6 | { value: 'MongoDB', count: 18 }, 7 | { value: 'JavaScript', count: 38 }, 8 | { value: 'React', count: 30 }, 9 | { value: 'Nodejs', count: 28 }, 10 | { value: 'Express.js', count: 25 }, 11 | { value: 'HTML5', count: 33 }, 12 | { value: 'CSS3', count: 20 }, 13 | { value: 'Webpack', count: 22 }, 14 | { value: 'Babel.js', count: 7 }, 15 | { value: 'ECMAScript', count: 25 }, 16 | { value: 'Jest', count: 15 }, 17 | { value: 'Mocha', count: 17 }, 18 | { value: 'React Native', count: 27 }, 19 | { value: 'Angular.js', count: 30 }, 20 | { value: 'TypeScript', count: 15 }, 21 | { value: 'Flow', count: 30 }, 22 | { value: 'NPM', count: 11 }, 23 | ] 24 | 25 | /* CSS: 26 | .simple-cloud .tag-cloud-tag { 27 | cursor: pointer; 28 | } 29 | */ 30 | 31 | // minSize, maxSize - font size in px 32 | // tags - array of objects with properties value and count 33 | // shuffle - indicates if data should be shuffled (true by default) 34 | // onClick event handler has `tag` and `event` parameter 35 | export default () => ( 36 | alert(`'${tag.value}' was selected!`)} 42 | /> 43 | ) 44 | -------------------------------------------------------------------------------- /examples/src/styles/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | --header-text-color: GhostWhite; 3 | --text-color: #d9d9d9; 4 | --border-color: #3c3e40; 5 | --body-color: #292929; 6 | --section-color: #282c34; 7 | } 8 | body { 9 | font-family: 'Open Sans', sans-serif; 10 | color: var(--text-color); 11 | background-color: var(--body-color); 12 | margin: 0; 13 | padding: 0; 14 | } 15 | main { 16 | margin: 0; 17 | padding: 0; 18 | } 19 | header { 20 | background-color: var(--section-color); 21 | min-height: 150px; 22 | display: flex; 23 | flex-direction: column; 24 | align-items: center; 25 | justify-content: center; 26 | color: var(--header-text-color); 27 | } 28 | footer { 29 | background-color: var(--section-color); 30 | display: flex; 31 | flex-direction: column; 32 | align-items: center; 33 | justify-content: center; 34 | color: var(--header-text-color); 35 | } 36 | a { 37 | text-decoration: none; 38 | color: var(--text-color); 39 | } 40 | a:hover { 41 | text-decoration: underline; 42 | } 43 | header > h1, 44 | header > h3 { 45 | margin: 5px; 46 | } 47 | section { 48 | background-color: var(--body-color); 49 | padding-bottom: 40px; 50 | } 51 | article { 52 | border-top: 1px solid var(--border-color); 53 | padding: 15px 10px 10px 10px; 54 | } 55 | article > div { 56 | max-width: 800px; 57 | margin: auto; 58 | } 59 | .detail-wrapper { 60 | margin-top: 10px; 61 | font-size: 1.2em; 62 | } 63 | .detail-wrapper a { 64 | color: var(--text-color); 65 | text-decoration: none; 66 | } 67 | .code-preview { 68 | border: 1px solid var(--border-color); 69 | } 70 | .cloud-wrapper { 71 | border: 1px solid var(--border-color); 72 | text-align: center; 73 | background-color: var(--section-color); 74 | padding: 20px; 75 | } 76 | a.github-link { 77 | margin-left: 7px; 78 | color: var(--text-color); 79 | } 80 | /* styles needed by examples */ 81 | @keyframes blinker { 82 | 50% { 83 | opacity: 0; 84 | } 85 | } 86 | .myTagCloud span { 87 | text-decoration: underline; 88 | } 89 | .simple-cloud .tag-cloud-tag { 90 | cursor: pointer; 91 | } 92 | .controls { 93 | padding: 10px 0px; 94 | color: white; 95 | display: flex; 96 | flex-wrap: wrap; 97 | align-items: center; 98 | justify-content: center; 99 | } 100 | .controls > div > * { 101 | margin: 4px 5px; 102 | } 103 | .controls input[type='number'] { 104 | width: 40px; 105 | } 106 | -------------------------------------------------------------------------------- /examples/src/tag-props.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TagCloud } from 'react-tagcloud' 3 | 4 | const data = [ 5 | { value: 'jQuery', count: 25 }, 6 | { value: 'MongoDB', count: 18 }, 7 | { value: 'JavaScript', count: 38 }, 8 | { 9 | value: 'React', 10 | count: 30, 11 | // props below will be passed to tag component 12 | props: { 13 | title: 'React is awesome', 14 | style: { 15 | color: 'red', 16 | }, 17 | }, 18 | }, 19 | { value: 'Nodejs', count: 28 }, 20 | { value: 'Express.js', count: 25 }, 21 | { value: 'HTML5', count: 33 }, 22 | { value: 'CSS3', count: 20 }, 23 | { value: 'Webpack', count: 22 }, 24 | { value: 'Babel.js', count: 7 }, 25 | { value: 'ECMAScript', count: 25 }, 26 | { value: 'Jest', count: 15 }, 27 | { value: 'Mocha', count: 17 }, 28 | { value: 'React Native', count: 27 }, 29 | { value: 'Angular.js', count: 30 }, 30 | { value: 'TypeScript', count: 15 }, 31 | { value: 'Flow', count: 30 }, 32 | { value: 'NPM', count: 11 }, 33 | ] 34 | 35 | export default () => ( 36 | 45 | ) 46 | -------------------------------------------------------------------------------- /lib/TagCloud.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 4 | 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | exports.TagCloud = TagCloud; 9 | 10 | var _propTypes = _interopRequireDefault(require("prop-types")); 11 | 12 | var _react = _interopRequireWildcard(require("react")); 13 | 14 | var _shuffleArray = _interopRequireDefault(require("shuffle-array")); 15 | 16 | var _randomcolor = _interopRequireDefault(require("randomcolor")); 17 | 18 | var _seedrandom = _interopRequireDefault(require("seedrandom")); 19 | 20 | var _defaultRenderer = require("./defaultRenderer"); 21 | 22 | var _helpers = require("./helpers"); 23 | 24 | var _excluded = ["renderer", "shuffle", "className", "colorOptions", "containerComponent"]; 25 | 26 | function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } 27 | 28 | function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } 29 | 30 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 31 | 32 | function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } 33 | 34 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } 35 | 36 | function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } 37 | 38 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } 39 | 40 | function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } 41 | 42 | function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } 43 | 44 | function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } 45 | 46 | function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } 47 | 48 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } 49 | 50 | function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } 51 | 52 | function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } 53 | 54 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } 55 | 56 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } 57 | 58 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } 59 | 60 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 61 | 62 | var handlersPropNames = ['onClick', 'onDoubleClick', 'onMouseMove', 'onMouseOver', 'onMouseOut', // rn handlers 63 | 'onPress', 'onPressIn', 'onPressOut']; 64 | var cloudPropNames = ['tags', 'shuffle', 'renderer', 'maxSize', 'minSize', 'colorOptions', 'disableRandomColor', 'randomSeed', 'randomNumberGenerator', 'containerComponent']; 65 | 66 | function getTagHashCode(tag) { 67 | return tag.key + tag.value + tag.count; 68 | } 69 | 70 | function generateColor(tag, _ref) { 71 | var disableRandomColor = _ref.disableRandomColor, 72 | colorOptions = _ref.colorOptions, 73 | randomSeed = _ref.randomSeed; 74 | 75 | if (tag.color) { 76 | return tag.color; 77 | } 78 | 79 | if (disableRandomColor) { 80 | return undefined; 81 | } 82 | 83 | return (0, _randomcolor["default"])(_objectSpread({ 84 | seed: randomSeed && "".concat(randomSeed, ":").concat(getTagHashCode(tag)) 85 | }, colorOptions)); 86 | } 87 | 88 | function withTagCloudHandlers(elem, tag, cloudHandlers) { 89 | var origHandlers = (0, _helpers.pick)(elem.props, handlersPropNames); 90 | var props = (0, _helpers.keys)(cloudHandlers).reduce(function (acc, handlerName) { 91 | if (cloudHandlers[handlerName] || origHandlers[handlerName]) { 92 | acc[handlerName] = function (e) { 93 | cloudHandlers[handlerName] && cloudHandlers[handlerName](tag, e); 94 | origHandlers[handlerName] && origHandlers(e); 95 | }; 96 | } 97 | 98 | return acc; 99 | }, {}); 100 | return /*#__PURE__*/_react["default"].cloneElement(elem, props); 101 | } 102 | 103 | function renderTags(props, data) { 104 | var minSize = props.minSize, 105 | maxSize = props.maxSize; 106 | var counts = data.map(function (_ref2) { 107 | var tag = _ref2.tag; 108 | return tag.count; 109 | }), 110 | min = Math.min.apply(Math, _toConsumableArray(counts)), 111 | max = Math.max.apply(Math, _toConsumableArray(counts)); 112 | var cloudHandlers = (0, _helpers.pick)(props, handlersPropNames); 113 | return data.map(function (_ref3) { 114 | var tag = _ref3.tag, 115 | color = _ref3.color; 116 | var fontSize = (0, _helpers.fontSizeConverter)(tag.count, min, max, minSize, maxSize); 117 | var elem = props.renderer(tag, fontSize, color); 118 | return withTagCloudHandlers(elem, tag, cloudHandlers); 119 | }); 120 | } 121 | 122 | function randomize(props) { 123 | var tags = props.tags, 124 | shuffle = props.shuffle, 125 | randomSeed = props.randomSeed, 126 | randomNumberGenerator = props.randomNumberGenerator; 127 | var rng = randomSeed ? (0, _seedrandom["default"])(randomSeed) : randomNumberGenerator; 128 | var copy = tags.slice(); 129 | var data = shuffle ? (0, _shuffleArray["default"])(copy, { 130 | rng: rng 131 | }) : copy; 132 | return data.map(function (tag) { 133 | return { 134 | tag: tag, 135 | color: generateColor(tag, props) 136 | }; 137 | }); 138 | } 139 | 140 | function TagCloud(_ref4) { 141 | var _ref4$renderer = _ref4.renderer, 142 | renderer = _ref4$renderer === void 0 ? _defaultRenderer.defaultRenderer : _ref4$renderer, 143 | _ref4$shuffle = _ref4.shuffle, 144 | shuffle = _ref4$shuffle === void 0 ? true : _ref4$shuffle, 145 | _ref4$className = _ref4.className, 146 | className = _ref4$className === void 0 ? 'tag-cloud' : _ref4$className, 147 | _ref4$colorOptions = _ref4.colorOptions, 148 | colorOptions = _ref4$colorOptions === void 0 ? {} : _ref4$colorOptions, 149 | _ref4$containerCompon = _ref4.containerComponent, 150 | containerComponent = _ref4$containerCompon === void 0 ? 'div' : _ref4$containerCompon, 151 | otherProps = _objectWithoutProperties(_ref4, _excluded); 152 | 153 | var props = _objectSpread({ 154 | renderer: renderer, 155 | shuffle: shuffle, 156 | className: className, 157 | colorOptions: colorOptions, 158 | containerComponent: containerComponent 159 | }, otherProps); 160 | 161 | var _useState = (0, _react.useState)([]), 162 | _useState2 = _slicedToArray(_useState, 2), 163 | data = _useState2[0], 164 | setData = _useState2[1]; 165 | 166 | var tagsComparison = props.tags.map(getTagHashCode).join(':'); // randomize (color, shuffle) when tags or certain props change 167 | 168 | (0, _react.useEffect)(function () { 169 | setData(randomize(props)); 170 | }, [JSON.stringify(props.colorOptions), props.randomSeed, props.shuffle, props.disableRandomColor, tagsComparison]); 171 | var other = (0, _helpers.omit)(props, [].concat(cloudPropNames, handlersPropNames)); 172 | var Container = props.containerComponent; 173 | return /*#__PURE__*/_react["default"].createElement(Container, other, renderTags(props, data)); 174 | } 175 | 176 | TagCloud.propTypes = { 177 | tags: _propTypes["default"].array.isRequired, 178 | maxSize: _propTypes["default"].number.isRequired, 179 | minSize: _propTypes["default"].number.isRequired, 180 | shuffle: _propTypes["default"].bool, 181 | colorOptions: _propTypes["default"].object, 182 | disableRandomColor: _propTypes["default"].bool, 183 | renderer: _propTypes["default"].func, 184 | className: _propTypes["default"].string, 185 | randomSeed: _propTypes["default"].any, 186 | randomNumberGenerator: _propTypes["default"].func, 187 | containerComponent: _propTypes["default"].elementType 188 | }; -------------------------------------------------------------------------------- /lib/TagCloudOld.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.TagCloud = undefined; 7 | 8 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 9 | 10 | var _react = require('react'); 11 | 12 | var _react2 = _interopRequireDefault(_react); 13 | 14 | var _propTypes = require('prop-types'); 15 | 16 | var _propTypes2 = _interopRequireDefault(_propTypes); 17 | 18 | var _defaultRenderer = require('./defaultRenderer'); 19 | 20 | var _shuffleArray = require('shuffle-array'); 21 | 22 | var _shuffleArray2 = _interopRequireDefault(_shuffleArray); 23 | 24 | var _randomcolor = require('randomcolor'); 25 | 26 | var _randomcolor2 = _interopRequireDefault(_randomcolor); 27 | 28 | var _helpers = require('./helpers'); 29 | 30 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 31 | 32 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 33 | 34 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 35 | 36 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 37 | 38 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 39 | 40 | var eventHandlers = ['onClick', 'onDoubleClick', 'onMouseMove']; 41 | var cloudProps = ['tags', 'shuffle', 'renderer', 'maxSize', 'minSize', 'colorOptions', 'disableRandomColor', 'randomNumberGenerator']; 42 | 43 | var generateColor = function generateColor(tag, _ref) { 44 | var disableRandomColor = _ref.disableRandomColor, 45 | colorOptions = _ref.colorOptions; 46 | 47 | if (tag.color) { 48 | return tag.color; 49 | } 50 | if (disableRandomColor) { 51 | return undefined; 52 | } 53 | return (0, _randomcolor2.default)(colorOptions); 54 | }; 55 | 56 | var TagCloud = exports.TagCloud = function (_React$Component) { 57 | _inherits(TagCloud, _React$Component); 58 | 59 | function TagCloud() { 60 | _classCallCheck(this, TagCloud); 61 | 62 | return _possibleConstructorReturn(this, (TagCloud.__proto__ || Object.getPrototypeOf(TagCloud)).apply(this, arguments)); 63 | } 64 | 65 | _createClass(TagCloud, [{ 66 | key: 'componentWillReceiveProps', 67 | value: function componentWillReceiveProps(newProps) { 68 | var propsEqual = (0, _helpers.propertiesEqual)(this.props, newProps, Object.keys(TagCloud.propTypes)); 69 | var tagsEqual = (0, _helpers.arraysEqual)(newProps.tags, this.props.tags); 70 | if (!tagsEqual || !propsEqual) { 71 | this._populate(newProps); 72 | } 73 | } 74 | }, { 75 | key: 'componentWillMount', 76 | value: function componentWillMount() { 77 | this._populate(this.props); 78 | } 79 | }, { 80 | key: 'render', 81 | value: function render() { 82 | var props = (0, _helpers.omitProps)(this.props, [].concat(cloudProps, eventHandlers)); 83 | var tagElements = this._attachEventHandlers(); 84 | return _react2.default.createElement( 85 | 'div', 86 | props, 87 | tagElements 88 | ); 89 | } 90 | }, { 91 | key: '_attachEventHandlers', 92 | value: function _attachEventHandlers() { 93 | var _this2 = this; 94 | 95 | var cloudHandlers = (0, _helpers.includeProps)(this.props, eventHandlers); 96 | return this._data.map(function (_ref2) { 97 | var tag = _ref2.tag, 98 | fontSize = _ref2.fontSize, 99 | color = _ref2.color; 100 | 101 | var elem = _this2.props.renderer(tag, fontSize, color); 102 | var tagHandlers = (0, _helpers.includeProps)(elem.props, eventHandlers); 103 | var globalHandlers = Object.keys(cloudHandlers).reduce(function (r, k) { 104 | r[k] = function (e) { 105 | cloudHandlers[k](tag, e); 106 | tagHandlers[k] && tagHandlers(e); 107 | }; 108 | return r; 109 | }, {}); 110 | return _react2.default.cloneElement(elem, globalHandlers); 111 | }); 112 | } 113 | }, { 114 | key: '_populate', 115 | value: function _populate(props) { 116 | var tags = props.tags, 117 | shuffle = props.shuffle, 118 | minSize = props.minSize, 119 | maxSize = props.maxSize, 120 | randomNumberGenerator = props.randomNumberGenerator; 121 | 122 | var counts = tags.map(function (tag) { 123 | return tag.count; 124 | }), 125 | min = Math.min.apply(Math, _toConsumableArray(counts)), 126 | max = Math.max.apply(Math, _toConsumableArray(counts)); 127 | var data = tags.map(function (tag) { 128 | return { 129 | tag: tag, 130 | color: generateColor(tag, props), 131 | fontSize: (0, _helpers.fontSizeConverter)(tag.count, min, max, minSize, maxSize) 132 | }; 133 | }); 134 | this._data = shuffle ? (0, _shuffleArray2.default)(data, { copy: true, rng: randomNumberGenerator }) : data; 135 | } 136 | }]); 137 | 138 | return TagCloud; 139 | }(_react2.default.Component); 140 | 141 | TagCloud.propTypes = { 142 | tags: _propTypes2.default.array.isRequired, 143 | maxSize: _propTypes2.default.number.isRequired, 144 | minSize: _propTypes2.default.number.isRequired, 145 | shuffle: _propTypes2.default.bool, 146 | colorOptions: _propTypes2.default.object, 147 | disableRandomColor: _propTypes2.default.bool, 148 | renderer: _propTypes2.default.func, 149 | className: _propTypes2.default.string, 150 | randomNumberGenerator: _propTypes2.default.func 151 | }; 152 | 153 | TagCloud.defaultProps = { 154 | renderer: _defaultRenderer.defaultRenderer, 155 | shuffle: true, 156 | className: 'tag-cloud', 157 | colorOptions: {} 158 | }; -------------------------------------------------------------------------------- /lib/default-renderer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 8 | 9 | var _react = require("react"); 10 | 11 | var _react2 = _interopRequireDefault(_react); 12 | 13 | var _randomcolor = require("randomcolor"); 14 | 15 | var _randomcolor2 = _interopRequireDefault(_randomcolor); 16 | 17 | var defaultClassName = "tag-cloud-tag"; 18 | 19 | var defaultStyles = { 20 | margin: "0px 3px", 21 | verticalAlign: "middle", 22 | display: "inline-block" 23 | }; 24 | 25 | var defaultTagRenderer = function defaultTagRenderer(tag) { 26 | return tag.value; 27 | }; 28 | 29 | exports["default"] = function () { 30 | var _ref = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; 31 | 32 | var _ref$tagRenderer = _ref.tagRenderer; 33 | var tagRenderer = _ref$tagRenderer === undefined ? defaultTagRenderer : _ref$tagRenderer; 34 | var _ref$colorOptions = _ref.colorOptions; 35 | var colorOptions = _ref$colorOptions === undefined ? {} : _ref$colorOptions; 36 | var _ref$props = _ref.props; 37 | var props = _ref$props === undefined ? {} : _ref$props; 38 | return function (tag, size, key) { 39 | var handlers = arguments.length <= 3 || arguments[3] === undefined ? {} : arguments[3]; 40 | 41 | var className = defaultClassName, 42 | fontSize = size + "px", 43 | color = props.disableRandomColor ? tag.color || 'black' : (0, _randomcolor2["default"])(colorOptions); 44 | 45 | var eventHandlers = {}; 46 | Object.keys(handlers).forEach(function (key) { 47 | return handlers[key] && (eventHandlers[key] = function (e) { 48 | return handlers[key](tag, e); 49 | }); 50 | }); 51 | 52 | var elementProps = Object.assign({}, { className: className }, eventHandlers, props, { key: key }); 53 | elementProps.style = Object.assign({}, defaultStyles, { color: color }, props.style, { fontSize: fontSize }); 54 | 55 | return _react2["default"].createElement( 56 | "span", 57 | elementProps, 58 | tagRenderer(tag) 59 | ); 60 | }; 61 | }; 62 | 63 | module.exports = exports["default"]; -------------------------------------------------------------------------------- /lib/defaultRenderer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.defaultRenderer = void 0; 7 | 8 | var _react = _interopRequireDefault(require("react")); 9 | 10 | var _excluded = ["className", "style"]; 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 13 | 14 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } 15 | 16 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; } 17 | 18 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } 19 | 20 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 21 | 22 | function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } 23 | 24 | function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } 25 | 26 | var defaultRenderer = function defaultRenderer(tag, size, color) { 27 | var _ref = tag.props || {}, 28 | className = _ref.className, 29 | style = _ref.style, 30 | props = _objectWithoutProperties(_ref, _excluded); 31 | 32 | var fontSize = size + 'px'; 33 | var key = tag.key || tag.value; 34 | 35 | var tagStyle = _objectSpread(_objectSpread({}, styles), {}, { 36 | color: color, 37 | fontSize: fontSize 38 | }, style); 39 | 40 | var tagClassName = 'tag-cloud-tag'; 41 | 42 | if (className) { 43 | tagClassName += ' ' + className; 44 | } 45 | 46 | return /*#__PURE__*/_react["default"].createElement("span", _extends({ 47 | className: tagClassName, 48 | style: tagStyle, 49 | key: key 50 | }, props), tag.value); 51 | }; 52 | 53 | exports.defaultRenderer = defaultRenderer; 54 | var styles = { 55 | margin: '0px 3px', 56 | verticalAlign: 'middle', 57 | display: 'inline-block' 58 | }; -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.fontSizeConverter = void 0; 7 | exports.keys = keys; 8 | exports.omit = void 0; 9 | exports.pick = pick; 10 | 11 | /** 12 | * Computes appropriate font size of tag. 13 | */ 14 | var fontSizeConverter = function fontSizeConverter(count, min, max, minSize, maxSize) { 15 | if (max - min === 0) { 16 | // handle devision by zero 17 | return Math.round((minSize + maxSize) / 2); 18 | } 19 | 20 | return Math.round((count - min) * (maxSize - minSize) / (max - min) + minSize); 21 | }; 22 | /** 23 | * Creates an object composed of not omitted object properties. 24 | */ 25 | 26 | 27 | exports.fontSizeConverter = fontSizeConverter; 28 | 29 | var omit = function omit(obj, keys) { 30 | return Object.keys(obj).reduce(function (r, key) { 31 | if (!~keys.indexOf(key)) { 32 | r[key] = obj[key]; 33 | } 34 | 35 | return r; 36 | }, {}); 37 | }; 38 | /** 39 | * Creates an object composed of the picked object properties. 40 | */ 41 | 42 | 43 | exports.omit = omit; 44 | 45 | function pick(obj, keys) { 46 | return keys.reduce(function (picked, key) { 47 | picked[key] = obj[key]; 48 | return picked; 49 | }, {}); 50 | } 51 | /** 52 | * Returns an array of object keys. 53 | */ 54 | 55 | 56 | function keys(obj) { 57 | return Object.keys(obj); 58 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "TagCloud", { 7 | enumerable: true, 8 | get: function get() { 9 | return _TagCloud.TagCloud; 10 | } 11 | }); 12 | 13 | var _TagCloud = require("./TagCloud"); -------------------------------------------------------------------------------- /lib/tag-cloud.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 8 | 9 | var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 12 | 13 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 14 | 15 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 16 | 17 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 18 | 19 | var _react = require("react"); 20 | 21 | var _react2 = _interopRequireDefault(_react); 22 | 23 | var _defaultRenderer = require("./default-renderer"); 24 | 25 | var _defaultRenderer2 = _interopRequireDefault(_defaultRenderer); 26 | 27 | var _arrayShuffle = require("shuffle-array"); 28 | 29 | var _arrayShuffle2 = _interopRequireDefault(_arrayShuffle); 30 | 31 | var omitted = ['tags', 'shuffle', 'renderer', 'maxSize', 'minSize', 'onClick']; 32 | var omittedProps = omitted.reduce(function (r, k) { 33 | return Object.assign(r, _defineProperty({}, k, undefined)); 34 | }, {}); 35 | 36 | var fontSizeConverter = function fontSizeConverter(count, min, max, minSize, maxSize) { 37 | return Math.round((count - min) * (maxSize - minSize) / (max - min) + minSize); 38 | }; 39 | 40 | var createTags = function createTags(_ref) { 41 | var tags = _ref.tags; 42 | var minSize = _ref.minSize; 43 | var maxSize = _ref.maxSize; 44 | var renderer = _ref.renderer; 45 | var onClick = _ref.onClick; 46 | 47 | var counts = tags.map(function (tag) { 48 | return tag.count; 49 | }), 50 | min = Math.min.apply(Math, counts), 51 | max = Math.max.apply(Math, counts); 52 | var computeFontSize = function computeFontSize(tag) { 53 | return { 54 | tag: tag, 55 | fontSize: fontSizeConverter(tag.count, min, max, minSize, maxSize) 56 | }; 57 | }; 58 | var handlers = { onClick: onClick }; 59 | var createComponent = function createComponent(_ref2, key) { 60 | var tag = _ref2.tag; 61 | var fontSize = _ref2.fontSize; 62 | return renderer(tag, fontSize, key, handlers); 63 | }; 64 | return tags.map(computeFontSize).map(createComponent); 65 | }; 66 | 67 | var TagCloud = (function (_React$Component) { 68 | _inherits(TagCloud, _React$Component); 69 | 70 | function TagCloud() { 71 | _classCallCheck(this, TagCloud); 72 | 73 | _get(Object.getPrototypeOf(TagCloud.prototype), "constructor", this).apply(this, arguments); 74 | } 75 | 76 | _createClass(TagCloud, [{ 77 | key: "render", 78 | value: function render() { 79 | var props = Object.assign({}, this.props, omittedProps); 80 | var tags = createTags(this.props); 81 | return _react2["default"].createElement( 82 | "div", 83 | props, 84 | this.props.shuffle ? (0, _arrayShuffle2["default"])(tags) : tags 85 | ); 86 | } 87 | }]); 88 | 89 | return TagCloud; 90 | })(_react2["default"].Component); 91 | 92 | exports["default"] = TagCloud; 93 | 94 | TagCloud.propTypes = { 95 | tags: _react2["default"].PropTypes.array.isRequired, 96 | maxSize: _react2["default"].PropTypes.number.isRequired, 97 | minSize: _react2["default"].PropTypes.number.isRequired, 98 | shuffle: _react2["default"].PropTypes.bool, 99 | renderer: _react2["default"].PropTypes.func, 100 | className: _react2["default"].PropTypes.string 101 | }; 102 | 103 | TagCloud.defaultProps = { 104 | renderer: (0, _defaultRenderer2["default"])(), 105 | shuffle: true, 106 | className: "tag-cloud" 107 | }; 108 | module.exports = exports["default"]; 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-tagcloud", 3 | "version": "2.3.3", 4 | "description": "Tag/word cloud component for react", 5 | "keywords": [ 6 | "react-tagcloud", 7 | "react", 8 | "tag-cloud", 9 | "word-cloud", 10 | "tagcloud", 11 | "wordcloud" 12 | ], 13 | "main": "lib/index.js", 14 | "scripts": { 15 | "compile": "babel -d lib/ src/", 16 | "compile:watch": "while inotifywait -e close_write src/*.js; do yarn compile; done", 17 | "prepublish": "npm run compile", 18 | "test": "jest", 19 | "lint": "eslint src/**/*.js __tests__/**/*.js" 20 | }, 21 | "author": "madox2", 22 | "license": { 23 | "type": "MIT", 24 | "url": "https://github.com/madox2/react-tagcloud/blob/master/LICENSE" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/madox2/react-tagcloud.git" 29 | }, 30 | "jest": { 31 | "testRegex": "(/__tests__/.*-test)\\.js?$", 32 | "testEnvironment": "jsdom" 33 | }, 34 | "dependencies": { 35 | "prop-types": "^15.6.2", 36 | "randomcolor": "^0.6.2", 37 | "seedrandom": "^3.0.5", 38 | "shuffle-array": "^1.0.1" 39 | }, 40 | "peerDependencies": { 41 | "react": ">=16.8.0" 42 | }, 43 | "devDependencies": { 44 | "@babel/cli": "^7.16.0", 45 | "@babel/core": "^7.16.5", 46 | "@babel/preset-env": "^7.16.5", 47 | "@babel/preset-react": "^7.16.5", 48 | "babel-eslint": "^10.0.0", 49 | "babel-jest": "^27.4.5", 50 | "eslint": "^8.5.0", 51 | "eslint-config-react-app": "^7.0.0", 52 | "eslint-plugin-flowtype": "^8.0.3", 53 | "eslint-plugin-import": "^2.22.0", 54 | "eslint-plugin-jsx-a11y": "^6.3.1", 55 | "eslint-plugin-prettier": "^4.0.0", 56 | "eslint-plugin-react": "^7.20.3", 57 | "eslint-plugin-react-hooks": "^4.0.8", 58 | "jest-cli": "^27.4.5", 59 | "prettier": "^2.5.1", 60 | "react": "^17.0.2", 61 | "react-dom": "^17.0.2", 62 | "react-test-renderer": "^17.0.2", 63 | "upgrade": "^1.1.0", 64 | "webpack": "^5.65.0", 65 | "yarn": "^1.22.17" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /rn/defaultRenderer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Text, TouchableOpacity } from 'react-native' 3 | 4 | function TagItem({ children, onPress, onPressIn, onPressOut, ...props }) { 5 | if (onPress || onPressIn || onPressOut) { 6 | return ( 7 | 12 | {children} 13 | 14 | ) 15 | } 16 | return {children} 17 | } 18 | 19 | export const defaultRenderer = (tag, size, color) => { 20 | const { style, ...props } = tag.props || {} 21 | const fontSize = size 22 | const key = tag.key || tag.value 23 | const tagStyle = { ...styles, color, fontSize, ...style } 24 | 25 | return ( 26 | 27 | {tag.value} 28 | 29 | ) 30 | } 31 | 32 | const styles = { 33 | marginRight: 3, 34 | marginLeft: 3, 35 | } 36 | -------------------------------------------------------------------------------- /rn/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { View, StyleSheet } from 'react-native' 4 | import { TagCloud as BaseTagCloud } from '../src' 5 | import { defaultRenderer } from './defaultRenderer' 6 | 7 | export function TagCloud(props) { 8 | return ( 9 | 15 | ) 16 | } 17 | 18 | const styles = StyleSheet.create({ 19 | container: { 20 | flexDirection: 'row', 21 | flexWrap: 'wrap', 22 | alignItems: 'center', 23 | justifyContent: 'center', 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /src/TagCloud.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React, { useState, useEffect } from 'react' 3 | import arrayShuffle from 'shuffle-array' 4 | import randomColor from 'randomcolor' 5 | import seedrandom from 'seedrandom' 6 | 7 | import { defaultRenderer } from './defaultRenderer' 8 | import { fontSizeConverter, keys, omit, pick } from './helpers' 9 | 10 | const handlersPropNames = [ 11 | 'onClick', 12 | 'onDoubleClick', 13 | 'onMouseMove', 14 | 'onMouseOver', 15 | 'onMouseOut', 16 | // rn handlers 17 | 'onPress', 18 | 'onPressIn', 19 | 'onPressOut', 20 | ] 21 | const cloudPropNames = [ 22 | 'tags', 23 | 'shuffle', 24 | 'renderer', 25 | 'maxSize', 26 | 'minSize', 27 | 'colorOptions', 28 | 'disableRandomColor', 29 | 'randomSeed', 30 | 'randomNumberGenerator', 31 | 'containerComponent', 32 | ] 33 | 34 | function getTagHashCode(tag) { 35 | return tag.key + tag.value + tag.count 36 | } 37 | 38 | function generateColor(tag, { disableRandomColor, colorOptions, randomSeed }) { 39 | if (tag.color) { 40 | return tag.color 41 | } 42 | if (disableRandomColor) { 43 | return undefined 44 | } 45 | return randomColor({ 46 | seed: randomSeed && `${randomSeed}:${getTagHashCode(tag)}`, 47 | ...colorOptions, 48 | }) 49 | } 50 | 51 | function withTagCloudHandlers(elem, tag, cloudHandlers) { 52 | const origHandlers = pick(elem.props, handlersPropNames) 53 | const props = keys(cloudHandlers).reduce((acc, handlerName) => { 54 | if (cloudHandlers[handlerName] || origHandlers[handlerName]) { 55 | acc[handlerName] = (e) => { 56 | cloudHandlers[handlerName] && cloudHandlers[handlerName](tag, e) 57 | origHandlers[handlerName] && origHandlers(e) 58 | } 59 | } 60 | return acc 61 | }, {}) 62 | return React.cloneElement(elem, props) 63 | } 64 | 65 | function renderTags(props, data) { 66 | const { minSize, maxSize } = props 67 | const counts = data.map(({ tag }) => tag.count), 68 | min = Math.min(...counts), 69 | max = Math.max(...counts) 70 | const cloudHandlers = pick(props, handlersPropNames) 71 | return data.map(({ tag, color }) => { 72 | const fontSize = fontSizeConverter(tag.count, min, max, minSize, maxSize) 73 | const elem = props.renderer(tag, fontSize, color) 74 | return withTagCloudHandlers(elem, tag, cloudHandlers) 75 | }) 76 | } 77 | 78 | function randomize(props) { 79 | const { tags, shuffle, randomSeed, randomNumberGenerator } = props 80 | const rng = randomSeed ? seedrandom(randomSeed) : randomNumberGenerator 81 | const copy = tags.slice() 82 | const data = shuffle ? arrayShuffle(copy, { rng }) : copy 83 | return data.map((tag) => ({ 84 | tag, 85 | color: generateColor(tag, props), 86 | })) 87 | } 88 | 89 | export function TagCloud({ 90 | renderer = defaultRenderer, 91 | shuffle = true, 92 | className = 'tag-cloud', 93 | colorOptions = {}, 94 | containerComponent = 'div', 95 | ...otherProps 96 | }) { 97 | const props = { 98 | renderer, 99 | shuffle, 100 | className, 101 | colorOptions, 102 | containerComponent, 103 | ...otherProps, 104 | } 105 | const [data, setData] = useState([]) 106 | const tagsComparison = props.tags.map(getTagHashCode).join(':') 107 | // randomize (color, shuffle) when tags or certain props change 108 | useEffect(() => { 109 | setData(randomize(props)) 110 | }, [ 111 | JSON.stringify(props.colorOptions), 112 | props.randomSeed, 113 | props.shuffle, 114 | props.disableRandomColor, 115 | tagsComparison, 116 | ]) 117 | const other = omit(props, [...cloudPropNames, ...handlersPropNames]) 118 | const Container = props.containerComponent 119 | return {renderTags(props, data)} 120 | } 121 | 122 | TagCloud.propTypes = { 123 | tags: PropTypes.array.isRequired, 124 | maxSize: PropTypes.number.isRequired, 125 | minSize: PropTypes.number.isRequired, 126 | shuffle: PropTypes.bool, 127 | colorOptions: PropTypes.object, 128 | disableRandomColor: PropTypes.bool, 129 | renderer: PropTypes.func, 130 | className: PropTypes.string, 131 | randomSeed: PropTypes.any, 132 | randomNumberGenerator: PropTypes.func, 133 | containerComponent: PropTypes.elementType, 134 | } 135 | -------------------------------------------------------------------------------- /src/defaultRenderer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const defaultRenderer = (tag, size, color) => { 4 | const { className, style, ...props } = tag.props || {} 5 | const fontSize = size + 'px' 6 | const key = tag.key || tag.value 7 | const tagStyle = { ...styles, color, fontSize, ...style } 8 | 9 | let tagClassName = 'tag-cloud-tag' 10 | if (className) { 11 | tagClassName += ' ' + className 12 | } 13 | 14 | return ( 15 | 16 | {tag.value} 17 | 18 | ) 19 | } 20 | 21 | const styles = { 22 | margin: '0px 3px', 23 | verticalAlign: 'middle', 24 | display: 'inline-block', 25 | } 26 | -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Computes appropriate font size of tag. 3 | */ 4 | export const fontSizeConverter = (count, min, max, minSize, maxSize) => { 5 | if (max - min === 0) { 6 | // handle devision by zero 7 | return Math.round((minSize + maxSize) / 2) 8 | } 9 | return Math.round( 10 | ((count - min) * (maxSize - minSize)) / (max - min) + minSize, 11 | ) 12 | } 13 | 14 | /** 15 | * Creates an object composed of not omitted object properties. 16 | */ 17 | export const omit = (obj, keys) => { 18 | return Object.keys(obj).reduce((r, key) => { 19 | if (!~keys.indexOf(key)) { 20 | r[key] = obj[key] 21 | } 22 | return r 23 | }, {}) 24 | } 25 | 26 | /** 27 | * Creates an object composed of the picked object properties. 28 | */ 29 | export function pick(obj, keys) { 30 | return keys.reduce((picked, key) => { 31 | picked[key] = obj[key] 32 | return picked 33 | }, {}) 34 | } 35 | 36 | /** 37 | * Returns an array of object keys. 38 | */ 39 | export function keys(obj) { 40 | return Object.keys(obj) 41 | } 42 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { TagCloud } from './TagCloud' 2 | --------------------------------------------------------------------------------