react-d3-cloud-modern 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +11 -0
- package/.idea/inspectionProfiles/Project_Default.xml +40 -0
- package/.idea/modules.xml +8 -0
- package/.idea/react-d3-cloud-modern.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/LICENSE +22 -0
- package/README.md +212 -0
- package/babel.config.js +11 -0
- package/index.html +11 -0
- package/jest.config.js +8 -0
- package/lib/WordCloud.d.ts +31 -0
- package/lib/WordCloud.js +99 -0
- package/lib/esm/WordCloud.d.ts +31 -0
- package/lib/esm/WordCloud.js +99 -0
- package/lib/esm/index.d.ts +2 -0
- package/lib/esm/index.js +9 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +9 -0
- package/lint-staged.config.js +5 -0
- package/package.json +95 -0
- package/prettier.config.js +4 -0
- package/src/WordCloud.tsx +138 -0
- package/src/__tests__/WordCloud.spec.tsx +167 -0
- package/src/index.ts +3 -0
- package/test/jest-setup.ts +2 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +29 -0
package/.babelrc
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<component name="InspectionProjectProfileManager">
|
|
2
|
+
<profile version="1.0">
|
|
3
|
+
<option name="myName" value="Project Default" />
|
|
4
|
+
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
5
|
+
<inspection_tool class="HttpUrlsUsage" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
6
|
+
<inspection_tool class="PyBroadExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
7
|
+
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
|
8
|
+
<option name="ignoredPackages">
|
|
9
|
+
<value>
|
|
10
|
+
<list size="3">
|
|
11
|
+
<item index="0" class="java.lang.String" itemvalue="langchain_experimental" />
|
|
12
|
+
<item index="1" class="java.lang.String" itemvalue="langchain_decorators" />
|
|
13
|
+
<item index="2" class="java.lang.String" itemvalue="huggingface_hub" />
|
|
14
|
+
</list>
|
|
15
|
+
</value>
|
|
16
|
+
</option>
|
|
17
|
+
</inspection_tool>
|
|
18
|
+
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
|
19
|
+
<option name="ignoredErrors">
|
|
20
|
+
<list>
|
|
21
|
+
<option value="E501" />
|
|
22
|
+
<option value="E722" />
|
|
23
|
+
<option value="W605" />
|
|
24
|
+
</list>
|
|
25
|
+
</option>
|
|
26
|
+
</inspection_tool>
|
|
27
|
+
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
|
28
|
+
<option name="ignoredErrors">
|
|
29
|
+
<list>
|
|
30
|
+
<option value="N803" />
|
|
31
|
+
</list>
|
|
32
|
+
</option>
|
|
33
|
+
</inspection_tool>
|
|
34
|
+
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
|
35
|
+
<option name="processCode" value="true" />
|
|
36
|
+
<option name="processLiterals" value="true" />
|
|
37
|
+
<option name="processComments" value="true" />
|
|
38
|
+
</inspection_tool>
|
|
39
|
+
</profile>
|
|
40
|
+
</component>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="ProjectModuleManager">
|
|
4
|
+
<modules>
|
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/react-d3-cloud-modern.iml" filepath="$PROJECT_DIR$/.idea/react-d3-cloud-modern.iml" />
|
|
6
|
+
</modules>
|
|
7
|
+
</component>
|
|
8
|
+
</project>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="WEB_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
6
|
+
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
7
|
+
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
8
|
+
</content>
|
|
9
|
+
<orderEntry type="inheritedJdk" />
|
|
10
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
11
|
+
</component>
|
|
12
|
+
</module>
|
package/.idea/vcs.xml
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016-present Yoctol (github.com/Yoctol/react-d3-cloud)
|
|
4
|
+
Copyright (c) 2024 Carlos Alvidrez (github.com/carlosalvidrez/react-d3-cloud-modern)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# react-d3-cloud-modern
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/react-d3-cloud)
|
|
4
|
+
[](https://github.com/Yoctol/react-d3-cloud/actions?query=branch%3Amaster)
|
|
5
|
+
|
|
6
|
+
A word cloud react component built with [d3-cloud](https://github.com/jasondavies/d3-cloud).
|
|
7
|
+
|
|
8
|
+
Forked from [react-d3-cloud] in order to modernize its dependency on libraries with multiple high-level vulnerabilities.
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install react-d3-cloud-modern
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Simple:
|
|
21
|
+
|
|
22
|
+
```jsx
|
|
23
|
+
import React from 'react';
|
|
24
|
+
import { render } from 'react-dom';
|
|
25
|
+
import WordCloud from 'react-d3-cloud-modern';
|
|
26
|
+
|
|
27
|
+
const data = [
|
|
28
|
+
{ text: 'Hey', value: 1000 },
|
|
29
|
+
{ text: 'lol', value: 200 },
|
|
30
|
+
{ text: 'first impression', value: 800 },
|
|
31
|
+
{ text: 'very cool', value: 1000000 },
|
|
32
|
+
{ text: 'duck', value: 10 },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
render(<WordCloud data={data} />, document.getElementById('root'));
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
More configuration:
|
|
39
|
+
|
|
40
|
+
```jsx
|
|
41
|
+
import React from 'react';
|
|
42
|
+
import { render } from 'react-dom';
|
|
43
|
+
import WordCloud from 'react-d3-cloud-modern';
|
|
44
|
+
import { scaleOrdinal } from 'd3-scale';
|
|
45
|
+
import { schemeCategory10 } from 'd3-scale-chromatic';
|
|
46
|
+
|
|
47
|
+
const data = [
|
|
48
|
+
{ text: 'Hey', value: 1000 },
|
|
49
|
+
{ text: 'lol', value: 200 },
|
|
50
|
+
{ text: 'first impression', value: 800 },
|
|
51
|
+
{ text: 'very cool', value: 1000000 },
|
|
52
|
+
{ text: 'duck', value: 10 },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const schemeCategory10ScaleOrdinal = scaleOrdinal(schemeCategory10);
|
|
56
|
+
|
|
57
|
+
render(
|
|
58
|
+
<WordCloud
|
|
59
|
+
data={data}
|
|
60
|
+
width={500}
|
|
61
|
+
height={500}
|
|
62
|
+
font="Times"
|
|
63
|
+
fontStyle="italic"
|
|
64
|
+
fontWeight="bold"
|
|
65
|
+
fontSize={(word) => Math.log2(word.value) * 5}
|
|
66
|
+
spiral="rectangular"
|
|
67
|
+
rotate={(word) => word.value % 360}
|
|
68
|
+
padding={5}
|
|
69
|
+
random={Math.random}
|
|
70
|
+
fill={(d, i) => schemeCategory10ScaleOrdinal(i)}
|
|
71
|
+
onWordClick={(event, d) => {
|
|
72
|
+
console.log(`onWordClick: ${d.text}`);
|
|
73
|
+
}}
|
|
74
|
+
onWordMouseOver={(event, d) => {
|
|
75
|
+
console.log(`onWordMouseOver: ${d.text}`);
|
|
76
|
+
}}
|
|
77
|
+
onWordMouseOut={(event, d) => {
|
|
78
|
+
console.log(`onWordMouseOut: ${d.text}`);
|
|
79
|
+
}}
|
|
80
|
+
/>,
|
|
81
|
+
document.getElementById('root')
|
|
82
|
+
);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Please checkout [demo](https://codesandbox.io/embed/react-d3-cloud-demo-forked-50wzl)
|
|
86
|
+
|
|
87
|
+
for more detailed props, please refer to below:
|
|
88
|
+
|
|
89
|
+
## Props
|
|
90
|
+
|
|
91
|
+
| name | description | type | required | default |
|
|
92
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | -------- | ------------------------------------------- |
|
|
93
|
+
| data | The words array | `{ text: string, value: number }>[]` | ✓ |
|
|
94
|
+
| width | Width of the layout (px) | `number` | | `700` |
|
|
95
|
+
| height | Height of the layout (px) | `number` | | `600` |
|
|
96
|
+
| font | The font accessor function, which indicates the font face for each word. A constant may be specified instead of a function. | `string \| (d) => string` | | `'serif'` |
|
|
97
|
+
| fontStyle | The fontStyle accessor function, which indicates the font style for each word. A constant may be specified instead of a function. | `string \| (d) => string` | | `'normal'` |
|
|
98
|
+
| fontWeight | The fontWeight accessor function, which indicates the font weight for each word. A constant may be specified instead of a function. | `string \| number \| (d) => string \| number` | | `'normal'` |
|
|
99
|
+
| fontSize | The fontSize accessor function, which indicates the numerical font size for each word. | `(d) => number` | | `(d) => Math.sqrt(d.value)` |
|
|
100
|
+
| rotate | The rotate accessor function, which indicates the rotation angle (in degrees) for each word. | `(d) => number` | | `() => (~~(Math.random() * 6) - 3) * 30` |
|
|
101
|
+
| spiral | The current type of spiral used for positioning words. This can either be one of the two built-in spirals, "archimedean" and "rectangular", or an arbitrary spiral generator can be used | `'archimedean' \| 'rectangular' \| ([width, height]) => t => [x, y]` | | `'archimedean'` |
|
|
102
|
+
| padding | The padding accessor function, which indicates the numerical padding for each word. | `number \| (d) => number` | | `1` |
|
|
103
|
+
| random | The internal random number generator, used for selecting the initial position of each word, and the clockwise/counterclockwise direction of the spiral for each word. This should return a number in the range `[0, 1)`. | `(d) => number` | | `Math.random` |
|
|
104
|
+
| fill | The fill accessor function, which indicates the color for each word. | `(d, i) => string` | | `(d, i) => schemeCategory10ScaleOrdinal(i)` |
|
|
105
|
+
| onWordClick | The function will be called when `click` event is triggered on a word | `(event, d) => {}` | | null |
|
|
106
|
+
| onWordMouseOver | The function will be called when `mouseover` event is triggered on a word | `(event, d) => {}` | | null |
|
|
107
|
+
| onWordMouseOut | The function will be called when `mouseout` event is triggered on a word | `(event, d) => {}` | | null |
|
|
108
|
+
|
|
109
|
+
## FAQ
|
|
110
|
+
|
|
111
|
+
### How to Use with Next.js/SSR
|
|
112
|
+
|
|
113
|
+
To make `<WordCloud />` work with Server-Side Rendering (SSR), you need to avoid rendering it on the server:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
{
|
|
117
|
+
typeof window !== 'undefined' && <WordCloud data={data} />;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### How to Avoid Unnecessary Re-render
|
|
122
|
+
|
|
123
|
+
As of version 0.10.1, `<WordCloud />` has been wrapped by `React.memo()` and deep equal comparison under the hood to avoid unnecessary re-render. All you need to do is to make your function props deep equal comparable using `useCallback()`:
|
|
124
|
+
|
|
125
|
+
```js
|
|
126
|
+
import React, { useCallback } from 'react';
|
|
127
|
+
import { render } from 'react-dom';
|
|
128
|
+
import WordCloud from 'react-d3-cloud-modern';
|
|
129
|
+
import { scaleOrdinal } from 'd3-scale';
|
|
130
|
+
import { schemeCategory10 } from 'd3-scale-chromatic';
|
|
131
|
+
|
|
132
|
+
function App() {
|
|
133
|
+
const data = [
|
|
134
|
+
{ text: 'Hey', value: 1000 },
|
|
135
|
+
{ text: 'lol', value: 200 },
|
|
136
|
+
{ text: 'first impression', value: 800 },
|
|
137
|
+
{ text: 'very cool', value: 1000000 },
|
|
138
|
+
{ text: 'duck', value: 10 },
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const fontSize = useCallback((word) => Math.log2(word.value) * 5, []);
|
|
142
|
+
const rotate = useCallback((word) => word.value % 360, []);
|
|
143
|
+
const fill = useCallback((d, i) => scaleOrdinal(schemeCategory10)(i), []);
|
|
144
|
+
const onWordClick = useCallback((word) => {
|
|
145
|
+
console.log(`onWordClick: ${word}`);
|
|
146
|
+
}, []);
|
|
147
|
+
const onWordMouseOver = useCallback((word) => {
|
|
148
|
+
console.log(`onWordMouseOver: ${word}`);
|
|
149
|
+
}, []);
|
|
150
|
+
const onWordMouseOut = useCallback((word) => {
|
|
151
|
+
console.log(`onWordMouseOut: ${word}`);
|
|
152
|
+
}, []);
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<WordCloud
|
|
156
|
+
data={data}
|
|
157
|
+
width={500}
|
|
158
|
+
height={500}
|
|
159
|
+
font="Times"
|
|
160
|
+
fontStyle="italic"
|
|
161
|
+
fontWeight="bold"
|
|
162
|
+
fontSize={fontSize}
|
|
163
|
+
spiral="rectangular"
|
|
164
|
+
rotate={rotate}
|
|
165
|
+
padding={5}
|
|
166
|
+
random={Math.random}
|
|
167
|
+
fill={fill}
|
|
168
|
+
onWordClick={onWordClick}
|
|
169
|
+
onWordMouseOver={onWordMouseOver}
|
|
170
|
+
onWordMouseOut={onWordMouseOut}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Build
|
|
177
|
+
|
|
178
|
+
```sh
|
|
179
|
+
npm run build
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Test
|
|
183
|
+
|
|
184
|
+
### pre-install
|
|
185
|
+
|
|
186
|
+
#### Mac OS X
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
brew install pkg-config cairo pango libpng jpeg giflib librsvg
|
|
190
|
+
npm install
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
#### Ubuntu and Other Debian Based Systems
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
sudo apt-get update
|
|
197
|
+
sudo apt-get install libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++
|
|
198
|
+
npm install
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
For more details, please check out [Installation guides](https://github.com/Automattic/node-canvas/wiki) at node-canvas wiki.
|
|
202
|
+
|
|
203
|
+
### Run Tests
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
npm test
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
MIT © [Yoctol](https://github.com/Yoctol/react-d3-cloud)
|
|
212
|
+
| [carlosalvidrez](https://github.com/carlosalvidrez/react-d3-cloud-modern)
|
package/babel.config.js
ADDED
package/index.html
ADDED
package/jest.config.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import cloud from 'd3-cloud';
|
|
3
|
+
import { BaseType, ValueFn } from 'd3-selection';
|
|
4
|
+
interface Datum {
|
|
5
|
+
text: string;
|
|
6
|
+
value: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Word extends cloud.Word {
|
|
9
|
+
text: string;
|
|
10
|
+
value: number;
|
|
11
|
+
}
|
|
12
|
+
type WordCloudProps = {
|
|
13
|
+
data: Datum[];
|
|
14
|
+
width?: number;
|
|
15
|
+
height?: number;
|
|
16
|
+
font?: string | ((word: Word, index: number) => string);
|
|
17
|
+
fontStyle?: string | ((word: Word, index: number) => string);
|
|
18
|
+
fontWeight?: string | number | ((word: Word, index: number) => string | number);
|
|
19
|
+
fontSize?: number | ((word: Word, index: number) => number);
|
|
20
|
+
rotate?: number | ((word: Word, index: number) => number);
|
|
21
|
+
spiral?: 'archimedean' | 'rectangular' | ((size: [number, number]) => (t: number) => [number, number]);
|
|
22
|
+
padding?: number | ((word: Word, index: number) => number);
|
|
23
|
+
random?: () => number;
|
|
24
|
+
fill?: ValueFn<SVGTextElement, Word, string>;
|
|
25
|
+
onWordClick?: (this: BaseType, event: any, d: Word) => void;
|
|
26
|
+
onWordMouseOver?: (this: BaseType, event: any, d: Word) => void;
|
|
27
|
+
onWordMouseOut?: (this: BaseType, event: any, d: Word) => void;
|
|
28
|
+
};
|
|
29
|
+
declare function WordCloud({ data, width, height, font, fontStyle, fontWeight, fontSize, rotate, spiral, padding, random, fill, onWordClick, onWordMouseOver, onWordMouseOut, }: WordCloudProps): React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
|
30
|
+
declare const _default: React.MemoExoticComponent<typeof WordCloud>;
|
|
31
|
+
export default _default;
|
package/lib/WordCloud.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports["default"] = void 0;
|
|
8
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
9
|
+
var _reactFauxDom = _interopRequireDefault(require("react-faux-dom"));
|
|
10
|
+
var _d3Cloud = _interopRequireDefault(require("d3-cloud"));
|
|
11
|
+
var _reactFastCompare = _interopRequireDefault(require("react-fast-compare"));
|
|
12
|
+
var _d3Selection = require("d3-selection");
|
|
13
|
+
var _d3Scale = require("d3-scale");
|
|
14
|
+
var _d3ScaleChromatic = require("d3-scale-chromatic");
|
|
15
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
|
|
16
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
|
|
17
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
|
|
18
|
+
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
19
|
+
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."); }
|
|
20
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
21
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
22
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
23
|
+
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
24
|
+
var defaultScaleOrdinal = (0, _d3Scale.scaleOrdinal)(_d3ScaleChromatic.schemeCategory10);
|
|
25
|
+
function WordCloud(_ref) {
|
|
26
|
+
var data = _ref.data,
|
|
27
|
+
_ref$width = _ref.width,
|
|
28
|
+
width = _ref$width === void 0 ? 700 : _ref$width,
|
|
29
|
+
_ref$height = _ref.height,
|
|
30
|
+
height = _ref$height === void 0 ? 600 : _ref$height,
|
|
31
|
+
_ref$font = _ref.font,
|
|
32
|
+
font = _ref$font === void 0 ? 'serif' : _ref$font,
|
|
33
|
+
_ref$fontStyle = _ref.fontStyle,
|
|
34
|
+
fontStyle = _ref$fontStyle === void 0 ? 'normal' : _ref$fontStyle,
|
|
35
|
+
_ref$fontWeight = _ref.fontWeight,
|
|
36
|
+
fontWeight = _ref$fontWeight === void 0 ? 'normal' : _ref$fontWeight,
|
|
37
|
+
_ref$fontSize = _ref.fontSize,
|
|
38
|
+
fontSize = _ref$fontSize === void 0 ? function (d) {
|
|
39
|
+
return Math.sqrt(d.value);
|
|
40
|
+
} : _ref$fontSize,
|
|
41
|
+
_ref$rotate = _ref.rotate,
|
|
42
|
+
rotate = _ref$rotate === void 0 ? function () {
|
|
43
|
+
return (~~(Math.random() * 6) - 3) * 30;
|
|
44
|
+
} : _ref$rotate,
|
|
45
|
+
_ref$spiral = _ref.spiral,
|
|
46
|
+
spiral = _ref$spiral === void 0 ? 'archimedean' : _ref$spiral,
|
|
47
|
+
_ref$padding = _ref.padding,
|
|
48
|
+
padding = _ref$padding === void 0 ? 1 : _ref$padding,
|
|
49
|
+
_ref$random = _ref.random,
|
|
50
|
+
random = _ref$random === void 0 ? Math.random : _ref$random,
|
|
51
|
+
_ref$fill = _ref.fill,
|
|
52
|
+
fill = _ref$fill === void 0 ? function (_, i) {
|
|
53
|
+
return defaultScaleOrdinal(i);
|
|
54
|
+
} : _ref$fill,
|
|
55
|
+
onWordClick = _ref.onWordClick,
|
|
56
|
+
onWordMouseOver = _ref.onWordMouseOver,
|
|
57
|
+
onWordMouseOut = _ref.onWordMouseOut;
|
|
58
|
+
var elementRef = (0, _react.useRef)();
|
|
59
|
+
if (!elementRef.current) {
|
|
60
|
+
elementRef.current = _reactFauxDom["default"].createElement('div');
|
|
61
|
+
}
|
|
62
|
+
var el = elementRef.current;
|
|
63
|
+
|
|
64
|
+
// clear old words
|
|
65
|
+
(0, _d3Selection.select)(el).selectAll('*').remove();
|
|
66
|
+
|
|
67
|
+
// render based on new data
|
|
68
|
+
var layout = (0, _d3Cloud["default"])().words(data).size([width, height]).font(font).fontStyle(fontStyle).fontWeight(fontWeight).fontSize(fontSize).rotate(rotate).spiral(spiral).padding(padding).random(random).on('end', function (words) {
|
|
69
|
+
var _layout$size = layout.size(),
|
|
70
|
+
_layout$size2 = _slicedToArray(_layout$size, 2),
|
|
71
|
+
w = _layout$size2[0],
|
|
72
|
+
h = _layout$size2[1];
|
|
73
|
+
var texts = (0, _d3Selection.select)(el).append('svg').attr('viewBox', "0 0 ".concat(w, " ").concat(h)).attr('preserveAspectRatio', 'xMinYMin meet').append('g').attr('transform', "translate(".concat(w / 2, ",").concat(h / 2, ")")).selectAll('text').data(words).enter().append('text').style('font-family', function (d) {
|
|
74
|
+
return d.font;
|
|
75
|
+
}).style('font-style', function (d) {
|
|
76
|
+
return d.style;
|
|
77
|
+
}).style('font-weight', function (d) {
|
|
78
|
+
return d.weight;
|
|
79
|
+
}).style('font-size', function (d) {
|
|
80
|
+
return "".concat(d.size, "px");
|
|
81
|
+
}).style('fill', fill).attr('text-anchor', 'middle').attr('transform', function (d) {
|
|
82
|
+
return "translate(".concat([d.x, d.y], ")rotate(").concat(d.rotate, ")");
|
|
83
|
+
}).text(function (d) {
|
|
84
|
+
return d.text;
|
|
85
|
+
});
|
|
86
|
+
if (onWordClick) {
|
|
87
|
+
texts.on('click', onWordClick);
|
|
88
|
+
}
|
|
89
|
+
if (onWordMouseOver) {
|
|
90
|
+
texts.on('mouseover', onWordMouseOver);
|
|
91
|
+
}
|
|
92
|
+
if (onWordMouseOut) {
|
|
93
|
+
texts.on('mouseout', onWordMouseOut);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
layout.start();
|
|
97
|
+
return el.toReact();
|
|
98
|
+
}
|
|
99
|
+
var _default = exports["default"] = /*#__PURE__*/_react["default"].memo(WordCloud, _reactFastCompare["default"]);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import cloud from 'd3-cloud';
|
|
3
|
+
import { BaseType, ValueFn } from 'd3-selection';
|
|
4
|
+
interface Datum {
|
|
5
|
+
text: string;
|
|
6
|
+
value: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Word extends cloud.Word {
|
|
9
|
+
text: string;
|
|
10
|
+
value: number;
|
|
11
|
+
}
|
|
12
|
+
type WordCloudProps = {
|
|
13
|
+
data: Datum[];
|
|
14
|
+
width?: number;
|
|
15
|
+
height?: number;
|
|
16
|
+
font?: string | ((word: Word, index: number) => string);
|
|
17
|
+
fontStyle?: string | ((word: Word, index: number) => string);
|
|
18
|
+
fontWeight?: string | number | ((word: Word, index: number) => string | number);
|
|
19
|
+
fontSize?: number | ((word: Word, index: number) => number);
|
|
20
|
+
rotate?: number | ((word: Word, index: number) => number);
|
|
21
|
+
spiral?: 'archimedean' | 'rectangular' | ((size: [number, number]) => (t: number) => [number, number]);
|
|
22
|
+
padding?: number | ((word: Word, index: number) => number);
|
|
23
|
+
random?: () => number;
|
|
24
|
+
fill?: ValueFn<SVGTextElement, Word, string>;
|
|
25
|
+
onWordClick?: (this: BaseType, event: any, d: Word) => void;
|
|
26
|
+
onWordMouseOver?: (this: BaseType, event: any, d: Word) => void;
|
|
27
|
+
onWordMouseOut?: (this: BaseType, event: any, d: Word) => void;
|
|
28
|
+
};
|
|
29
|
+
declare function WordCloud({ data, width, height, font, fontStyle, fontWeight, fontSize, rotate, spiral, padding, random, fill, onWordClick, onWordMouseOver, onWordMouseOut, }: WordCloudProps): React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
|
30
|
+
declare const _default: React.MemoExoticComponent<typeof WordCloud>;
|
|
31
|
+
export default _default;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports["default"] = void 0;
|
|
8
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
9
|
+
var _reactFauxDom = _interopRequireDefault(require("react-faux-dom"));
|
|
10
|
+
var _d3Cloud = _interopRequireDefault(require("d3-cloud"));
|
|
11
|
+
var _reactFastCompare = _interopRequireDefault(require("react-fast-compare"));
|
|
12
|
+
var _d3Selection = require("d3-selection");
|
|
13
|
+
var _d3Scale = require("d3-scale");
|
|
14
|
+
var _d3ScaleChromatic = require("d3-scale-chromatic");
|
|
15
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
|
|
16
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
|
|
17
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
|
|
18
|
+
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
19
|
+
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."); }
|
|
20
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
21
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
22
|
+
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
23
|
+
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
24
|
+
var defaultScaleOrdinal = (0, _d3Scale.scaleOrdinal)(_d3ScaleChromatic.schemeCategory10);
|
|
25
|
+
function WordCloud(_ref) {
|
|
26
|
+
var data = _ref.data,
|
|
27
|
+
_ref$width = _ref.width,
|
|
28
|
+
width = _ref$width === void 0 ? 700 : _ref$width,
|
|
29
|
+
_ref$height = _ref.height,
|
|
30
|
+
height = _ref$height === void 0 ? 600 : _ref$height,
|
|
31
|
+
_ref$font = _ref.font,
|
|
32
|
+
font = _ref$font === void 0 ? 'serif' : _ref$font,
|
|
33
|
+
_ref$fontStyle = _ref.fontStyle,
|
|
34
|
+
fontStyle = _ref$fontStyle === void 0 ? 'normal' : _ref$fontStyle,
|
|
35
|
+
_ref$fontWeight = _ref.fontWeight,
|
|
36
|
+
fontWeight = _ref$fontWeight === void 0 ? 'normal' : _ref$fontWeight,
|
|
37
|
+
_ref$fontSize = _ref.fontSize,
|
|
38
|
+
fontSize = _ref$fontSize === void 0 ? function (d) {
|
|
39
|
+
return Math.sqrt(d.value);
|
|
40
|
+
} : _ref$fontSize,
|
|
41
|
+
_ref$rotate = _ref.rotate,
|
|
42
|
+
rotate = _ref$rotate === void 0 ? function () {
|
|
43
|
+
return (~~(Math.random() * 6) - 3) * 30;
|
|
44
|
+
} : _ref$rotate,
|
|
45
|
+
_ref$spiral = _ref.spiral,
|
|
46
|
+
spiral = _ref$spiral === void 0 ? 'archimedean' : _ref$spiral,
|
|
47
|
+
_ref$padding = _ref.padding,
|
|
48
|
+
padding = _ref$padding === void 0 ? 1 : _ref$padding,
|
|
49
|
+
_ref$random = _ref.random,
|
|
50
|
+
random = _ref$random === void 0 ? Math.random : _ref$random,
|
|
51
|
+
_ref$fill = _ref.fill,
|
|
52
|
+
fill = _ref$fill === void 0 ? function (_, i) {
|
|
53
|
+
return defaultScaleOrdinal(i);
|
|
54
|
+
} : _ref$fill,
|
|
55
|
+
onWordClick = _ref.onWordClick,
|
|
56
|
+
onWordMouseOver = _ref.onWordMouseOver,
|
|
57
|
+
onWordMouseOut = _ref.onWordMouseOut;
|
|
58
|
+
var elementRef = (0, _react.useRef)();
|
|
59
|
+
if (!elementRef.current) {
|
|
60
|
+
elementRef.current = _reactFauxDom["default"].createElement('div');
|
|
61
|
+
}
|
|
62
|
+
var el = elementRef.current;
|
|
63
|
+
|
|
64
|
+
// clear old words
|
|
65
|
+
(0, _d3Selection.select)(el).selectAll('*').remove();
|
|
66
|
+
|
|
67
|
+
// render based on new data
|
|
68
|
+
var layout = (0, _d3Cloud["default"])().words(data).size([width, height]).font(font).fontStyle(fontStyle).fontWeight(fontWeight).fontSize(fontSize).rotate(rotate).spiral(spiral).padding(padding).random(random).on('end', function (words) {
|
|
69
|
+
var _layout$size = layout.size(),
|
|
70
|
+
_layout$size2 = _slicedToArray(_layout$size, 2),
|
|
71
|
+
w = _layout$size2[0],
|
|
72
|
+
h = _layout$size2[1];
|
|
73
|
+
var texts = (0, _d3Selection.select)(el).append('svg').attr('viewBox', "0 0 ".concat(w, " ").concat(h)).attr('preserveAspectRatio', 'xMinYMin meet').append('g').attr('transform', "translate(".concat(w / 2, ",").concat(h / 2, ")")).selectAll('text').data(words).enter().append('text').style('font-family', function (d) {
|
|
74
|
+
return d.font;
|
|
75
|
+
}).style('font-style', function (d) {
|
|
76
|
+
return d.style;
|
|
77
|
+
}).style('font-weight', function (d) {
|
|
78
|
+
return d.weight;
|
|
79
|
+
}).style('font-size', function (d) {
|
|
80
|
+
return "".concat(d.size, "px");
|
|
81
|
+
}).style('fill', fill).attr('text-anchor', 'middle').attr('transform', function (d) {
|
|
82
|
+
return "translate(".concat([d.x, d.y], ")rotate(").concat(d.rotate, ")");
|
|
83
|
+
}).text(function (d) {
|
|
84
|
+
return d.text;
|
|
85
|
+
});
|
|
86
|
+
if (onWordClick) {
|
|
87
|
+
texts.on('click', onWordClick);
|
|
88
|
+
}
|
|
89
|
+
if (onWordMouseOver) {
|
|
90
|
+
texts.on('mouseover', onWordMouseOver);
|
|
91
|
+
}
|
|
92
|
+
if (onWordMouseOut) {
|
|
93
|
+
texts.on('mouseout', onWordMouseOut);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
layout.start();
|
|
97
|
+
return el.toReact();
|
|
98
|
+
}
|
|
99
|
+
var _default = exports["default"] = /*#__PURE__*/_react["default"].memo(WordCloud, _reactFastCompare["default"]);
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports["default"] = void 0;
|
|
7
|
+
var _WordCloud = _interopRequireDefault(require("./WordCloud"));
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
|
|
9
|
+
var _default = exports["default"] = _WordCloud["default"];
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports["default"] = void 0;
|
|
7
|
+
var _WordCloud = _interopRequireDefault(require("./WordCloud"));
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
|
|
9
|
+
var _default = exports["default"] = _WordCloud["default"];
|
package/package.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-d3-cloud-modern",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A word cloud component using d3-cloud",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "cph",
|
|
7
|
+
"homepage": "https://github.com/carlosalvidrez/react-d3-cloud-modern",
|
|
8
|
+
"repository": "carlosalvidrez/react-d3-cloud-modern",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/carlosalvidrez/react-d3-cloud-modern/issues"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
"import": "./lib/esm/index.js",
|
|
14
|
+
"require": "./lib/index.js"
|
|
15
|
+
},
|
|
16
|
+
"main": "lib/index.js",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "npm run clean && npm run build:cjs && npm run build:esm",
|
|
19
|
+
"build:cjs": "babel src -d lib --extensions '.ts,.tsx' --ignore '**/__tests__/**' && tsc -p tsconfig.build.json -m commonjs --emitDeclarationOnly",
|
|
20
|
+
"build:esm": "cross-env ESM=true babel src -d lib/esm --extensions '.ts,.tsx' --ignore '**/__tests__/**' && tsc -p tsconfig.build.json -m esnext --emitDeclarationOnly --outDir lib/esm",
|
|
21
|
+
"check": "tsc --noEmit",
|
|
22
|
+
"clean": "rimraf lib",
|
|
23
|
+
"lint": "eslint .",
|
|
24
|
+
"lint:fix": "npm run lint -- --fix",
|
|
25
|
+
"lint:staged": "lint-staged",
|
|
26
|
+
"prepare": "husky install",
|
|
27
|
+
"prepublishOnly": "npm run build",
|
|
28
|
+
"test": "npm run lint && npm run testonly",
|
|
29
|
+
"testonly": "jest",
|
|
30
|
+
"testonly:watch": "npm run testonly -- --watch",
|
|
31
|
+
"prettier": "prettier --write --list-different .",
|
|
32
|
+
"prettier:check": "prettier --check .",
|
|
33
|
+
"preversion": "npm test"
|
|
34
|
+
},
|
|
35
|
+
"types": "lib/index.d.ts",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"d3-cloud": "^1.2.5",
|
|
38
|
+
"d3-scale": "^3.3.0",
|
|
39
|
+
"d3-scale-chromatic": "^2.0.0",
|
|
40
|
+
"d3-selection": "^2.0.0",
|
|
41
|
+
"prop-types": "^15.7.2",
|
|
42
|
+
"react-fast-compare": "^3.2.0",
|
|
43
|
+
"react-faux-dom": "^4.5.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": "^16.8.0 || ^17.0.0-0 || ^18.0.0-0",
|
|
47
|
+
"react-dom": "^16.8.0 || ^17.0.0-0 || ^18.0.0-0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@babel/cli": "^7.14.8",
|
|
51
|
+
"@babel/core": "^7.15.0",
|
|
52
|
+
"@babel/preset-env": "^7.15.0",
|
|
53
|
+
"@babel/preset-react": "^7.14.5",
|
|
54
|
+
"@babel/preset-typescript": "^7.24.7",
|
|
55
|
+
"@testing-library/jest-dom": "^5.14.1",
|
|
56
|
+
"@testing-library/react": "^12.1.1",
|
|
57
|
+
"@types/d3-cloud": "^1.2.5",
|
|
58
|
+
"@types/d3-scale": "^4.0.1",
|
|
59
|
+
"@types/d3-scale-chromatic": "^3.0.0",
|
|
60
|
+
"@types/d3-selection": "^3.0.1",
|
|
61
|
+
"@types/jest": "^27.0.1",
|
|
62
|
+
"@types/react": "^17.0.80",
|
|
63
|
+
"@types/react-dom": "^18.3.0",
|
|
64
|
+
"@types/react-faux-dom": "^4.1.5",
|
|
65
|
+
"@typescript-eslint/eslint-plugin": "^4.29.3",
|
|
66
|
+
"@typescript-eslint/parser": "^4.29.3",
|
|
67
|
+
"babel-eslint": "^10.1.0",
|
|
68
|
+
"babel-plugin-typescript-to-proptypes": "^1.4.2",
|
|
69
|
+
"canvas": "^2.8.0",
|
|
70
|
+
"cross-env": "^7.0.3",
|
|
71
|
+
"eslint": "^7.32.0",
|
|
72
|
+
"eslint-config-yoctol": "^0.26.2",
|
|
73
|
+
"eslint-plugin-import": "^2.24.0",
|
|
74
|
+
"eslint-plugin-jsx-a11y": "^6.4.1",
|
|
75
|
+
"eslint-plugin-prettier": "^3.4.0",
|
|
76
|
+
"eslint-plugin-react": "^7.25.2",
|
|
77
|
+
"eslint-plugin-react-hooks": "^4.2.0",
|
|
78
|
+
"eslint-plugin-sort-imports-es6-autofix": "^0.6.0",
|
|
79
|
+
"husky": "^7.0.1",
|
|
80
|
+
"jest": "^27.0.6",
|
|
81
|
+
"lint-staged": "^11.1.2",
|
|
82
|
+
"prettier": "^2.3.2",
|
|
83
|
+
"prettier-package-json": "^2.6.0",
|
|
84
|
+
"react": "^16.14.0",
|
|
85
|
+
"react-dom": "^16.14.0",
|
|
86
|
+
"regenerator-runtime": "^0.13.9",
|
|
87
|
+
"rimraf": "^3.0.2",
|
|
88
|
+
"typescript": "^4.4.2"
|
|
89
|
+
},
|
|
90
|
+
"keywords": [
|
|
91
|
+
"d3",
|
|
92
|
+
"react",
|
|
93
|
+
"word-cloud"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import ReactFauxDom from 'react-faux-dom';
|
|
3
|
+
import cloud from 'd3-cloud';
|
|
4
|
+
import isDeepEqual from 'react-fast-compare';
|
|
5
|
+
import { BaseType, ValueFn, select } from 'd3-selection';
|
|
6
|
+
import { scaleOrdinal } from 'd3-scale';
|
|
7
|
+
import { schemeCategory10 } from 'd3-scale-chromatic';
|
|
8
|
+
|
|
9
|
+
interface Datum {
|
|
10
|
+
text: string;
|
|
11
|
+
value: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Word extends cloud.Word {
|
|
15
|
+
text: string;
|
|
16
|
+
value: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type WordCloudProps = {
|
|
20
|
+
data: Datum[];
|
|
21
|
+
width?: number;
|
|
22
|
+
height?: number;
|
|
23
|
+
font?: string | ((word: Word, index: number) => string);
|
|
24
|
+
fontStyle?: string | ((word: Word, index: number) => string);
|
|
25
|
+
fontWeight?:
|
|
26
|
+
| string
|
|
27
|
+
| number
|
|
28
|
+
| ((word: Word, index: number) => string | number);
|
|
29
|
+
fontSize?: number | ((word: Word, index: number) => number);
|
|
30
|
+
rotate?: number | ((word: Word, index: number) => number);
|
|
31
|
+
spiral?:
|
|
32
|
+
| 'archimedean'
|
|
33
|
+
| 'rectangular'
|
|
34
|
+
| ((size: [number, number]) => (t: number) => [number, number]);
|
|
35
|
+
padding?: number | ((word: Word, index: number) => number);
|
|
36
|
+
random?: () => number;
|
|
37
|
+
fill?: ValueFn<SVGTextElement, Word, string>;
|
|
38
|
+
onWordClick?: (this: BaseType, event: any, d: Word) => void;
|
|
39
|
+
onWordMouseOver?: (this: BaseType, event: any, d: Word) => void;
|
|
40
|
+
onWordMouseOut?: (this: BaseType, event: any, d: Word) => void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const defaultScaleOrdinal = scaleOrdinal(schemeCategory10);
|
|
44
|
+
|
|
45
|
+
function WordCloud({
|
|
46
|
+
data,
|
|
47
|
+
width = 700,
|
|
48
|
+
height = 600,
|
|
49
|
+
font = 'serif',
|
|
50
|
+
fontStyle = 'normal',
|
|
51
|
+
fontWeight = 'normal',
|
|
52
|
+
fontSize = (d) => Math.sqrt(d.value),
|
|
53
|
+
// eslint-disable-next-line no-bitwise
|
|
54
|
+
rotate = () => (~~(Math.random() * 6) - 3) * 30,
|
|
55
|
+
spiral = 'archimedean',
|
|
56
|
+
padding = 1,
|
|
57
|
+
random = Math.random,
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
59
|
+
// @ts-ignore The ordinal function should accept number
|
|
60
|
+
fill = (_, i) => defaultScaleOrdinal(i),
|
|
61
|
+
onWordClick,
|
|
62
|
+
onWordMouseOver,
|
|
63
|
+
onWordMouseOut,
|
|
64
|
+
}: WordCloudProps) {
|
|
65
|
+
const elementRef = useRef<ReactFauxDom.Element>();
|
|
66
|
+
|
|
67
|
+
if (!elementRef.current) {
|
|
68
|
+
elementRef.current = ReactFauxDom.createElement('div');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const el = elementRef.current;
|
|
72
|
+
|
|
73
|
+
// clear old words
|
|
74
|
+
select(el).selectAll('*').remove();
|
|
75
|
+
|
|
76
|
+
// render based on new data
|
|
77
|
+
const layout = cloud<Word>()
|
|
78
|
+
.words(data)
|
|
79
|
+
.size([width, height])
|
|
80
|
+
.font(font)
|
|
81
|
+
.fontStyle(fontStyle)
|
|
82
|
+
.fontWeight(fontWeight)
|
|
83
|
+
.fontSize(fontSize)
|
|
84
|
+
.rotate(rotate)
|
|
85
|
+
.spiral(spiral)
|
|
86
|
+
.padding(padding)
|
|
87
|
+
.random(random)
|
|
88
|
+
.on('end', (words) => {
|
|
89
|
+
const [w, h] = layout.size();
|
|
90
|
+
|
|
91
|
+
const texts = select(el)
|
|
92
|
+
.append('svg')
|
|
93
|
+
.attr('viewBox', `0 0 ${w} ${h}`)
|
|
94
|
+
.attr('preserveAspectRatio', 'xMinYMin meet')
|
|
95
|
+
.append('g')
|
|
96
|
+
.attr('transform', `translate(${w / 2},${h / 2})`)
|
|
97
|
+
.selectAll('text')
|
|
98
|
+
.data(words)
|
|
99
|
+
.enter()
|
|
100
|
+
.append('text')
|
|
101
|
+
.style(
|
|
102
|
+
'font-family',
|
|
103
|
+
((d) => d.font) as ValueFn<SVGTextElement, Word, string>
|
|
104
|
+
)
|
|
105
|
+
.style(
|
|
106
|
+
'font-style',
|
|
107
|
+
((d) => d.style) as ValueFn<SVGTextElement, Word, string>
|
|
108
|
+
)
|
|
109
|
+
.style(
|
|
110
|
+
'font-weight',
|
|
111
|
+
((d) => d.weight) as ValueFn<SVGTextElement, Word, string | number>
|
|
112
|
+
)
|
|
113
|
+
.style(
|
|
114
|
+
'font-size',
|
|
115
|
+
((d) => `${d.size}px`) as ValueFn<SVGTextElement, Word, string>
|
|
116
|
+
)
|
|
117
|
+
.style('fill', fill)
|
|
118
|
+
.attr('text-anchor', 'middle')
|
|
119
|
+
.attr('transform', (d) => `translate(${[d.x, d.y]})rotate(${d.rotate})`)
|
|
120
|
+
.text((d) => d.text);
|
|
121
|
+
|
|
122
|
+
if (onWordClick) {
|
|
123
|
+
texts.on('click', onWordClick);
|
|
124
|
+
}
|
|
125
|
+
if (onWordMouseOver) {
|
|
126
|
+
texts.on('mouseover', onWordMouseOver);
|
|
127
|
+
}
|
|
128
|
+
if (onWordMouseOut) {
|
|
129
|
+
texts.on('mouseout', onWordMouseOut);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
layout.start();
|
|
134
|
+
|
|
135
|
+
return el.toReact();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export default React.memo(WordCloud, isDeepEqual);
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
RenderResult,
|
|
4
|
+
fireEvent,
|
|
5
|
+
render,
|
|
6
|
+
screen,
|
|
7
|
+
} from '@testing-library/react';
|
|
8
|
+
|
|
9
|
+
import WordCloud, { Word } from '../WordCloud';
|
|
10
|
+
|
|
11
|
+
const data = [
|
|
12
|
+
{ text: 'Hey', value: 1 },
|
|
13
|
+
{ text: 'Ok', value: 5 },
|
|
14
|
+
{ text: 'Cool', value: 10 },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
function renderInStrictMode(ui: React.ReactElement): RenderResult {
|
|
18
|
+
return render(ui, { wrapper: React.StrictMode });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
it('should render words', async () => {
|
|
22
|
+
renderInStrictMode(<WordCloud data={data} />);
|
|
23
|
+
|
|
24
|
+
await screen.findByText('Hey');
|
|
25
|
+
|
|
26
|
+
expect(screen.getByText('Hey')).toBeInTheDocument();
|
|
27
|
+
expect(screen.getByText('Ok')).toBeInTheDocument();
|
|
28
|
+
expect(screen.getByText('Cool')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should render correct words after re-render', async () => {
|
|
32
|
+
const { rerender } = renderInStrictMode(<WordCloud data={data} />);
|
|
33
|
+
|
|
34
|
+
const newData = [
|
|
35
|
+
{ text: 'New', value: 1 },
|
|
36
|
+
{ text: 'World', value: 5 },
|
|
37
|
+
];
|
|
38
|
+
rerender(<WordCloud data={newData} />);
|
|
39
|
+
|
|
40
|
+
await screen.findByText('New');
|
|
41
|
+
|
|
42
|
+
expect(screen.queryByText('Hey')).not.toBeInTheDocument();
|
|
43
|
+
expect(screen.queryByText('Ok')).not.toBeInTheDocument();
|
|
44
|
+
expect(screen.queryByText('Cool')).not.toBeInTheDocument();
|
|
45
|
+
|
|
46
|
+
expect(screen.getByText('New')).toBeInTheDocument();
|
|
47
|
+
expect(screen.getByText('World')).toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should render with custom font', async () => {
|
|
51
|
+
const font = 'sans-serif';
|
|
52
|
+
|
|
53
|
+
renderInStrictMode(<WordCloud data={data} font={font} />);
|
|
54
|
+
|
|
55
|
+
await screen.findByText('Hey');
|
|
56
|
+
|
|
57
|
+
expect(screen.getByText('Hey')).toHaveStyle('font-family: sans-serif');
|
|
58
|
+
expect(screen.getByText('Ok')).toHaveStyle('font-family: sans-serif');
|
|
59
|
+
expect(screen.getByText('Cool')).toHaveStyle('font-family: sans-serif');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should render with custom fontStyle', async () => {
|
|
63
|
+
const fontStyle = 'bold';
|
|
64
|
+
|
|
65
|
+
renderInStrictMode(<WordCloud data={data} fontStyle={fontStyle} />);
|
|
66
|
+
|
|
67
|
+
await screen.findByText('Hey');
|
|
68
|
+
|
|
69
|
+
expect(screen.getByText('Hey')).toHaveStyle('font-style: bold');
|
|
70
|
+
expect(screen.getByText('Ok')).toHaveStyle('font-style: bold');
|
|
71
|
+
expect(screen.getByText('Cool')).toHaveStyle('font-style: bold');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should render with custom fontWeight', async () => {
|
|
75
|
+
const fontWeight = 900;
|
|
76
|
+
|
|
77
|
+
renderInStrictMode(<WordCloud data={data} fontWeight={fontWeight} />);
|
|
78
|
+
|
|
79
|
+
await screen.findByText('Hey');
|
|
80
|
+
|
|
81
|
+
expect(screen.getByText('Hey')).toHaveStyle('font-weight: 900');
|
|
82
|
+
expect(screen.getByText('Ok')).toHaveStyle('font-weight: 900');
|
|
83
|
+
expect(screen.getByText('Cool')).toHaveStyle('font-weight: 900');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should render with custom fontSize', async () => {
|
|
87
|
+
const fontSize = (d: Word) => d.value * 2;
|
|
88
|
+
|
|
89
|
+
renderInStrictMode(<WordCloud data={data} fontSize={fontSize} />);
|
|
90
|
+
|
|
91
|
+
await screen.findByText('Hey');
|
|
92
|
+
|
|
93
|
+
expect(screen.getByText('Hey')).toHaveStyle('font-size: 2px');
|
|
94
|
+
expect(screen.getByText('Ok')).toHaveStyle('font-size: 10px');
|
|
95
|
+
expect(screen.getByText('Cool')).toHaveStyle('font-size: 20px');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should render with custom rotate', async () => {
|
|
99
|
+
const rotate = (_: Word, i: number) => (i + 1) * 30;
|
|
100
|
+
|
|
101
|
+
renderInStrictMode(<WordCloud data={data} rotate={rotate} />);
|
|
102
|
+
|
|
103
|
+
await screen.findByText('Hey');
|
|
104
|
+
|
|
105
|
+
expect(screen.getByText('Hey')).toHaveAttribute(
|
|
106
|
+
'transform',
|
|
107
|
+
expect.stringContaining('rotate(30)')
|
|
108
|
+
);
|
|
109
|
+
expect(screen.getByText('Ok')).toHaveAttribute(
|
|
110
|
+
'transform',
|
|
111
|
+
expect.stringContaining('rotate(60)')
|
|
112
|
+
);
|
|
113
|
+
expect(screen.getByText('Cool')).toHaveAttribute(
|
|
114
|
+
'transform',
|
|
115
|
+
expect.stringContaining('rotate(90)')
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should render with custom fill', async () => {
|
|
120
|
+
const fill = () => '#000000';
|
|
121
|
+
|
|
122
|
+
renderInStrictMode(<WordCloud data={data} fill={fill} />);
|
|
123
|
+
|
|
124
|
+
await screen.findByText('Hey');
|
|
125
|
+
|
|
126
|
+
expect(screen.getByText('Hey')).toHaveStyle('fill: #000000');
|
|
127
|
+
expect(screen.getByText('Ok')).toHaveStyle('fill: #000000');
|
|
128
|
+
expect(screen.getByText('Cool')).toHaveStyle('fill: #000000');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should support click handler', async () => {
|
|
132
|
+
const onWordClick = jest.fn();
|
|
133
|
+
|
|
134
|
+
renderInStrictMode(<WordCloud data={data} onWordClick={onWordClick} />);
|
|
135
|
+
|
|
136
|
+
await screen.findByText('Hey');
|
|
137
|
+
|
|
138
|
+
fireEvent.click(screen.getByText('Hey'));
|
|
139
|
+
|
|
140
|
+
expect(onWordClick).toBeCalled();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should support mouse over handler', async () => {
|
|
144
|
+
const onWordMouseOver = jest.fn();
|
|
145
|
+
|
|
146
|
+
renderInStrictMode(
|
|
147
|
+
<WordCloud data={data} onWordMouseOver={onWordMouseOver} />
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
await screen.findByText('Hey');
|
|
151
|
+
|
|
152
|
+
fireEvent.mouseOver(screen.getByText('Hey'));
|
|
153
|
+
|
|
154
|
+
expect(onWordMouseOver).toBeCalled();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should support mouse out handler', async () => {
|
|
158
|
+
const onWordMouseOut = jest.fn();
|
|
159
|
+
|
|
160
|
+
renderInStrictMode(<WordCloud data={data} onWordMouseOut={onWordMouseOut} />);
|
|
161
|
+
|
|
162
|
+
await screen.findByText('Hey');
|
|
163
|
+
|
|
164
|
+
fireEvent.mouseOut(screen.getByText('Hey'));
|
|
165
|
+
|
|
166
|
+
expect(onWordMouseOut).toBeCalled();
|
|
167
|
+
});
|
package/src/index.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["./test/jest-setup.ts"],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "es2018",
|
|
5
|
+
"module": "es2015",
|
|
6
|
+
"lib": ["es2018", "dom"],
|
|
7
|
+
"jsx": "react",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"isolatedModules": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"noImplicitAny": true,
|
|
12
|
+
"strictNullChecks": true,
|
|
13
|
+
"strictFunctionTypes": true,
|
|
14
|
+
"strictBindCallApply": true,
|
|
15
|
+
"strictPropertyInitialization": true,
|
|
16
|
+
"noImplicitThis": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noImplicitReturns": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
24
|
+
"moduleResolution": "node",
|
|
25
|
+
"esModuleInterop": true,
|
|
26
|
+
"skipLibCheck": true,
|
|
27
|
+
"forceConsistentCasingInFileNames": true
|
|
28
|
+
}
|
|
29
|
+
}
|