diff --git a/app/scripts/components/world-map.js b/app/scripts/components/world-map.js
new file mode 100644
index 000000000..93e4a3238
--- /dev/null
+++ b/app/scripts/components/world-map.js
@@ -0,0 +1,87 @@
+import React, {Component, PropTypes} from 'react'
+import d3 from 'd3'
+import topojson from 'topojson'
+import {AutoSizer} from 'react-virtualized'
+import ReactFauxDOM from 'react-faux-dom'
+
+import worldData from '../../data/world.json'
+
+export default class WorldMap extends Component {
+ static propTypes = {
+ coordinates: PropTypes.array.isRequired
+ };
+
+ _renderMap = ({width, height}) => {
+ const projection = d3.geo.equirectangular()
+ .scale(height / Math.PI)
+ .translate([width / 2, height / 2])
+ .precision(0.1)
+
+ const path = d3.geo.path()
+ .projection(projection)
+
+ const graticule = d3.geo.graticule()
+
+ const el = d3.select(ReactFauxDOM.createElement('svg'))
+ .attr('width', width)
+ .attr('height', height)
+
+ // Fill Pattern
+ el.append('defs')
+ .append('pattern')
+ .attr('id', 'gridpattern')
+ .attr('x', 0)
+ .attr('y', 0)
+ .attr('width', 4)
+ .attr('height', 4)
+ .attr('patternUnits', 'userSpaceOnUse')
+ .append('circle')
+ .attr('cx', 0)
+ .attr('cy', 0)
+ .attr('r', 1)
+ .attr('style', 'stroke: none; fill: rgba(255, 255, 255, 0.7)')
+
+ el.append('path')
+ .datum(graticule)
+ .attr('class', 'graticule')
+ .attr('d', path)
+
+ el.insert('path', '.graticule')
+ .datum(topojson.feature(worldData, worldData.objects.land))
+ .attr('d', path)
+ .attr('fill', 'url(#gridpattern)')
+
+ el.insert('path', '.graticule')
+ .datum(topojson.mesh(
+ worldData,
+ worldData.objects.countries,
+ (a, b) => a !== b
+ ))
+ .attr('d', path)
+ .attr('fill', 'none')
+ .attr('stroke', 'none')
+ .attr('stroke-width', '0.5px')
+
+ el.append('path')
+ .datum({
+ type: 'MultiPoint',
+ coordinates: this.props.coordinates
+ })
+ .attr('d', path.pointRadius((d) => 8))
+ .attr('class', 'world-locations-base')
+
+ el.append('path')
+ .datum({
+ type: 'MultiPoint',
+ coordinates: this.props.coordinates
+ })
+ .attr('d', path.pointRadius((d) => 2))
+ .attr('class', 'world-locations-center')
+
+ return el.node().toReact()
+ };
+
+ render () {
+ return {this._renderMap}
+ }
+}
diff --git a/app/scripts/components/world.js b/app/scripts/components/world.js
index 4db1e5981..40423b6a1 100644
--- a/app/scripts/components/world.js
+++ b/app/scripts/components/world.js
@@ -1,11 +1,6 @@
import React, {Component, PropTypes} from 'react'
-import d3 from 'd3'
-import ReactFauxDOM from 'react-faux-dom'
-import topojson from 'topojson'
-import {AutoSizer} from 'react-virtualized'
import {map, isEqual} from 'lodash'
-
-import worldData from '../../data/world.json'
+import WorldMap from './world-map'
export default class World extends Component {
static propTypes = {
@@ -18,93 +13,18 @@ export default class World extends Component {
locations: {}
};
- _renderMap = (locations) => {
- return ({width, height}) => {
- const projection = d3.geo.equirectangular()
- .scale(height / Math.PI)
- .translate([width / 2, height / 2])
- .precision(0.1)
-
- const path = d3.geo.path()
- .projection(projection)
-
- const graticule = d3.geo.graticule()
-
- const el = d3.select(ReactFauxDOM.createElement('svg'))
- .attr('width', width)
- .attr('height', height)
-
- // Fill Pattern
- el.append('defs')
- .append('pattern')
- .attr('id', 'gridpattern')
- .attr('x', 0)
- .attr('y', 0)
- .attr('width', 4)
- .attr('height', 4)
- .attr('patternUnits', 'userSpaceOnUse')
- .append('circle')
- .attr('cx', 0)
- .attr('cy', 0)
- .attr('r', 1)
- .attr('style', 'stroke: none; fill: rgba(255, 255, 255, 0.7)')
-
- el.append('path')
- .datum(graticule)
- .attr('class', 'graticule')
- .attr('d', path)
-
- el.insert('path', '.graticule')
- .datum(topojson.feature(worldData, worldData.objects.land))
- .attr('d', path)
- .attr('fill', 'url(#gridpattern)')
-
- el.insert('path', '.graticule')
- .datum(topojson.mesh(
- worldData,
- worldData.objects.countries,
- (a, b) => a !== b
- ))
- .attr('d', path)
- .attr('fill', 'none')
- .attr('stroke', 'none')
- .attr('stroke-width', '0.5px')
-
- el.append('path')
- .datum({
- type: 'MultiPoint',
- coordinates: locations
- })
- .attr('d', path.pointRadius((d) => 8))
- .attr('class', 'world-locations-base')
-
- el.append('path')
- .datum({
- type: 'MultiPoint',
- coordinates: locations
- })
- .attr('d', path.pointRadius((d) => 2))
- .attr('class', 'world-locations-center')
-
- return el.node().toReact()
- }
- };
-
shouldComponentUpdate (nextProps) {
return !isEqual(nextProps, this.props)
}
render () {
- // Draw peer locations
- const locations = map(this.props.locations, ({lon, lat}) => {
+ const coordinates = map(this.props.locations, ({lon, lat}) => {
return [lon, lat]
})
return (
-
- {this._renderMap(locations)}
-
+
{this.props.peersCount}
Peers
diff --git a/test/components/world-map.spec.js b/test/components/world-map.spec.js
new file mode 100644
index 000000000..20f08a502
--- /dev/null
+++ b/test/components/world-map.spec.js
@@ -0,0 +1,24 @@
+import {expect} from 'chai'
+import {shallow, render} from 'enzyme'
+import React from 'react'
+
+import WorldMap from '../../app/scripts/components/world-map'
+
+describe('WorldMap', () => {
+ const c = [[12, 14]]
+
+ it('should render an svg', () => {
+ const el = render(
)
+ expect(el.find('svg').length).to.equal(1)
+ })
+
+ it('should have the correct coordiates', () => {
+ const el = shallow()
+ const p = el.instance().props
+
+ c.forEach((coordinates, i) => {
+ expect(coordinates[0]).to.equal(p.coordinates[i][0])
+ expect(coordinates[1]).to.equal(p.coordinates[i][1])
+ })
+ })
+})
diff --git a/test/components/world.spec.js b/test/components/world.spec.js
new file mode 100644
index 000000000..22f0a7ff6
--- /dev/null
+++ b/test/components/world.spec.js
@@ -0,0 +1,22 @@
+import {expect} from 'chai'
+import {shallow} from 'enzyme'
+import React from 'react'
+
+import World from '../../app/scripts/components/world'
+
+describe('World', () => {
+ it('should have the correct defaults', () => {
+ const el = shallow()
+ expect(el.find('WorldMap').length).to.equal(1)
+
+ var inst = el.instance()
+ expect(inst.props.peersCount).to.equal(0)
+ expect(Object.keys(inst.props.locations).length)
+ .to.equal(0)
+ })
+
+ it('should set peersCount', () => {
+ const el = shallow()
+ expect(el.find('.counter').node.props.children).to.equal(1337)
+ })
+})