-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Andrew McKendrick
authored and
Andrew McKendrick
committed
Dec 2, 2020
1 parent
49d73e2
commit 2737a58
Showing
13 changed files
with
310 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Tag Cloud | ||
|
||
Displays a cloud of tags (aka tag cloud). | ||
|
||
## Examples | ||
|
||
![Tag Cloud](tag-cloud.png) | ||
|
||
``` html | ||
<TagCloud tags={[{ tag: 'dogs', value: 10 }, { tag: 'cats', value: 20 }, { tag: 'birds', value: 4 }]} className="tenrec" /> | ||
``` | ||
|
||
## API | ||
|
||
| Name | Type | Default | Description | | ||
|---|---|---|---| | ||
| tags | Array | | Required. An array containing the tags that appear within the cloud. See below for more details. | | ||
| className | String | null | Optional. The name of an additional class to apply to the component. | | ||
|
||
### Structure of Tags | ||
|
||
The tags attribute is an array of zero or more tag elements. Each tag has the following structure: | ||
|
||
``` json | ||
{ | ||
tag: 'name goes here', | ||
value: 10 | ||
} | ||
``` | ||
|
||
The tag property is the name of the tag that will appear within the tag cloud. | ||
The value is the number of that tag used to size the tag in relation to other | ||
tags. For example, if the tag is "Dog" and there are 10 dogs in the house, then | ||
this number would be 10. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,11 @@ | ||
/* eslint-disable react/jsx-filename-extension */ | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import TagCloudTag from './tag-cloud-tag/tag-cloud-tag'; | ||
import TagCloud from './tag-cloud/tag-cloud'; | ||
|
||
ReactDOM.render( | ||
<React.StrictMode> | ||
<TagCloudTag tag="Toronto" size={3} /> | ||
<TagCloudTag tag="Edmonton" size={2} /> | ||
<TagCloudTag tag="New York" size={5} /> | ||
<TagCloud tags={[{ tag: 'Toronto', value: 3 }, { tag: 'Edmonton', value: 100 }]} /> | ||
</React.StrictMode>, | ||
document.getElementById('root'), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/** | ||
* Contains static methods for working with tags and tag clouds. | ||
*/ | ||
class TagCloudHelper { | ||
/** | ||
* Calculates the total value of all tags. | ||
* | ||
* @param {Array} tags An array of tags. | ||
* @example | ||
* // Returns 34 | ||
* calculateTotal([{tag: "dogs", value: 10}, {tag: "cats", value: 20}, | ||
* {tag: "birds", value: 4}]); | ||
* @returns {number} The total value of all tags. | ||
*/ | ||
static calculateTotal(tags) { | ||
if (!tags || !Array.isArray(tags) || !tags.length) { | ||
return 0; | ||
} | ||
|
||
const reducer = (accumulator, currentValue) => ( | ||
Number.isFinite(accumulator) ? accumulator : this.getTagValue(accumulator)) | ||
+ this.getTagValue(currentValue); | ||
|
||
return tags.reduce(reducer); | ||
} | ||
|
||
/** | ||
* Calculates the tag size based on the value of the specified tag and the | ||
* total of all tags. | ||
* | ||
* @param {number} total The total value of all tags. | ||
* @param {number} value The value of the current tag. | ||
* @example | ||
* @returns {number} A value from 1 to 5 indicating the size of the tag in | ||
* relation to other tags. | ||
*/ | ||
static getTagSize(total, value) { | ||
if (!Number.isFinite(total) || !Number.isFinite(value)) { | ||
return 1; | ||
} | ||
|
||
if (value < 0) { | ||
return 1; | ||
} | ||
|
||
if (value > total) { | ||
return 5; | ||
} | ||
|
||
return Math.min(Math.trunc(Math.trunc((value / total) * 100) / 20) + 1, 5); | ||
} | ||
|
||
/** | ||
* Gets the value of the specified tag. | ||
* | ||
* @param {object} tag The tag. | ||
* @example | ||
* // Returns 10 | ||
* getTagValue({ tag: "dog", value: 10 }) | ||
* @returns {number} The value of the tag. | ||
*/ | ||
static getTagValue(tag) { | ||
if (!tag || !Number.isFinite(tag.value)) { | ||
return 0; | ||
} | ||
|
||
return tag.value; | ||
} | ||
} | ||
|
||
export default TagCloudHelper; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import TagCloudHelper from './tag-cloud-helper'; | ||
|
||
describe('calculateTotal', () => { | ||
it('returns zero if the tags argument is undefined or null or not an array.', () => { | ||
expect(TagCloudHelper.calculateTotal(undefined)).toBe(0); | ||
expect(TagCloudHelper.calculateTotal(null)).toBe(0); | ||
expect(TagCloudHelper.calculateTotal('Not an array.')).toBe(0); | ||
}); | ||
|
||
it('returns zero if the tags argument is an empty array.', () => { | ||
expect(TagCloudHelper.calculateTotal([])).toBe(0); | ||
}); | ||
|
||
it('calculates the total of all tag values.', () => { | ||
expect(TagCloudHelper.calculateTotal([ | ||
{ tag: 'dogs', value: 10 }, | ||
{ tag: 'cats', value: 20 }, | ||
{ tag: 'birds', value: 4 }, | ||
])).toBe(34); | ||
}); | ||
|
||
it('does not include an element that does not have a value in the result.', () => { | ||
expect(TagCloudHelper.calculateTotal([ | ||
{ tag: 'dogs', value: 10 }, | ||
{ tag: 'cats' }, | ||
{ name: 'invalid' }, | ||
{ tag: 'birds', other: 4 }, | ||
])).toBe(10); | ||
}); | ||
}); | ||
|
||
describe('getTagSize', () => { | ||
it('returns 1 if the total is null, undefined, or not a number.', () => { | ||
expect(TagCloudHelper.getTagSize(null, 0)).toBe(1); | ||
expect(TagCloudHelper.getTagSize(undefined, 1)).toBe(1); | ||
expect(TagCloudHelper.getTagSize('1', 19)).toBe(1); | ||
}); | ||
|
||
it('returns 1 if the value is null, undefined, or not a number.', () => { | ||
expect(TagCloudHelper.getTagSize(100, null)).toBe(1); | ||
expect(TagCloudHelper.getTagSize(100, undefined)).toBe(1); | ||
expect(TagCloudHelper.getTagSize(100, '19')).toBe(1); | ||
}); | ||
|
||
it('returns 1 if the the value is between 0 and 19, percentage wise.', () => { | ||
expect(TagCloudHelper.getTagSize(100, 0)).toBe(1); | ||
expect(TagCloudHelper.getTagSize(100, 1)).toBe(1); | ||
expect(TagCloudHelper.getTagSize(100, 19)).toBe(1); | ||
}); | ||
|
||
it('returns 2 if the the value is between 20 and 39, percentage wise.', () => { | ||
expect(TagCloudHelper.getTagSize(100, 20)).toBe(2); | ||
expect(TagCloudHelper.getTagSize(100, 39)).toBe(2); | ||
}); | ||
|
||
it('returns 3 if the the value is between 40 and 59, percentage wise.', () => { | ||
expect(TagCloudHelper.getTagSize(100, 40)).toBe(3); | ||
expect(TagCloudHelper.getTagSize(100, 59)).toBe(3); | ||
}); | ||
|
||
it('returns 4 if the the value is between 60 and 79, percentage wise.', () => { | ||
expect(TagCloudHelper.getTagSize(100, 60)).toBe(4); | ||
expect(TagCloudHelper.getTagSize(100, 79)).toBe(4); | ||
}); | ||
|
||
it('returns 5 if the the value is between 80 and 100, percentage wise.', () => { | ||
expect(TagCloudHelper.getTagSize(100, 80)).toBe(5); | ||
expect(TagCloudHelper.getTagSize(100, 100)).toBe(5); | ||
}); | ||
|
||
it('returns 5 if the the value is greater than the total.', () => { | ||
expect(TagCloudHelper.getTagSize(100, 101)).toBe(5); | ||
}); | ||
|
||
it('returns 1 if the the value is less than zero.', () => { | ||
expect(TagCloudHelper.getTagSize(100, -1)).toBe(1); | ||
}); | ||
}); | ||
|
||
describe('getTagValue', () => { | ||
it('returns zero if the tag argument is undefined or null.', () => { | ||
expect(TagCloudHelper.getTagValue(undefined)).toBe(0); | ||
expect(TagCloudHelper.getTagValue(null)).toBe(0); | ||
}); | ||
|
||
it('return zero if there is no value property on the specified tag.', () => { | ||
expect(TagCloudHelper.getTagValue({ tag: 'Cat' })).toBe(0); | ||
}); | ||
|
||
it('return the value of the value property on the specified tag.', () => { | ||
expect(TagCloudHelper.getTagValue({ tag: 'Cat', value: 10 })).toBe(10); | ||
}); | ||
}); |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import classnames from 'classnames'; | ||
import TagCloudTag from '../tag-cloud-tag/tag-cloud-tag'; | ||
import './tag-cloud.css'; | ||
import TagCloudHelper from './tag-cloud-helper'; | ||
|
||
/** | ||
* Represents a tag cloud or a group of tags that are sized based on how common | ||
* those tags are in relation to other tags. For example, a tag cloud that is | ||
* based on the type of pets may have 10 dogs, 20 cats, and 4 birds. Three tags | ||
* will be displayed, one for Dogs, one for Cats, and one for Birds. The | ||
* font-size of the Dogs tag will appear smaller than Cats, but larger than | ||
* Birds. | ||
* | ||
* @param {*} props The properties of the component. | ||
* @example | ||
* // Displays the component. | ||
* <TagCloud tags={[{tag: "dogs", value: 10}, {tag: "cats", value: 20}, | ||
* {tag: "birds", value: 4}]} /> | ||
* @returns {HTMLElement} An HTML element representing the component. | ||
*/ | ||
function TagCloud(props) { | ||
const { className, tags } = props; | ||
|
||
// Calculate the total of all tag values. This will be used to calculate the | ||
// percentage of tag in relation to other tags. | ||
const total = TagCloudHelper.calculateTotal(tags); | ||
|
||
const elements = tags.map((tag) => { | ||
const size = TagCloudHelper.getTagSize(total, tag.value); | ||
|
||
return <TagCloudTag key={tag.tag} size={size} tag={tag.tag} />; | ||
}); | ||
|
||
return ( | ||
<div className={classnames('tag-cloud', className)}> | ||
{elements} | ||
</div> | ||
); | ||
} | ||
|
||
export default TagCloud; | ||
|
||
TagCloud.propTypes = { | ||
/** Specifies an array of JSON key/value pairs, one pair for each tag with the | ||
* name of the tag and popularity of the tag. For example: { tag: 'birds', value: 4 } */ | ||
tags: PropTypes.arrayOf(PropTypes.object).isRequired, | ||
|
||
/** The class to apply to the resulting element. */ | ||
className: PropTypes.string, | ||
}; | ||
|
||
TagCloud.defaultProps = { | ||
className: null, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import React from 'react'; | ||
import { render, unmountComponentAtNode } from 'react-dom'; | ||
import { act } from 'react-dom/test-utils'; | ||
import TagCloud from './tag-cloud'; | ||
|
||
let container = null; | ||
beforeEach(() => { | ||
container = document.createElement('div'); | ||
document.body.appendChild(container); | ||
}); | ||
|
||
afterEach(() => { | ||
unmountComponentAtNode(container); | ||
container.remove(); | ||
container = null; | ||
}); | ||
|
||
it('does not display any additional class name when no className attribute is specified.', () => { | ||
act(() => { | ||
render(<TagCloud tags={[{ tag: 'dogs', value: 10 }, { tag: 'cats', value: 20 }, { tag: 'birds', value: 4 }]} />, container); | ||
}); | ||
expect(container.querySelector('div:first-child').getAttribute('class')).toBe('tag-cloud'); | ||
}); | ||
|
||
it('includes the additional class name when a className attribute is specified.', () => { | ||
act(() => { | ||
render(<TagCloud tags={[{ tag: 'dogs', value: 10 }, { tag: 'cats', value: 20 }, { tag: 'birds', value: 4 }]} className="example" />, container); | ||
}); | ||
expect(container.querySelector('div:first-child').getAttribute('class')).toBe('tag-cloud example'); | ||
}); | ||
|
||
it('displays the same number of tags that are passed in.', () => { | ||
act(() => { | ||
render(<TagCloud tags={[{ tag: 'dogs', value: 10 }, { tag: 'cats', value: 20 }, { tag: 'birds', value: 4 }]} />, container); | ||
}); | ||
expect(container.querySelectorAll('div[class*="tag-cloud-tag"]').length).toBe(3); | ||
}); | ||
|
||
it('displays correct size of each tag.', () => { | ||
act(() => { | ||
render(<TagCloud tags={[{ tag: 'dogs', value: 10 }, { tag: 'cats', value: 20 }, { tag: 'birds', value: 4 }]} />, container); | ||
}); | ||
expect(container.querySelectorAll('div[class*="tag-cloud-tag"')[0].getAttribute('class')).toContain('2'); | ||
expect(container.querySelectorAll('div[class*="tag-cloud-tag"')[1].getAttribute('class')).toContain('3'); | ||
expect(container.querySelectorAll('div[class*="tag-cloud-tag"')[2].getAttribute('class')).toContain('1'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters