This library was originally conceived to render the home page and next generation of IXP directory for Packet Clearing House (PCH).
It's primary function is to convert any dataset to a customizable set of components of Map, Filters and Table:
- Map - A fully customizable SVG map which dynamically responds to filters and can be exported to a stand alone SVG for external consumption
- Table - A tabular representation of your dataset which can be sorted by header rows. This also dynamically responds to filters.
- Filters - A programmatically generated list of drop downs and input fields to drill down into your dataset
You can also browse other code samples and examples here:
- Dependencies
- Declaring MapTable elements
- Import datasets
- Map datasets
- Declaring MapTable elements
- Import datasets
- Map datasets
- Dataset requirements
- Columns details
-
[columnsDetails format](#columnsdetails-format)
-
- Naming conventions
- ScaledValue
- Map
-
[Options](#options)
-
- Map
- Filters
- Table
- Export as SVG
- Contribute
-
[Set up your development environment](#set-up-your-development-environment)
-
[Requirements](#requirements)
-
-
[Getting Started](#getting-started)
-
[Todo](#todo)
-
- Release History
- D3.js
- TopoJSON*: homepage or download (cdnjs)
* Only used if you need a map
Here is minimum amount of HTML to render a MapTable with Map, Filter and Table.
<div id='vizContainer'></div>
<script src="d3.min.js"></script> <!-- You can import it from cdnjs.com or bower-->
<script src="topojson.min.js"></script> <!-- You can remove this line if you're not using the map --> <!-- You can import it from cdnjs.com or bower -->
<script src="maptable.min.js"></script> <!-- You can import it from cdnjs.com or bower -->
<script>
var viz = d3.maptable('#vizContainer')
.csv('/examples/data/ixp.csv')
.map({ path: '/examples/maps/world-110m.json' }) // You can remove this line if you want to disable the map
.filters() // You can remove this line if you want to disable filters
.table() // You can remove this line if you want to disable the table
.render(); // This is important to render the visualization
</script>
MapTable is available on cdnjs.com. Remember though, cool kids concatenate their scripts to minimize http requests.
If you want to style the MapTable elements with some existing styles, you can prepend the above HTML with:
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="/maptable.css">
To create a visualization (Map or/and Table or/and Filters) of your dataset, you need to provide first the container of your visualization
<div id='vizContainer'></div>
The default order of the 3 components is Map
, Filters
and Table
. If you want to place the components in a different order, you can put them on the main container:
<div id='vizContainer'>
<div id='mt-map'></div>
<div id='mt-filters' class='panel panel-default'></div>
<div id='mt-table'></div>
</div>
You instantiate the MapTable library into the viz
variable based on the #vizContainer
ID you declared in the DOM:
<script>
var viz = d3.maptable('#vizContainer'); // #vizContainer is the css selector that will contain your visualization
</script>
The MapTable viz
declaration in the above example is a chain of functions. The possible functions that you can use are:
- viz.json(jsonPath) with
jsonPath
as string - viz.csv(csvPath) with
csvPath
as string - viz.tsv(tsvPath) with
tsvPath
as string - viz.columns(columnDetails) with
columnDetails
as a JS dictionary. You can add/remove it of you want to customize your columns or create virtual columns based on the data. - viz.map(mapOptions) with
mapOptions
as a JS dictionary. You can add/remove it of you want a map on your visualization. - viz.filters(filtersOptions) with
filtersOptions
as a JS dictionary. You can add/remove it of you want filters on your visualization. - viz.table(tableOptions) with
tableOptions
as a JS dictionary. You can add/remove it of you want a table on your visualization. - viz.render() that closes the chain and renders the visualization. Don't forget this!
Datasets can be defined by using one of the these three sources:
# viz.json(url)
Import JSON file at the specified url with the mime type "application/json".
# viz.csv(url)
Import CSV file at the specified url with the mime type "text/csv".
# viz.tsv(url)
Import TSV file at the specified url with the mime type "text/tab-separated-values".
To plot lands and countries on the map, we're using TopoJSON library. The map can be generated through this tool: topojson-map-generator.
In order to plot your dataset on a map, there are minimum set of columns needed.
If you're planning to add markers on your map, you would need to provide latitude
, longitude
of your markers. You can also edit these keys name using the map options longitudeKey
, latitudeKey
.
[
{"longitude": "13.23000", "latitude": "-8.85000"},
{"longitude": "168.32000", "latitude": "-17.75000"},
]
If you're planing to add country related information, you should provide consistent country information on your dataset from the TopJSON file. You should provide at least one of these types on your mapOptions:
countryIdentifierKey:
(string, default: 'country_code') Column name of country identifier (from the dataset). It goes as pair with the optioncountryIdentifierType
.countryIdentifierType:
(string, default: 'iso_a2') Country identifier type that we're using to attach data to countries on the map. The available types are:iso_a2
(default): ISO_3166-1_alpha-2 country code formatiso_a3
: ISO_3166-1_alpha-3 country code formatname
: Country name that came from the GeoJSON map file.continent
: Continent name that came from the GeoJSON map file.
For example for this dataset:
[
{"country_code": "MAR", "Country Name": "Morocco", "bar": "foo"},
{"country_code": "FRA", "Country Name": "France", "bar": "foo"},
]
You would use these options:
{
countryIdentifierKey: 'country_code',
countryIdentifierType: 'iso_a3'
}
or
{
countryIdentifierKey: 'Country Name',
countryIdentifierType: 'name'
}
By default, MapTable imports all the columns and detects their format automatically. As well, you can customize behaviors of specific columns and create virtual columns.
# viz.columns(columnDetails)
with columnDetails
as a JS dictionary. You can add/remove it of you want to customize your columns or create virtual columns based on the data.
<PUT_YOUR_COLUMN_KEY>:
(Object) Provide the columns key here to apply the options below to it. If this key is not defined in yoru dataset, then it will be dynamically added as virtual column:nowrap:
(bool, default: false) When present, it specifies that the content inside a that column should not wrap.title:
(string, default: columnKey) What we show as column name in filters and table.filterMethod:
(string, default: 'field') Column format type, used for filtering. Available options:field
: filter by keyworddropdown
: exact match using a dropdowncompare
: filter using comparison (≥, ≤, between ....)
virtual
: (function(d), default: null) To create a new column that doesn't exists in the dataset, and we'd like to show it on the table or filters. You can also use it if you want to transform an existing column.cellContent
: (function(d), default: null) Function that transforms an existing content using other rows. (for example to change the color depending on the data).dataParse:
(function(d), default: null) Function that return the formatted data used to sort and compare cells.filterInputType:
(string, default: 'text') HTML input type that we're using for the filters for that specific column (e.g. date, number, tel ...)
Example (adding nowrap
and type
to the region
column key):
.columns({
region: {
nowrap: true,
filterMethod: 'dropdown'
}
})
For the below examples, we define viz
as the variable that loads MapTable.
Functions that have d
as parameter, means that d
is a JS dictionary that contains data for one row.
Functions that have groupedData
as parameter, means that groupedData
is a JS dictionary { key: 'groupedByKey', values: [ {d}, ... ] }
that contains the key that have been used to group the data, and the matching values related to that grouping.
We use this type to change the attributes of markers and countries.
It can be static value, for example a hex color for countries.attr.fill = '#FFFFFF'
.
Or if the value of the attribute is depending on the data, the expected value would be an object as explained on the below example.
For example, if we want to have countries background color to be related to a scale from green to red, and be white if the country don't have any related data. The value of countries.attr.fill
would be:
{
min: 'green', // Color for the minimum value
max: 'red', // Color for the maximum value
empty: 'white', // Color if no value is affected for that country or marker
legend: true, // Works only for the countries.attr.fill at the moment (contributions are welcome)
rollup: function (values) { // What is the value that we're attaching to the country / and attribute
// values is an array that contains rows that match that country or marker
return values.length; // for example here the count of row, it could be also the mean, sum...
}
}
If you want to attach the data boundaries to the value of an attribute, you may set as values for min and max as minValue
and maxValue
. For example, if we want to have markers radius to be related to a scale from minimum value and the maximum value, but also transform the value following a function. The value for the map options on markers.attr.r
would be:
{
min: 'minValue',
max: 'maxValue',
transform: function (val) {
return Math.sqrt(val);
},
}
#viz.map(mapOptions)
with mapOptions
as a JS dictionary. You can add/remove it of you want a map on your visualization.
path:
(string, required) URL of the TOPOJSON map, you can get them from Mike Bostock's repo: world atlas and us atlas. Or use this tool to generate these files as we did on the examples.width:
(integer, default:'window.innerWidth') Map Width.height:
(integer, default:'window.innerHeight') Map Height.zoom:
(bool, default: true) Enable zoom on the map (when scrolling up/down on the map).title:
(object, default: see below) Add a title within the map.title.bgColor:
(string, default: '#000000') Title font size.title.fontSize:
(integer, default: 12) Title font size.title.fontFamily:
(string, default: 'Helevetica, Arial, Sans-Serif') Title font family.title.content:
(function(countShown, countTotal, filtersDescription) Function to define how the title is renderedtitle.source:
(function())_ Function to define how the HTML in the title.
Example:
title: {
bgColor: "#F5F5F5",
fontSize: "11",
content: function(countShown, countTotal, filtersDescription) {
if (countShown === 0 || countTotal === 0) out = "No data shown";
else if (countShown < countTotal) out = 'Showing <tspan font-weight="bold">' + countShown + '</tspan> from <tspan font-weight="bold">' + countTotal + "</tspan>";
else out = '<tspan font-weight="bold">' + countTotal + "</tspan> shown";
if (filtersDescription !== '') out += " — " + filtersDescription;
return out;
},
source: function() {
return 'Source: <a xlink:href="http://www.example.com" target="_blank"><tspan font-weight="bold">example.com</tspan></a>';
}
},
scaleZoom:
([integer, integer], default: [1, 10]) The map zoom scale.scaleHeight:
(float, default: 1.0) Ratio to scale the map height.autoFitContent:
(bool, default: true) Enable auto zoom to focus on the active markers.fitContentMargin:
(integer, default: 10) Padding in pixels to leave when we filter on a specific area.ratioFromWidth:
(float, default: 0.5) Ratio between the height and the width: height/width, used to deduce the height from the the width.countryIdentifierKey:
(string, default: 'country_code') Column name of country identifier (from the dataset). It goes as pair with the optioncountryIdentifierType
.countryIdentifierType:
(string, default: 'iso_a2') Country identifier type that we're using to attach data to countries on the map. The available types are:iso_a2
(default): ISO_3166-1_alpha-2 country code formatiso_a3
: ISO_3166-1_alpha-3 country code formatname
: Country name that came from the GeoJSON map file.continent
: Continent name that came from the GeoJSON map file.
longitudeKey:
(string, default: 'longitude') Column name of the longitude (from the dataset).latitudeKey:
(string, default: 'latitude') Column name of the latitude (from the dataset).exportSvg:
(string, default: null) URL endpoint to download the current visualization as SVG. Read more on the section export SVG. (more details on a the section "Export as SVG")watermark:
(object, default: null) Add a watermark within the map.watermark.src:
(string) URL of the image (svg, png, jpg).watermark.width:
(integer) Image width.watermark.height:
(integer) Image height.watermark.position:
(string) Watermark position (top|middle|bottom) (left|middle|right). e.g.bottom left
.watermark.style:
(string) Additional css style for the watermark.
Example:
watermark: {
src: 'https://example.com/image.svg',
width: 130,
height: 60,
position: "bottom left",
style: "opacity:0.1"
},
markers:
(object, default: null) Add markers on the map.markers.customTag:
(function(markerObject)), default: null) This is more advanced feature. If you'd like to override the default market tag (svg:circle) to something different (like an image), you can use this callback function to append to the markerObject your custom implementation (see below example). x and y are coordinates in pixels of the marker.markers.attrX:
(string, default: 'cx') Attribute to position the marker on the X-Axismarkers.attrY:
(string, default: 'cy') Attribute to position the marker on the Y-Axismarkers.attrXDelta:
(integer, default: 0) Left relative margin of the markermarkers.attrYDelta:
(integer, default: 0) Top relative margin of the markermarkers.tooltipClassName:
(string, default: 'mt-map-tooltip popover bottom') Class name of the tooltip used for markers (we're using bootstrap).markers.tooltip:
(function(groupedData)) Function that returns html that we would use as content for the tooltip. We recommend you to use the bootstrap popover..markers.attr:
(object) Markers attributes (same naming as SVG attributes).markers.attr.fill:
(ScaledValue) Marker background color.markers.attr.r:
(ScaledValue) Marker radius.markers.attr.stroke:
(ScaledValue) Marker border color.markers.attr.stroke-width:
(ScaledValue) Marker border width.
Example (grouping by value):
markers: {
tooltip: function(a) {
out = '<div class="arrow"></div>';
out += '<span class="badge pull-right"> ' + a.values.length + '</span><h3 class="popover-title"> ' + a.key + '</h3>';
out += '<div class="popover-content">';
for (i = 0; i < a.values.length; i++) out += " • " + a.values[i].long_name + "<br>";
out += "</div>";
return out;
},
attr: {
r: {
min: "minValue",
max: "maxValue",
transform: function(v) {
return 3 * Math.sqrt(v);
},
rollup: function(values) {
return values.length;
},
},
fill: "yellow",
stroke: "#d9d9d9",
"stroke-width": 0.5
}
},
Example (with custom tag - Advanced feature):
markers: {
className: 'starsMarker',
customTag: function(markerObject){
return markerObject.append("svg:image")
.attr("xlink:href", "https://www.example.com/star.svg")
.attr("width", "13")
.attr("height", "27");
},
attrX: 'x',
attrY: 'y',
attrXDelta: -6,
attrYDelta: -13
},
countries:
(object, default: null) Add countries on the map.countries.rollup:
(function(groupedData)) Function that returns a value that we would use for every country.countries.tooltip:
(function(groupedData)) Function that returns html that we would use as content for the tooltip. We recommend you to use the bootstrap popover. The parameter isgroupedData
(check above on the naming conventions for more details).countries.attr:
(object) Markers attributes (same naming as svg attributes).countries.attr.fill:
(ScaledValue) Marker background color.countries.attr.r:
(ScaledValue) Marker radius.countries.attr.stroke:
(ScaledValue) Marker border color.countries.attr.stroke-width:
(ScaledValue) Marker border width.countries.attr.min:
(ScaledValue) Color for the minimum valuecountries.attr.max:
(ScaledValue) Color for the maximum valuecountries.attr.minNegative:
(ScaledValue, optional) Color for the minimum (closest to 0) negative value. Use this andmaxNegative
if you want to show different colors on the map for negative values. It is optional.countries.attr.maxNegative:
(ScaledValue, optional) Color for the maximum (farthest from 0) negative.countries.attr.empty
(ScaledValue) Color if no value is affected for that countrycountries.attr.legend:
(bool, default: false) show or hide the legendcountries.attr.rollup:
(function(groupedData), default: values.length) Function for the values we're attaching to the country and attribute. return value needs to be an array that contains rows that match that country or marker. Defaults tovalues.length
, the count of matching countries Example
countries: {
tooltip: function(a) {
out = '<div class="arrow"></div>';
if (a.values.length === 0) {
out += '<h3 class="popover-title"> ' + a.key + '</h3>';
out += '<div class="popover-content">N/A</div>';
} else {
out += '<h3 class="popover-title"> ' + a.values[0]['country_name'] + '</h3>';
out += '<div class="popover-content">' + a.values.length + '</div>';
}
return out;
},
attr: {
fill: {
min: "#a9b6c2",
max: "#6c89a3",
empty: "#f9f9f9",
rollup: function(values) {
return values.length;
},
},
stroke: "#d9d9d9",
"stroke-width": 0.5
},
},
# viz.filters(options)
with filtersOptions
as a JS dictionary. You can add/remove it of you want filters on your visualization.
show:
([string, ...], default: null) Set the order and the columns that we want to see in the filters.
If you want to add a table on your visualization:
# viz.table(tableOptions)
with tableOptions
as a JS dictionary. You can add/remove it of you want a table on your visualization.
show:
([string, ...], default: null) Set the order and the columns that we want to see in the table.className:
(string, default: 'table table-striped table-bordered') Table class namerowClassName:
(function(d), default: null) Function that returns the row class name depending on its content. Useful to highlight rows.defaultSorting:
(object, default: see below) How we sort things on the table.defaultSorting.key:
(string, default: ) default sorting on which column.defaultSorting.mode:
(string, default: 'asc') sorting mode:asc
for ascending,desc
for descending.
collapseRowsBy:
([string, ...], default: null) Array of columns that we want to be collapsed.
You can enable this feature to allow users download the map on their computer as SVG. However, you would need to set up a server endpoint that is going to allow users download the SVG file.
The sample code for a PHP server is located in /server/exportSvg.php
. Contributions are welcomed for implementations of in other languages.
- Mohammed Elalj @melalj - Original Author & Lead Architect
- Ashley Jones @Ths2-9Y-LqJt6 - Feature Requester & Deliverer, QA, Love, Release Engineer
You are welcomed to fork the project and make pull requests.
Install any items with "sudo":
- NodeJs, type
npm -v
on your terminal to check if you have it. node.js 4 and npm 2 versions or higher required. - Gulp
sudo npm install -g gulp
- Bower
sudo npm install -g bower
Run these commands as your unprivileged user you're doing your development as:
- Run
npm install
to install dependencies - Run
bower install
to download Browser Javascript libraries - Run
gulp
to start the local dev environment on http://localhost:5000 - Edit files in
./dev
and they will be automatically compiled to./src
- To have production ready files, run:
gulp dist
. All built files are located in the folder./dist
- Enjoy 🍻
- Publish v1
- Write unit tests 🙏
- Improve documentation (spell, formulation, emoji...)
- Secondary sorting
- Append SVG filters to the map and use them as styling
- Legend gradient transformation (if we used the log scale)
- Have multiple legends depending on the attribute
- Legend marker radius
- Version 1.2.1 September 21 2016
- Fix bad use of
attrValue
in GeoMap.js - Issue #30
- Fix bad use of
- Version 1.2 September 20 2016
- Version 1.1.1 July 14 2016
- Version 1.1 June 22 2016
- Version 1.0.2 May 16 2016
- Version 1.0.1 Mar 25 2016
- First Full featured release
- Version 1.0.0
- Initial commit