Add MapChart widget
Using TopoJSON [1].
The chart component is build around a convention suggested by d3 author
Mike Bostok [2].
[1] https://github.com/mbostock/topojson
[2] http://bost.ocks.org/mike/chart/
Change-Id: I2b8aefae338f9b24b71d0f2c679993e26dabd6cf
diff --git a/bundles/org.eclipse.rap.addons.chart/js/chart/chart.js b/bundles/org.eclipse.rap.addons.chart/js/chart/chart.js
index e991a9e..b06646b 100644
--- a/bundles/org.eclipse.rap.addons.chart/js/chart/chart.js
+++ b/bundles/org.eclipse.rap.addons.chart/js/chart/chart.js
@@ -69,12 +69,15 @@
return element;
},
- notifySelection: function( index, detail ) {
+ notifySelection: function( index, detail, text ) {
var remoteObject = rap.getRemoteObject( this );
var params = { "index": index };
if( arguments.length > 1 ) {
params.detail = detail;
}
+ if( arguments.length > 2 ) {
+ params.text = text;
+ }
remoteObject.notify( "Selection", params );
},
diff --git a/bundles/org.eclipse.rap.addons.chart/js/chart/topojson/topojson-world.js b/bundles/org.eclipse.rap.addons.chart/js/chart/topojson/topojson-world.js
new file mode 100644
index 0000000..b7e4bab
--- /dev/null
+++ b/bundles/org.eclipse.rap.addons.chart/js/chart/topojson/topojson-world.js
@@ -0,0 +1,195 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * EclipseSource - initial API and implementation
+ ******************************************************************************/
+
+/*global topojson:false */
+
+rwt.chart.register( "topojson-world", function( widget ) {
+
+ var _dataPath;
+ var _width;
+ var _height;
+ var _world;
+ var _path;
+ var _showGraticule;
+ var _toolTip;
+ var _scaleFactor = 1;
+ var _center = [ 0, 0 ];
+ var _countries = {};
+ var _colors = [];
+
+ var chart = function( svg, widget ) {
+ if( !_world ) {
+ if( _dataPath ) {
+ createToolTip( widget._element );
+ load( svg );
+ }
+ } else {
+ update( svg );
+ }
+ };
+
+ chart.dataPath = function( path ) {
+ _dataPath = path;
+ return chart;
+ };
+
+ chart.width = function( width ) {
+ _width = width;
+ return chart;
+ };
+
+ chart.height = function( height ) {
+ _height = height;
+ return chart;
+ };
+
+ chart.countries = function( countries ) {
+ _countries = countries;
+ return chart;
+ };
+
+ chart.colors = function( colors ) {
+ _colors = colors;
+ return chart;
+ };
+
+ chart.showGraticule = function( show ) {
+ _showGraticule = show;
+ return chart;
+ };
+
+ chart.scaleFactor = function( factor ) {
+ _scaleFactor = factor;
+ return chart;
+ };
+
+ chart.center = function( center ) {
+ _center = center;
+ return chart;
+ };
+
+ function createToolTip( parentElement ) {
+ if( !_toolTip ) {
+ _toolTip = d3.select( parentElement ).append( "div" ).attr( "class", "tooltip hidden" );
+ }
+ }
+
+ function load( svg ) {
+ d3.json( _dataPath, function( error, world ) {
+ if( error ) {
+ throw error;
+ }
+ _world = world;
+ update( svg );
+ });
+ }
+
+ function update( svg ) {
+ var countries = topojson.feature( _world, _world.objects.countries );
+ calculateColorIndices( countries.features );
+ updateProjection( countries );
+ updateGraticule( svg );
+ svg.selectAll( ".country" )
+ .data( countries.features )
+ .enter()
+ .insert( "path", ".graticule" )
+ .attr( "class", "country" )
+ .attr( "id", function( d ) { return d.id; } )
+ .on( "click", function( d ) {
+ widget.notifySelection( d.id, 0, d.properties.code3 );
+ })
+ .on( "mouseover", function( d ) {
+ if( getCountryData( d ) ) {
+ var text = rwt.util.Encoding.escapeText( getCountryData( d ).label );
+ text = rwt.util.Encoding.replaceNewLines( text, "<br/>" );
+ _toolTip.classed( "hidden", false ).html( text );
+ }
+ })
+ .on( "mouseout", function() {
+ _toolTip.classed( "hidden", true );
+ })
+ .on( "mousemove", function( d ) {
+ if( getCountryData( d ) ) {
+ var mouse = d3.mouse( svg.node() ).map( function( d ) { return parseInt( d ); } );
+ var left = mouse[ 0 ] + 10;
+ var top = mouse[ 1 ] + 15;
+ _toolTip.attr( "style", "left:" + left + "px;top:" + top + "px;" );
+ }
+ });
+ svg.selectAll( ".country" )
+ .attr( "d", _path )
+ .style( "fill", fill );
+ }
+
+ function updateProjection( countries ) {
+ var scale = 150;
+ var offset = [ _width / 2, _height / 2 ];
+ var projection = d3.geo.equirectangular()
+ .scale( scale )
+ .translate( offset )
+ .precision( 0.1 );
+ _path = d3.geo.path().projection( projection );
+ var bounds = _path.bounds( countries );
+ var hscale = scale * _width / ( bounds[ 1 ][ 0 ] - bounds[ 0 ][ 0 ] );
+ var vscale = scale * _height / ( bounds[ 1 ][ 1 ] - bounds[ 0 ][ 1 ] );
+ scale = ( hscale < vscale ) ? hscale : vscale;
+ projection = d3.geo.equirectangular()
+ .scale( scale * _scaleFactor )
+ .translate( offset )
+ .precision( 0.1 )
+ .center( _center );
+ _path = _path.projection( projection );
+ }
+
+ function updateGraticule( svg ) {
+ if( _showGraticule ) {
+ if( svg.selectAll( ".graticule" ).size() === 0 ) {
+ var graticule = d3.geo.graticule();
+ svg.append( "path" )
+ .datum( graticule )
+ .attr( "class", "graticule" );
+ svg.append( "path" )
+ .datum( graticule.outline )
+ .attr( "class", "graticule outline" );
+ }
+ svg.selectAll( ".graticule" ).attr( "d", _path );
+ } else {
+ svg.selectAll( ".graticule" ).remove();
+ }
+ }
+
+ function fill( d ) {
+ if( getCountryData( d ) ) {
+ return getCountryData( d ).color;
+ }
+ return _colors[ d.col % _colors.length ];
+ }
+
+ function calculateColorIndices( countries ) {
+ var neighbors = topojson.neighbors( _world.objects.countries.geometries );
+ countries.forEach( function( d, i ) {
+ var getCol = function( n ) {
+ return countries[ n ].col;
+ };
+ d.col = d3.max( neighbors[ i ], getCol ) + 1 | 0;
+ });
+ }
+
+ function getCountryData( d ) {
+ var props = d.properties;
+ return _countries[ d.id ] || _countries[ props.code2 ] || _countries[ props.code3 ];
+ }
+
+ widget._scheduleUpdate();
+
+ return chart;
+
+});
diff --git a/bundles/org.eclipse.rap.addons.chart/src/org/eclipse/rap/addons/chart/Chart.java b/bundles/org.eclipse.rap.addons.chart/src/org/eclipse/rap/addons/chart/Chart.java
index bdab119..d48af17 100644
--- a/bundles/org.eclipse.rap.addons.chart/src/org/eclipse/rap/addons/chart/Chart.java
+++ b/bundles/org.eclipse.rap.addons.chart/src/org/eclipse/rap/addons/chart/Chart.java
@@ -77,6 +77,10 @@
if( detail != null ) {
event.detail = detail.asInt();
}
+ JsonValue text = properties.get( "text" );
+ if( text != null ) {
+ event.text = text.asString();
+ }
notifyListeners( SWT.Selection, event );
}
}
diff --git a/bundles/org.eclipse.rap.addons.chart/src/org/eclipse/rap/addons/chart/basic/MapChart.java b/bundles/org.eclipse.rap.addons.chart/src/org/eclipse/rap/addons/chart/basic/MapChart.java
new file mode 100644
index 0000000..e3edc7b
--- /dev/null
+++ b/bundles/org.eclipse.rap.addons.chart/src/org/eclipse/rap/addons/chart/basic/MapChart.java
@@ -0,0 +1,176 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Ralf Sternberg - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.addons.chart.basic;
+
+import static org.eclipse.rap.addons.chart.internal.ColorUtil.toHtmlString;
+import static org.eclipse.rap.rwt.internal.util.ParamCheck.notNullOrEmpty;
+
+import org.eclipse.rap.addons.chart.Chart;
+import org.eclipse.rap.json.JsonArray;
+import org.eclipse.rap.json.JsonObject;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.widgets.Composite;
+
+
+/**
+ * A basic world map chart widget.
+ *
+ * <dl>
+ * <dt><b>Styles:</b></dt>
+ * <dd>none</dd>
+ * <dt><b>Events:</b></dt>
+ * <dd>Selection</dd>
+ * </dl>
+ */
+@SuppressWarnings( "restriction" )
+public class MapChart extends Chart {
+
+ private static final String PROP_TOPOJSON_JS_URL = "org.eclipse.rap.addons.chart.topojsonJsUrl";
+ private static final String DEF_TOPOJSON_JS_URL
+ = "https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.20/topojson.min.js";
+
+ private boolean showGraticule;
+ private double scaleFactor = 1d;
+ private double longitude;
+ private double latitude;
+
+ /**
+ * Creates a new empty WorldMap chart.
+ *
+ * @param parent a composite control which will be the parent of the new instance (cannot be null)
+ * @param style the style of control to construct
+ * @param path a resource path to registered topology geo data file (cannot be null or empty)
+ *
+ */
+ public MapChart( Composite parent, int style, String path ) {
+ super( parent, style, "topojson-world" );
+ notNullOrEmpty( path, "path" );
+ requireJs( System.getProperty( PROP_TOPOJSON_JS_URL, DEF_TOPOJSON_JS_URL ) );
+ requireJs( registerResource( "chart/topojson/topojson-world.js" ) );
+ requireCss( registerResource( "resources/topojson-world.css" ) );
+ setOption( "dataPath", path );
+ }
+
+ /**
+ * Set array of colors, which will be used to fill the countries with.
+ *
+ * @param colors The array with country fill colors.
+ */
+ public void setColors( RGB[] colors ) {
+ checkWidget();
+ JsonArray json = new JsonArray();
+ for( RGB color : colors ) {
+ json.add( toHtmlString( color ) );
+ }
+ setOption( "colors", json );
+ }
+
+ /**
+ * Set whether graticule should be displayed or not. The default is <code>false</code>.
+ *
+ * @param show <code>true</code> to display graticule.
+ */
+ public void setShowGraticule( boolean show ) {
+ checkWidget();
+ if( show != showGraticule ) {
+ showGraticule = show;
+ setOption( "showGraticule", show );
+ }
+ }
+
+ /**
+ * Returns whether graticule is displayed.
+ *
+ * @return <code>true</code> if graticule is displayed.
+ */
+ public boolean getShowGraticule() {
+ checkWidget();
+ return showGraticule;
+ }
+
+ /**
+ * Set map scale factor. The default is 1.
+ *
+ * @param scaleFactor map scale factor.
+ */
+ public void setScaleFactor( double scaleFactor ) {
+ checkWidget();
+ if( scaleFactor <= 0 ) {
+ SWT.error( SWT.ERROR_INVALID_ARGUMENT );
+ }
+ if( scaleFactor != this.scaleFactor ) {
+ this.scaleFactor = scaleFactor;
+ setOption( "scaleFactor", scaleFactor );
+ }
+ }
+
+ /**
+ * Returns map scale factor.
+ *
+ * @return the scale factor.
+ */
+ public double getScaleFactor() {
+ checkWidget();
+ return scaleFactor;
+ }
+
+ /**
+ * Set map projection's center to the specified location.
+ *
+ * @param longitude location longitude in degrees.
+ * @param latitude location latitude in degrees.
+ */
+ public void setCenter( double longitude, double latitude ) {
+ checkWidget();
+ if( longitude != this.longitude || latitude != this.latitude ) {
+ this.longitude = longitude;
+ this.latitude = latitude;
+ setOption( "center", new JsonArray().add( longitude ).add( latitude ) );
+ }
+ }
+
+ /**
+ * Returns map projection's center as two-element array of longitude and latitude in degrees.
+ *
+ * @return the map projection's center.
+ */
+ public double[] getCenter() {
+ checkWidget();
+ return new double[] { longitude, latitude };
+ }
+
+ /**
+ * Sets the map data items to display. Later changes to this list won't be reflected. To change
+ * the chart data, call this method with a new list of items.
+ *
+ * @param items the map data items to display
+ */
+ public void setItems( MapDataItem... items ) {
+ JsonObject values = new JsonObject();
+ for( MapDataItem item : items ) {
+ values.add( item.getCountry(), toJson( item ) );
+ }
+ setOption( "countries", values );
+ }
+
+ private static JsonObject toJson( MapDataItem item ) {
+ JsonObject json = new JsonObject();
+ if( item.text != null ) {
+ json.add( "label", item.text );
+ }
+ if( item.color != null ) {
+ json.add( "color", toHtmlString( item.color ) );
+ }
+ return json;
+ }
+
+}
diff --git a/bundles/org.eclipse.rap.addons.chart/src/org/eclipse/rap/addons/chart/basic/MapDataItem.java b/bundles/org.eclipse.rap.addons.chart/src/org/eclipse/rap/addons/chart/basic/MapDataItem.java
new file mode 100644
index 0000000..a540a83
--- /dev/null
+++ b/bundles/org.eclipse.rap.addons.chart/src/org/eclipse/rap/addons/chart/basic/MapDataItem.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Ralf Sternberg - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.addons.chart.basic;
+
+import org.eclipse.swt.graphics.RGB;
+
+public class MapDataItem extends DataItem {
+
+ private final String country;
+
+ /**
+ * Create a new map data item with the given ISO 3166-1 country code.
+ * All numeric/two-letter/three-letter country codes are supported.
+ *
+ * @param country the country of the item
+ */
+ public MapDataItem( String country ) {
+ this( country, null, null );
+ }
+
+ /**
+ * Create a new map data item with the given ISO 3166-1 country code and text.
+ * All numeric/two-letter/three-letter country codes are supported.
+ *
+ * @param country the country of the item
+ * @param text the tooltip text for the item, or <code>null</code> to omit the tooltip
+ */
+ public MapDataItem( String country, String text ) {
+ this( country, text, null );
+ }
+
+ /**
+ * Create a new map data item with the given ISO 3166-1 country code, text, and color.
+ * All numeric/two-letter/three-letter country codes are supported.
+ *
+ * @param country the country of the item
+ * @param text the tooltip text for the item, or <code>null</code> to omit the tooltip
+ * @param color the color of this item, or <code>null</code> to use the default color
+ */
+ public MapDataItem( String country, String text, RGB color ) {
+ super( 0, text, color );
+ this.country = country;
+ }
+
+ /**
+ * Returns the value of this data item.
+ *
+ * @return the item value
+ */
+ public String getCountry() {
+ return country;
+ }
+
+}
diff --git a/bundles/org.eclipse.rap.addons.chart/src/resources/topojson-world.css b/bundles/org.eclipse.rap.addons.chart/src/resources/topojson-world.css
new file mode 100644
index 0000000..1e797f9
--- /dev/null
+++ b/bundles/org.eclipse.rap.addons.chart/src/resources/topojson-world.css
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * EclipseSource - initial API and implementation
+ ******************************************************************************/
+
+.graticule {
+ fill: none;
+ stroke: #000;
+ stroke-opacity: .1;
+ stroke-width: .5px;
+}
+
+.graticule.outline {
+ stroke: #333;
+ stroke-opacity: 1;
+ stroke-width: 1.5px;
+}
+
+.boundary {
+ fill: none;
+ stroke: #fff;
+ stroke-width: .5px;
+}
+
+.country {
+ fill: #ccc;
+ stroke: #fff;
+ stroke-width: .5px;
+ stroke-linejoin: round;
+}
+
+.country:hover {
+ stroke: #fff;
+ stroke-width: 1.5px;
+ opacity: 0.7;
+}
+
+.tooltip {
+ padding: 10px;
+ background-color: #201F1B;
+ background-image: none;
+ border: none;
+ border-radius: 1px;
+ font: 12px Verdana, "Lucida Sans", Arial, Helvetica, sans-serif;
+ color: #e0e0e0;
+ opacity: 0.9;
+ box-shadow: none;
+ text-align: center;
+ position: absolute;
+}
+
+.hidden {
+ display: none;
+}
diff --git a/examples/org.eclipse.rap.addons.chart.demo/src/org/eclipse/rap/addons/chart/demo/MapSnippet.java b/examples/org.eclipse.rap.addons.chart.demo/src/org/eclipse/rap/addons/chart/demo/MapSnippet.java
new file mode 100644
index 0000000..0e5864f
--- /dev/null
+++ b/examples/org.eclipse.rap.addons.chart.demo/src/org/eclipse/rap/addons/chart/demo/MapSnippet.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Ralf Sternberg - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.addons.chart.demo;
+
+import static org.eclipse.rap.addons.chart.Colors.CATEGORY_10;
+
+import org.eclipse.rap.addons.chart.basic.MapChart;
+import org.eclipse.rap.addons.chart.basic.MapDataItem;
+import org.eclipse.rap.rwt.application.AbstractEntryPoint;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+
+
+public class MapSnippet extends AbstractEntryPoint {
+
+ private MapChart mapChart;
+
+ @Override
+ public void createContents( Composite parent ) {
+ parent.setLayout( new GridLayout() );
+ createMapChart( parent );
+ }
+
+ private void createMapChart( Composite parent ) {
+ mapChart = new WorldMapChart( parent, SWT.NONE );
+ mapChart.setLayoutData( new GridData( SWT.FILL, SWT.FILL, true, true ) );
+ //mapChart.setShowGraticule( true );
+ mapChart.setScaleFactor( 5 );
+ mapChart.setCenter( 23.3219, 42.6977 );
+ mapChart.addListener( SWT.Selection, new Listener() {
+ @Override
+ public void handleEvent( Event event ) {
+ System.out.println( "Selected country #" + event.index + "," + event.text );
+ }
+ } );
+ //mapChart.setColors( Colors.CATEGORY_10 );
+ mapChart.setItems( createItems() );
+ }
+
+ private static MapDataItem[] createItems() {
+ return new MapDataItem[] {
+ new MapDataItem( "100", "Bulgaria\nSofia", CATEGORY_10[ 0 ] ),
+ new MapDataItem( "DEU", "Item 2", CATEGORY_10[ 1 ] ),
+ new MapDataItem( "ES", "Item 3", CATEGORY_10[ 2 ] ),
+ new MapDataItem( "AUT", "Item 4", CATEGORY_10[ 3 ] ),
+ new MapDataItem( "ITA", "Item 5", CATEGORY_10[ 4 ] )
+ };
+ }
+
+}
diff --git a/examples/org.eclipse.rap.addons.chart.demo/src/org/eclipse/rap/addons/chart/demo/WorldMapChart.java b/examples/org.eclipse.rap.addons.chart.demo/src/org/eclipse/rap/addons/chart/demo/WorldMapChart.java
new file mode 100644
index 0000000..e7f61d6
--- /dev/null
+++ b/examples/org.eclipse.rap.addons.chart.demo/src/org/eclipse/rap/addons/chart/demo/WorldMapChart.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Ralf Sternberg - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.addons.chart.demo;
+
+import static org.eclipse.rap.rwt.RWT.getResourceManager;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.rap.addons.chart.basic.MapChart;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+import org.eclipse.rap.rwt.service.ResourceManager;
+import org.eclipse.swt.widgets.Composite;
+
+
+public class WorldMapChart extends MapChart {
+
+ public WorldMapChart( Composite parent, int style ) {
+ super( parent, style, registerGeoData( "resources/world-110m.json" ) );
+ }
+
+ private static String registerGeoData( String path ) {
+ ResourceManager resourceManager = getResourceManager();
+ if( !resourceManager.isRegistered( path ) ) {
+ try (InputStream inputStream = getResourceLoader().getResourceAsStream( path )) {
+ resourceManager.register( path, inputStream );
+ } catch( Exception exception ) {
+ throw new RuntimeException( "Failed to register resource " + path, exception );
+ }
+ }
+ return resourceManager.getLocation( path );
+ }
+
+ private static ResourceLoader getResourceLoader() {
+ final ClassLoader classLoader = WorldMapChart.class.getClassLoader();
+ return new ResourceLoader() {
+ @Override
+ public InputStream getResourceAsStream( String resourceName ) throws IOException {
+ return classLoader.getResourceAsStream( resourceName );
+ }
+ };
+ }
+
+}
diff --git a/tests/org.eclipse.rap.addons.chart.test/src/org/eclipse/rap/addons/chart/basic/MapChart_Test.java b/tests/org.eclipse.rap.addons.chart.test/src/org/eclipse/rap/addons/chart/basic/MapChart_Test.java
new file mode 100644
index 0000000..2d2afbe
--- /dev/null
+++ b/tests/org.eclipse.rap.addons.chart.test/src/org/eclipse/rap/addons/chart/basic/MapChart_Test.java
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EclipseSource and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Ralf Sternberg - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.rap.addons.chart.basic;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.eclipse.rap.json.JsonArray;
+import org.eclipse.rap.json.JsonObject;
+import org.eclipse.rap.json.JsonValue;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.client.Client;
+import org.eclipse.rap.rwt.client.service.JavaScriptLoader;
+import org.eclipse.rap.rwt.internal.remote.ConnectionImpl;
+import org.eclipse.rap.rwt.remote.Connection;
+import org.eclipse.rap.rwt.remote.RemoteObject;
+import org.eclipse.rap.rwt.testfixture.TestContext;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+@SuppressWarnings( { "restriction", "deprecation" } )
+public class MapChart_Test {
+
+ private static final String PROP_TOPOJSON_JS_URL = "org.eclipse.rap.addons.chart.topojsonJsUrl";
+ private static final String DEF_TOPOJSON_JS_URL
+ = "https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.20/topojson.min.js";
+
+ private Display display;
+ private Shell shell;
+ private RemoteObject remoteObject;
+ private Connection connection;
+ private MapChart chart;
+
+ @Rule
+ public TestContext context = new TestContext();
+
+ @Before
+ public void setUp() {
+ display = new Display();
+ shell = new Shell( display );
+ remoteObject = mock( RemoteObject.class );
+ connection = fakeConnection( remoteObject );
+ chart = new MapChart( shell, SWT.NONE, "resources/world-110m.json" );
+ }
+
+ @Test
+ public void testCreate_requiresTopojsonJS() {
+ JavaScriptLoader loader = mock( JavaScriptLoader.class );
+ fakeLoader( loader );
+
+ new MapChart( shell, SWT.NONE, "resources/world-110m.json" );
+
+ verify( loader ).require( DEF_TOPOJSON_JS_URL );
+ }
+
+ @Test
+ public void testCreate_requiresTopojsonJS_fromSystemProperty() {
+ JavaScriptLoader loader = mock( JavaScriptLoader.class );
+ fakeLoader( loader );
+
+ System.setProperty( PROP_TOPOJSON_JS_URL, "custom://url" );
+ new MapChart( shell, SWT.NONE, "resources/world-110m.json" );
+ System.clearProperty( PROP_TOPOJSON_JS_URL );
+
+ verify( loader, never() ).require( DEF_TOPOJSON_JS_URL );
+ verify( loader ).require( "custom://url" );
+ }
+
+ @Test
+ public void testCreate_registeresJavaScriptResource() {
+ assertTrue( RWT.getResourceManager().isRegistered( "chart/topojson/topojson-world.js" ) );
+ }
+
+
+ @Test
+ public void testCreate_registeresCssResource() {
+ assertTrue( RWT.getResourceManager().isRegistered( "resources/topojson-world.css" ) );
+ }
+
+ @Test
+ public void testCreate_createsRemoteObject() {
+ verify( connection ).createRemoteObject( eq( "rwt.chart.Chart" ) );
+ }
+
+ @Test
+ public void testCreate_setsRenderer() {
+ verify( remoteObject ).set( "renderer", "topojson-world" );
+ }
+
+ @Test
+ public void testCreate_setsDataPath() {
+ JsonObject expected = new JsonObject().add( "dataPath", "resources/world-110m.json" );
+ verify( remoteObject ).call( "setOptions", expected );
+ }
+
+ @Test
+ public void testGetGraticule_defaultsToFalse() {
+ assertFalse( chart.getShowGraticule() );
+ }
+
+ @Test
+ public void testSetGraticule_changesValue() {
+ chart.setShowGraticule( true );
+
+ assertTrue( chart.getShowGraticule() );
+ }
+
+ @Test
+ public void testSetGraticule_isRendered() {
+ chart.setShowGraticule( true );
+
+ verify( remoteObject ).call( "setOptions", new JsonObject().add( "showGraticule", true ) );
+ }
+
+ @Test
+ public void testGetScaleFactor_defaultsToOne() {
+ assertEquals( 1d, chart.getScaleFactor(), 0d );
+ }
+
+ @Test
+ public void testSetScaleFactor_changesValue() {
+ chart.setScaleFactor( 1.1 );
+
+ assertEquals( 1.1, chart.getScaleFactor(), 0d );
+ }
+
+ @Test
+ public void testSetScaleFactor_isRendered() {
+ chart.setScaleFactor( 1.1 );
+
+ verify( remoteObject ).call( "setOptions", new JsonObject().add( "scaleFactor", 1.1 ) );
+ }
+
+ @Test
+ public void testGetCenter_defaultsToZero() {
+ assertArrayEquals( new double[] { 0d, 0d }, chart.getCenter(), 0d );
+ }
+
+ @Test
+ public void testSetCenter_changesValue() {
+ chart.setCenter( 1, 2 );
+
+ assertArrayEquals( new double[] { 1d, 2d }, chart.getCenter(), 0d );
+ }
+
+ @Test
+ public void testSetCenter_isRendered() {
+ chart.setCenter( 1, 2 );
+
+ JsonArray center = new JsonArray().add( 1 ).add( 2 );
+ verify( remoteObject ).call( "setOptions", new JsonObject().add( "center", center ) );
+ }
+
+ @Test
+ public void testSetColors_byArray_isRendered() {
+ RGB[] colors = {
+ new RGB( 0x1f, 0x77, 0xb4 ),
+ new RGB( 0xff, 0x7f, 0x0e ),
+ new RGB( 0x2c, 0xa0, 0x2c )
+ };
+
+ chart.setColors( colors );
+
+ JsonArray expectedColors = new JsonArray()
+ .add( "#1f77b4" )
+ .add( "#ff7f0e" )
+ .add( "#2ca02c" );
+ verify( remoteObject ).call( "setOptions", new JsonObject().add( "colors", expectedColors ) );
+ }
+
+ @Test
+ public void testSetItems() {
+ chart.setItems( new MapDataItem( "BGR", "foo" ),
+ new MapDataItem( "DEU", "bar", new RGB( 0x1f, 0x77, 0xb4 ) ) );
+
+ String expected = "{\"countries\": {"
+ + " \"BGR\": { \"label\": \"foo\" },"
+ + " \"DEU\": { \"label\": \"bar\", \"color\": \"#1f77b4\" }"
+ + "}}";
+ verify( remoteObject ).call( "setOptions", JsonValue.readFrom( expected ).asObject() );
+ }
+
+ private void fakeLoader( JavaScriptLoader loader ) {
+ Client client = mock( Client.class );
+ when( client.getService( JavaScriptLoader.class ) ).thenReturn( loader );
+ context.replaceClient( client );
+ }
+
+ private Connection fakeConnection( RemoteObject remoteObject ) {
+ ConnectionImpl connection = mock( ConnectionImpl.class );
+ when( connection.createRemoteObject( anyString() ) ).thenReturn( remoteObject );
+ when( connection.createServiceObject( anyString() ) ).thenReturn( mock( RemoteObject.class ) );
+ context.replaceConnection( connection );
+ return connection;
+ }
+
+}